проверить правильность адреса mmap

Я пишу высоконагруженный демон, который должен работать на FreeBSD 8.0, а также на Linux. Основная цель демона — передавать файлы, запрашиваемые по их идентификатору. Идентификатор преобразуется в локальное имя файла/размер файла через запрос к БД. Затем я использую последовательные вызовы mmap() для передачи файловых блоков с помощью send().

Однако иногда возникает несоответствие размера файла в БД и файла в файловой системе (реальный размер ‹ размер в БД). В этой ситуации я отправил все реальные блоки данных, и когда следующий блок данных отображается -- mmap не возвращает никаких ошибок, только обычный адрес (я также проверял переменную errno, она равна нулю после mmap). И когда демон пытается отправить этот блок, он получает Segmentation Fault. (Это поведение гарантированно выдается на FreeBSD 8.0 amd64)

Я использовал безопасную проверку перед открытием, чтобы гарантировать размер с вызовом stat(). Однако реальная жизнь показывает мне, что segfault все еще может быть поднят в редких ситуациях.

Итак, мой вопрос, есть ли способ проверить, доступен ли указатель, прежде чем разыменовывать его? Когда я открыл ядро ​​​​в gdb, gdb говорит, что данный адрес выходит за рамки. Возможно, есть другое решение, которое кто-то может предложить.

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#define FILENAME        "./datafile"

int main()
{
    unsigned long i, j;

    srand(time(NULL));
    unsigned long pagesize = sysconf(_SC_PAGESIZE);

    unsigned long basesize = 4 * pagesize;
    unsigned long cropsize = 2 * pagesize;

    // create 4*pagesize sized file
    int f = creat(FILENAME, 0644);
    for (i = 0; i < basesize; i++) {
        unsigned char c = (unsigned char)rand();
        if (write(f, &c, 1) < 1) { perror("write"); break; }
    }
    close(f);

    f = open(FILENAME, O_RDONLY);

    // walk trough file
    unsigned char xor = 0;
    unsigned long offset = 0;
    for (j = 0; j < 4; j++) {
        // trunc file to 2*pagesize
        if (j == 2) truncate(FILENAME, cropsize);

        char *data = mmap(NULL, pagesize, PROT_READ, MAP_PRIVATE, f, offset);
        if (data == MAP_FAILED) { perror("mmap"); break; }
        printf("mmap: %lu@%lu for %i\n", pagesize, offset, f);

        for (i = 0; i < pagesize; i++) xor ^= data[i];

        offset += pagesize;
    }

    close(f);

    return 0;
}

person reddot    schedule 25.03.2010    source источник


Ответы (1)


Конечно, я не могу доказать это отсюда, но я сильно подозреваю, что у вас просто ошибка бухгалтерского учета в вашем коде. Если вы вызываете mmap и передаете размер, и это удается, вы не должны получать SIGSEGV.

Я рекомендую вам применить valgrind к вашему расследованию.

Во многих системах Linux /proc/PID/maps покажет вам, какие регионы сопоставлены с какими правами доступа.

person bmargulies    schedule 25.03.2010
comment
Я поместил пример кода, иллюстрирующий проблему, в заголовке. Этот код эмулирует создание файла, а затем изменение его размера во время работы с mmap. В системе Linux у меня ошибка шины на третьем шаге, во FreeBSD есть SegFault. - person reddot; 26.03.2010