18

この質問には関係のないいくつかのあいまいな理由により、libcのテキストセクションがメモリ内にある場所に近いページを取得するために、MAP_FIXEDを使用することに頼る必要があります。

mmap(2)を読む前に(最初にやるべきだった)、MAP_FIXEDとベースア​​ドレスがすでにマップされた領域と重なっている状態でmmapを呼び出すと、エラーが発生することを期待していました。

しかし、そうではありません。たとえば、これは特定のプロセスの/ proc/mapsの一部です

7ffff7299000-7ffff744c000 r-xp 00000000 08:05 654098                     /lib/x86_64-linux-gnu/libc-2.15.so

これは、次のmmap呼び出しを行った後..。

  mmap(0x7ffff731b000,
       getpagesize(),
       PROT_READ | PROT_WRITE | PROT_EXEC,
       MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED,
       0,
       0);

... になる:

7ffff7299000-7ffff731b000 r-xp 00000000 08:05 654098                     /lib/x86_64-linux-gnu/libc-2.15.so
7ffff731b000-7ffff731c000 rwxp 00000000 00:00 0 
7ffff731c000-7ffff744c000 r-xp 00083000 08:05 654098                     /lib/x86_64-linux-gnu/libc-2.15.so

つまり、libc専用の仮想アドレス空間の一部を自分のページで上書きしました。明らかに私が欲しいものではありません...

mmap(2)マニュアルのMAP_FIXED部分には、次のように明確に記載されています。

addrとlenで指定されたメモリ領域が既存のマッピングのページとオーバーラップする場合、既存のマッピングのオーバーラップした部分は破棄されます。

これは私が見ているものを説明していますが、いくつか質問があります:

  1. 何かがすでに特定のアドレスにマップされているかどうかを検出する方法はありますか?/ proc / mapsにアクセスせずに?
  2. 重複するページを見つけた場合にmmapを強制的に失敗させる方法はありますか?
4

3 に答える 3

9
  1. を使用page = sysconf(SC_PAGE_SIZE)してページ サイズを確認し、チェックしたい各ページ サイズのブロックをスキャンしますmsync(addr, page, 0)( を使用して(unsigned long)addr % page == 0、つまりaddrページに合わせて配置します)。で返さ-1れた場合errno == ENOMEM、そのページはマップされていません。

    編集済み: fons が以下にコメントしているように、mincore(addr,page,&dummy)は よりも優れていmsync()ます。(syscall の実装はmm/mincore.cLinux カーネル ソースにあり、C ライブラリは通常、更新するラッパーを提供します。syscall は、ページが整列さerrnoれていることを確認した直後にマッピング チェックを行うため、マップされていない場合に最適です ( ) 。 . ページが既にマップされている場合はある程度機能するため、パフォーマンスが最重要である場合は、マップされていることがわかっているページをチェックしないようにしてください。addrENOMEM

    これは、各ページごとに個別に行う必要があります。これは、領域が 1 ページよりも大きい場合はENOMEM、領域が完全にマップされていないことを意味するためです。まだ部分的にマップされている可能性があります。マッピングは、常にページ サイズの単位に細分化されます。

  2. 私が知る限りmmap()、領域が既にマップされているか、既にマップされているページが含まれているかどうかを判断する方法はありません。(同じことが にも当てはまるためmremap()、マッピングを作成してから目的のリージョンに移動することはできません)。

    これは、競合状態のリスクを冒すことを意味します。内部でメモリ割り当てを行ったり、メモリ マッピングを変更したりする場合に備えて、C ライブラリ ラッパーではなく、実際の syscall を自分で実行することをお勧めします。

    #define _GNU_SOURCE
    #include <unistd.h>
    #include <sys/syscall.h>
    
    static size_t page = 0;
    static inline size_t page_size(void)
    {
        if (!page)
            page = (size_t)sysconf(_SC_PAGESIZE);
        return page;
    }
    
    
    static inline int raw_msync(void *addr, size_t length, int flags)
    {
        return syscall(SYS_msync, addr, length, flags);
    }
    
    static inline void *raw_mmap(void *addr, size_t length, int prot, int flags)
    {
        return (void *)syscall(SYS_mmap, addr, length, prot, flags, -1, (off_t)0);
    }
    

ただし、何をしようとしても、最終的には/proc/self/mapsとにかく解析する必要があると思います。

  • 標準 I/Ostdio.hを完全に回避し (さまざまな操作でメモリが動的に割り当てられ、マッピングが変更されるため)、代わりunistd.hに、マッピングに影響を与える可能性がはるかに低い低レベルのインターフェイスを使用することをお勧めします。以下は、マップされた各領域とその領域で有効になっている保護を見つけるために使用できる、単純で大まかな関数のセットです (その他の情報は破棄します)。実際には、約 1 キロバイトのコードを使用し、スタックではそれ未満であるため、限られたアーキテクチャ (組み込みデバイスなど) でも非常に役立ちます。

    #include <unistd.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <string.h>
    
    #ifndef   INPUT_BUFFER
    #define   INPUT_BUFFER   512
    #endif /* INPUT_BUFFER */
    
    #ifndef   INPUT_EOF
    #define   INPUT_EOF     -256
    #endif /* INPUT_EOF */
    
    #define   PERM_PRIVATE  16
    #define   PERM_SHARED    8
    #define   PERM_READ      4
    #define   PERM_WRITE     2
    #define   PERM_EXEC      1
    
    typedef struct {
        int            descriptor;
        int            status;
        unsigned char *next;
        unsigned char *ends;
        unsigned char  buffer[INPUT_BUFFER + 16];
    } input_buffer;
    
    /* Refill input buffer. Returns the number of new bytes.
     * Sets status to ENODATA at EOF.
    */
    static size_t input_refill(input_buffer *const input)
    {
        ssize_t n;
    
        if (input->status)
            return (size_t)0;
    
        if (input->next > input->buffer) {
            if (input->ends > input->next) {
                memmove(input->buffer, input->next,
                        (size_t)(input->ends - input->next));
                input->ends = input->buffer + (size_t)(input->ends - input->next);
                input->next = input->buffer;
            } else {
                input->ends = input->buffer;
                input->next = input->buffer;
            }
        }
    
        do {
            n = read(input->descriptor, input->ends,
                     INPUT_BUFFER - (size_t)(input->ends - input->buffer));
        } while (n == (ssize_t)-1 && errno == EINTR);
        if (n > (ssize_t)0) {
            input->ends += n;
            return (size_t)n;
    
        } else
        if (n == (ssize_t)0) {
            input->status = ENODATA;
            return (size_t)0;
        }
    
        if (n == (ssize_t)-1)
            input->status = errno;
        else
            input->status = EIO;
    
        return (size_t)0;
    }
    
    /* Low-lever getchar() equivalent.
    */
    static inline int input_next(input_buffer *const input)
    {
        if (input->next < input->ends)
            return *(input->next++);
        else
        if (input_refill(input) > 0)
            return *(input->next++);
        else
            return INPUT_EOF;
    }
    
    /* Low-level ungetc() equivalent.
    */
    static inline int input_back(input_buffer *const input, const int c)
    {
        if (c < 0 || c > 255)
            return INPUT_EOF;
        else
        if (input->next > input->buffer)
            return *(--input->next) = c;
        else
        if (input->ends >= input->buffer + sizeof input->buffer)
            return INPUT_EOF;
    
        memmove(input->next + 1, input->next, (size_t)(input->ends - input->next));
        input->ends++;
        return *(input->next) = c;
    }
    
    /* Low-level fopen() equivalent.
    */
    static int input_open(input_buffer *const input, const char *const filename)
    {
        if (!input)
            return errno = EINVAL;
    
        input->descriptor = -1;
        input->status = 0;
        input->next = input->buffer;
        input->ends = input->buffer;
    
        if (!filename || !*filename)
            return errno = input->status = EINVAL;
    
        do {
            input->descriptor = open(filename, O_RDONLY | O_NOCTTY);
        } while (input->descriptor == -1 && errno == EINTR);
        if (input->descriptor == -1)
            return input->status = errno;
    
        return 0;
    }
    
    /* Low-level fclose() equivalent.
    */
    static int input_close(input_buffer *const input)
    {
        int result;
    
        if (!input)
            return errno = EINVAL;
    
        /* EOF is not an error; we use ENODATA for that. */
        if (input->status == ENODATA)
            input->status = 0;
    
        if (input->descriptor != -1) {
            do {
                result = close(input->descriptor);
            } while (result == -1 && errno == EINTR);
            if (result == -1 && !input->status)
                input->status = errno;
        }
    
        input->descriptor = -1;
        input->next = input->buffer;
        input->ends = input->buffer;
    
        return errno = input->status;
    }
    
    /* Read /proc/self/maps, and fill in the arrays corresponding to the fields.
     * The function will return the number of mappings, even if not all are saved.
    */
    size_t read_maps(size_t const n,
                     void **const ptr, size_t *const len,
                     unsigned char *const mode)
    {
        input_buffer    input;
        size_t          i = 0;
        unsigned long   curr_start, curr_end;
        unsigned char   curr_mode;
        int             c;
    
        errno = 0;
    
        if (input_open(&input, "/proc/self/maps"))
            return (size_t)0; /* errno already set. */
    
        c = input_next(&input);
        while (c >= 0) {
    
            /* Skip leading controls and whitespace */
            while (c >= 0 && c <= 32)
                c = input_next(&input);
    
            /* EOF? */
            if (c < 0)
                break;
    
            curr_start = 0UL;
            curr_end = 0UL;
            curr_mode = 0U;
    
            /* Start of address range. */
            while (1)
                if (c >= '0' && c <= '9') {
                    curr_start = (16UL * curr_start) + c - '0';
                    c = input_next(&input);
                } else
                if (c >= 'A' && c <= 'F') {
                    curr_start = (16UL * curr_start) + c - 'A' + 10;
                    c = input_next(&input);
                } else
                if (c >= 'a' && c <= 'f') {
                    curr_start = (16UL * curr_start) + c - 'a' + 10;
                    c = input_next(&input);
                } else
                    break;
            if (c == '-')
                c = input_next(&input);
            else {
                errno = EIO;
                return (size_t)0;
            }
    
            /* End of address range. */
            while (1)
                if (c >= '0' && c <= '9') {
                    curr_end = (16UL * curr_end) + c - '0';
                    c = input_next(&input);
                } else
                if (c >= 'A' && c <= 'F') {
                    curr_end = (16UL * curr_end) + c - 'A' + 10;
                    c = input_next(&input);
                } else
                if (c >= 'a' && c <= 'f') {
                    curr_end = (16UL * curr_end) + c - 'a' + 10;
                    c = input_next(&input);
                } else
                    break;
            if (c == ' ')
                c = input_next(&input);
            else {
                errno = EIO;
                return (size_t)0;
            }
    
            /* Permissions. */
            while (1)
                if (c == 'r') {
                    curr_mode |= PERM_READ;
                    c = input_next(&input);
                } else
                if (c == 'w') {
                    curr_mode |= PERM_WRITE;
                    c = input_next(&input);
                } else
                if (c == 'x') {
                    curr_mode |= PERM_EXEC;
                    c = input_next(&input);
                } else
                if (c == 's') {
                    curr_mode |= PERM_SHARED;
                    c = input_next(&input);
                } else
                if (c == 'p') {
                    curr_mode |= PERM_PRIVATE;
                    c = input_next(&input);
                } else
                if (c == '-') {
                    c = input_next(&input);
                } else
                    break;
            if (c == ' ')
                c = input_next(&input);
            else {
                errno = EIO;
                return (size_t)0;
            }
    
            /* Skip the rest of the line. */
            while (c >= 0 && c != '\n')
                c = input_next(&input);
    
            /* Add to arrays, if possible. */
            if (i < n) {
                if (ptr) ptr[i] = (void *)curr_start;
                if (len) len[i] = (size_t)(curr_end - curr_start);
                if (mode) mode[i] = curr_mode;
            }
            i++;
        }
    
        if (input_close(&input))
            return (size_t)0; /* errno already set. */
    
        errno = 0;
        return i;
    }
    

    この関数は、領域、開始アドレスを配列に、長さを配列に、権限を配列にread_maps()読み取り、マップの総数 ( より大きい場合があります) を返すか、エラーが発生した場合はゼロを設定して返します。nvoid *ptrlenmodenerrno

    上記の低レベル I/O に syscall を使用して、C ライブラリの機能をまったく使用しないようにすることは十分に可能ですが、それはまったく必要ではないと思います。(私が知る限り、C ライブラリは、これらの実際のシステムコールの周りに非常に単純なラッパーを使用します。)

これが役に立つことを願っています。

于 2013-02-19T03:30:01.480 に答える
7

「これは私が見ていることを説明していますが、いくつか質問があります。」

「何かがすでに特定のアドレスにマップされているかどうかを検出する方法はありますか? /proc/maps にアクセスせずに?」

はい、MAP_FIXED なしで mmap を使用します。

「重複するページが見つかった場合に強制的に mmap を失敗させる方法はありますか?」

どうやらそうではありませんが、mmap が要求されたアドレス以外のマッピングを返す場合は、mmap の後に単に munmap を使用してください。

MAP_FIXEDなしで使用すると、Linux と Mac OS X の両方で (そして他の場所でもあると思われます) mmap は、[アドレス、アドレス + 長さ] の範囲内に既存のマッピングが存在しない場合、アドレス パラメーターに従います。したがって、 mmap が指定したアドレスとは異なるアドレスでマッピングに応答する場合、その範囲にマッピングが既に存在し、別の範囲を使用する必要があると推測できます。mmap は通常、address パラメータを無視すると、非常に高いアドレスでマッピングに応答するため、単に munmap を使用して領域のマッピングを解除し、別のアドレスで再試行します。

mincore を使用してアドレス範囲が使用されているかどうかを確認することは、時間の無駄であるだけでなく (一度に 1 ページずつプローブする必要があります)、機能しない可能性があります。古い Linux カーネルは、ファイル マッピングに対してのみ適切に mincore を失敗させます。MAP_ANON マッピングについては、何も答えません。しかし、私が指摘したように、必要なのは mmap と munmap だけです。

Smalltalk VM 用のメモリ マネージャーを実装するこの演習を行ったところです。sbrk(0) を使用して、最初のセグメントをマップできる最初のアドレスを見つけ、mmap と 1Mb のインクリメントを使用して、後続のセグメント用のスペースを検索します。

static long          pageSize = 0;
static unsigned long pageMask = 0;

#define roundDownToPage(v) ((v)&pageMask)
#define roundUpToPage(v) (((v)+pageSize-1)&pageMask)

void *
sqAllocateMemory(usqInt minHeapSize, usqInt desiredHeapSize)
{
    char *hint, *address, *alloc;
    unsigned long alignment, allocBytes;

    if (pageSize) {
        fprintf(stderr, "sqAllocateMemory: already called\n");
        exit(1);
    }
    pageSize = getpagesize();
    pageMask = ~(pageSize - 1);

    hint = sbrk(0); /* the first unmapped address above existing data */

    alignment = max(pageSize,1024*1024);
    address = (char *)(((usqInt)hint + alignment - 1) & ~(alignment - 1));

    alloc = sqAllocateMemorySegmentOfSizeAboveAllocatedSizeInto
                (roundUpToPage(desiredHeapSize), address, &allocBytes);
    if (!alloc) {
        fprintf(stderr, "sqAllocateMemory: initial alloc failed!\n");
        exit(errno);
    }
    return (usqInt)alloc;
}

/* Allocate a region of memory of at least size bytes, at or above minAddress.
 *  If the attempt fails, answer null.  If the attempt succeeds, answer the
 * start of the region and assign its size through allocatedSizePointer.
 */
void *
sqAllocateMemorySegmentOfSizeAboveAllocatedSizeInto(sqInt size, void *minAddress, sqInt *allocatedSizePointer)
{
    char *address, *alloc;
    long bytes, delta;

    address = (char *)roundUpToPage((unsigned long)minAddress);
    bytes = roundUpToPage(size);
    delta = max(pageSize,1024*1024);

    while ((unsigned long)(address + bytes) > (unsigned long)address) {
        alloc = mmap(address, bytes, PROT_READ | PROT_WRITE,
                     MAP_ANON | MAP_PRIVATE, -1, 0);
        if (alloc == MAP_FAILED) {
            perror("sqAllocateMemorySegmentOfSizeAboveAllocatedSizeInto mmap");
            return 0;
        }
        /* is the mapping both at or above address and not too far above address? */
        if (alloc >= address && alloc <= address + delta) {
            *allocatedSizePointer = bytes;
            return alloc;
        }
        /* mmap answered a mapping well away from where Spur prefers.  Discard
         * the mapping and try again delta higher.
         */
        if (munmap(alloc, bytes) != 0)
            perror("sqAllocateMemorySegment... munmap");
        address += delta;
    }
    return 0;
}

これはうまく機能しているように見え、既存のマッピングをスキップしながら昇順のアドレスにメモリを割り当てます。

HTH

于 2014-06-24T17:56:19.857 に答える
4

それposix_mem_offset()が私が探していたもののようです。

アドレスがマップされているかどうかを通知するだけでなく、たまたまマップされている場合は、それが属するマップ領域の境界を暗黙的に提供します (len引数に SIZE_MAX を指定することにより)。

したがって、 を適用する前に 、使用しているアドレスがまだマッピングされていないことを確認するためにMAP_FIXED使用できます。posix_mem_offset()

msync()orを使用することもできますmincore() (エラーをチェックするENOMEMと、アドレスが既にマップされていることがわかります) が、その場合は盲目的になります (アドレスがマップされているエリアに関する情報はありません)。また、msync()パフォーマンスに影響を与える可能性のある副作用があり、mincore()BSD のみ (POSIX ではない) です。

于 2013-02-19T11:48:11.407 に答える