mmap отличается поведением на Intel (i7) и на руке?

Я использую программу, скомпилированную на Opensuse 13.1 с процессором Intel i7. Я скомпилировал ту же программу в qemu (виртуальной) среде для имитации OpenSuse 13.1 с процессором arm. Эта строка кода:

rvp = mmap(rvp, length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED, fildes, 0);

дает мне указатель на память в Ram. Тем не менее, размер этой памяти различается между Intel и Arm:

в Intel размер равен «длине», независимо от размера файла, на который указывает fildes.

В ARM размер (приблизительно) равен размеру файла, на который указывает fildes, и игнорирует тот факт, что размер больше, чем fildes.

Я хочу выделить больше памяти, чем просто файл...

РЕДАКТИРОВАТЬ: я пытался обойти эту проблему двумя последовательными вызовами... безуспешно:

    rvp = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    unsigned int i;
    for (i = 0; i < length; i += 5000)
            printf("acces buffer at %i --> %u\n", i, rvp[i]);
    rvp = mmap(rvp, length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED, fildes, 0);
    for (i = 0; i < length; i += 5000)
            printf("acces buffer at %i --> %u\n", i, rvp[i]);

в качестве вывода я получаю:

access buffer at 0 --> 0
...
access buffer at 90000 -> 0
access buffer at 0 --> 0
...
access buffer at 85000 --> 0
Segmentation fault...

person Chris Maes    schedule 18.02.2014    source источник


Ответы (2)


официальная спецификация mmap не очень ясна в этом вопросе, но, пожалуйста, обратите внимание на фразу:

Ссылки на [память][,] в пределах диапазона адресов, начинающихся с pa и продолжающихся len байтов[,] до целых страниц после конца объекта, должны приводить к доставке сигнала SIGBUS.

Я расставил несколько запятых и добавил акцент, чтобы прояснить мысль: если вы mmap имеете область большего размера, чем файл, который ее поддерживает, ОС должна подать сигнал, если вы попытаетесь получить доступ за пределы конец файла, при условии, что вы переходите границу страницы (эта лицензия просто потому, что аппаратное обеспечение не позволяет ОС устанавливать границу между доступной и недоступной памятью, которая не попадает на границу страницы) (SIGBUS и SIGSEGV в настоящее время считаются взаимозаменяемыми многими операционными системами).

Вы были на правильном пути со своим подходом «два последовательных вызова», но он не сработал, потому что вы использовали одинаковую длину для обоих вызовов. Если бы вместо этого вы указали размер файла во втором вызове, это сработало бы. Вы можете получить фактический размер файла с помощью системного вызова fstat. Вам нужно проявить некоторую осторожность, если файл больше, чем желаемая длина выделения. Вот как бы я это написал:

char *map_file(int fd, size_t len)
{
    struct stat st;
    char *rv;

    if (fstat(fd, &st))
        return report_error("fstat");

    if (st.st_size >= (off_t) len) {
        /* If the file is at least as big as expected, just map the
           chunk we want. */
        rv = mmap(0, len, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
        if (rv == MAP_FAILED)
            return report_error("mmap");

    } else {
        /* Otherwise, we must allocate anonymous memory to fill in the gap. */
        char *anon;
        anon = mmap(0, len, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);
        if (anon == MAP_FAILED)
            return report_error("mmap (anon)");

        rv = mmap(anon, (size_t) st.st_size, PROT_READ|PROT_WRITE,
                  MAP_PRIVATE|MAP_FIXED, fd, 0);
        if (rv == MAP_FAILED) {
            int save_errno = errno;
            (void) munmap(anon, len);
            errno = save_errno;
            return report_error("mmap");
        }
    }
    return rv;
}

В зависимости от вашей более крупной цели вместо этого может иметь смысл увеличить файл, если он не такой большой, как ожидалось, используя ftruncate:

char *map_file(int fd, size_t len)
{
    struct stat st;
    char *rv;

    if (fstat(fd, &st))
        return report_error("fstat");
    /* if the file isn't as big as expected, make it bigger */
    if (st.st_size < (off_t) len)
        if (ftruncate(fd, (off_t) len))
            return report_error("ftruncate");

    rv = mmap(0, len, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
    if (rv == MAP_FAILED)
        return report_error("mmap");
    return rv;
}

Но, вероятно, если бы это было то, что вам нужно в контексте, вы бы использовали MAP_SHARED вместо MAP_PRIVATE.

(Примечание. Не показанная функция report_error регистрирует сообщение об ошибке, а затем возвращает NULL.)

person zwol    schedule 18.02.2014
comment
как я отметил в редактировании моего вопроса (1 час назад); Я попробовал ваше второе предложение: два последовательных вызова mmap... но второй снова обрезал доступную память до размера файла, который я читал... - person Chris Maes; 18.02.2014
comment
спасибо за вашу помощь, но я получил то, что хотел, используя альтернативный метод: rvp = malloc(length); unsigned int nread = read(fildes, rvp, length); - person Chris Maes; 18.02.2014
comment
@ChrisMaes Да, это произошло потому, что вы использовали одно и то же значение length в обоих вызовах mmap. Это сработало бы, если бы вы указали размер файла (возвращаемый функцией fstat) в качестве длины сопоставления во втором вызове. - person zwol; 18.02.2014

Хорошо, повозившись некоторое время, у меня есть ответы:

  • mmap следует использовать для просмотра файла, поэтому он не предназначен для выделения памяти больше, чем размер файла, к которому мы хотим получить доступ.
  • файл, к которому я обращался, случайно имел другой размер в моей системе Intel i7, поэтому есть причина, по которой он работал на Intel.

--> решение: используйте malloc для выделения памяти размера "длина", а затем используйте "чтение" для чтения файла...

person Chris Maes    schedule 18.02.2014