MMAP(2)

ИМЯ
mmap, munmap - отображает файлы или устройства в памяти, или удаляет их отображение

ОБЗОР
#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags,
           int fd, off_t offset);
int munmap(void *addr, size_t length);

Информацию по требованиям макроса тестирования свойств смотрите в разделе ЗАМЕЧАНИЯ.

ОПИСАНИЕ
Вызов mmap() создаёт новое отображение в виртуальном адресном пространстве вызывающего процесса. Адрес начала нового отображения указывается в addr. В аргументе length задаётся длина отображения (должна быть больше 0).

Если значение addr равно NULL, то ядро само выбирает адрес (выровненный по странице), по которому создаётся отображение; это наиболее переносимый метод создания нового отображения. Если значение addr не равно NULL, то ядро учитывает это при размещении отображения; в Linux отображение будет создано в странице ближайшей к границе. Адрес нового отображения возвращается как результат вызова.

Содержимое файлового отображения (в отличие от анонимного отображения; смотрите MAP_ANONYMOUS далее) инициализируется данными из файла (или объекта), на который указывает файловый дескриптор fd, длиной length байт, начиная со смещения offset. Значение offset должно быть кратно размеру (возвращается sysconf(_SC_PAGE_SIZE)) страницы.

В аргументе prot указывается желаемая защита памяти отображения (не должна конфликтовать с режимом открытого файла). Значением может быть PROT_NONE или побитово сложенные (OR) следующие флаги:

PROT_EXEC
    Страницы доступны для исполнения.

PROT_READ
    Страницы доступны для чтения.

PROT_WRITE
    Страницы доступны для записи.

PROT_NONE
    Страницы недоступны.

В аргументе flags задаётся будут ли изменения отображения видимы другим процессам, отображающим ту же область, и будут ли изменения перенесены в отображённый файл. Данное поведение определяется в flags одним из следующих значений:

MAP_SHARED
    Сделать отображение общим. Изменения отображения видимы всем процессам, отображающим ту же область и (если отображение выполняется из файла) изменения заносятся в отображённый файл (для более точного контроля над изменениями файла нужно использовать msync(2)).

MAP_PRIVATE
    Создать закрытое отображение с механизмом копирования при записи. Изменения отображения невидимы другим процессам, отображающим тот же файл, и сам файл не изменяется. Не определено, будут ли видимы в отображённой области изменения в файле, сделанные после вызова mmap().

Эти флаги описаны в POSIX.1-2001 и POSIX.1-2008.

Кроме этого в flags могут быть указаны (побитовым сложением):

MAP_32BIT (начиная с Linux 2.4.20, 2.6)
    Поместить отображение в первые 2 гигабайта адресного пространства процесса. Этот флаг поддерживается только на архитектуре x86-64 для 64-битных программ. Он был добавлен для размещения стеков нитей в первых 2 ГБ памяти, что даёт увеличение производительности при переключения контекста на некоторых первых 64-битных процессорах. В современных процессорах x86-64 такой проблемы с производительностью больше нет, поэтому на таких системах данный флаг больше не требуется. Он игнорируется, если указан флаг MAP_FIXED.

MAP_ANON
    Синоним MAP_ANONYMOUS. Устарел.

MAP_ANONYMOUS
    Отображение не привязанное к файлу; его содержимое инициализируется нулями. Аргумент fd игнорируется ; однако в некоторых реализациях при указании MAP_ANONYMOUS (или MAP_ANON) требуется указывать fd равное -1, и так нужно поступать для переносимости приложений. Аргумент offset должен быть ноль. Использование MAP_ANONYMOUS вместе с MAP_SHARED поддерживается в Linux только начиная с ядра версии 2.4.

MAP_DENYWRITE
    Этот флаг игнорируется. Раньше он обозначал, что попытки записи в подчинённые файлы должны завершаться с кодом ошибки ETXTBUSY. Но это стало основой для атак типа отказа в обслуживании.

MAP_EXECUTABLE
    Этот флаг игнорируется.

MAP_FILE
    Флаг для совместимости, игнорируется.

MAP_FIXED
    Не учитывать addr как подсказку: помещать отображение точно по этому адресу. Значение addr должно быть выровнено соответствующим образом: на большинстве архитектур он должно быть кратно размеру страницы; однако некоторые архитектуры могут накладывать дополнительные ограничения. Если область памяти, задаваемая addr и len, перекрывается со страницами существующих отображений, то перекрывающаяся часть существующих отображений будет отброшена. Если заданный адрес не может быть использован, то вызов mmap() завершается ошибкой. В переносимом ПО этот флаг нужно использовать осторожно, так как точная раскладка процесса в памяти, доступная для изменения, может значительно отличаться в разных версиях ядер, библиотеки С и выпусках операционной системы.

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

    Например, нить А просматривает /proc/<pid>/maps и находит доступный адресный диапазон, а в тоже время нить Б захватывает часть или всё тоже адресное пространство. Нить А вызывает mmap(MAP_FIXED), что приводит в перезаписи отображения, созданного нитью Б.

    Нить Б может не создавать отображение напрямую — достаточно простого вызова библиотечной функции, для которого внутри задействуется dlopen(3) для загрузки какой-то другой общей библиотеки. Вызов dlopen(3) отобразит библиотеку в адресное пространство процесса. Кроме этого, почти любой библиотечный вызов может бы реализован способом, который добавляет отображения памяти в адресное пространство этим же способом или просто выделяя память. В качестве примера можно упомянуть brk(2), malloc(3), pthread_create(3) и библиотеки PAM http://www.linux-pam.org.

MAP_GROWSDOWN
    Этот флаг используется для стеков. Для виртуальной системы памяти ядра он обозначает, что отображение должно расширяться вниз по памяти. Возвращаемый адрес указывает на одну страницу ниже области памяти, которая в действительности создаётся в виртуальном адресном пространстве процесса. Обращение к адресу ниже «защитной» страницы отображения приведёт к расширению отображения на страницу. Увеличение таким способом можно повторять до тех пор, пока рост отображения страницы верхним концом не достигнет следующего нижнего отображения, при таком обращении к «защитной» страницы возникнет сигнал SIGSEGV.

MAP_HUGETLB (начиная с Linux 2.6.32)
    Выделять отображение используя «огромные страницы». Дополнительную информацию смотрите в файле исходного кода ядра Linux Documentation/vm/hugetlbpage.txt, а также следующее дополнение.

MAP_HUGE_2MB, MAP_HUGE_1GB (начиная с Linux 3.8)
    Используется как дополнение к MAP_HUGETLB для выбора размера страницы hugetlb (2 МБ и 1 ГБ, соответственно), сработает только в системе которая поддерживает различные размеры больших страниц.

    Вообще, желаемый размер огромной страницы можно настроить закодировав логарифм 2 от желаемого размера страницы в шести битах со смещением MAP_HUGE_SHIFT (значение нуля в этом битовом поле означает выбор значения огромной страницы по умолчанию; это значение можно найти в поле Hugepagesize просмотрев файл /proc/meminfo). Таким образом две показанные выше константы определены как:

        #define MAP_HUGE_2MB    (21 << MAP_HUGE_SHIFT)
        #define MAP_HUGE_1GB    (30 << MAP_HUGE_SHIFT)

    Рабочий диапазон страниц огромного размера может быть обнаружен, перечислив подкаталоги в /sys/kernel/mm/hugepages.

MAP_LOCKED (начиная с Linux 2.5.37)
    Пометить отображаемую область как заблокированную таким же образом как с помощью mlock(2). Данная реализация будет пытаться заполнить (предотказ) область полностью, но вызов mmap() не завершится ошибкой ENOMEM, если это не удастся сделать. Поэтому действительные отказы могут произойти позднее. Такой алгоритм не совпадает с mlock(2). Нужно использовать mmap() плюс mlock(2), если действительные отказы недопустимы после инициализации отображения. В старых ядрах флаг MAP_LOCKED игнорируется.

MAP_NONBLOCK (начиная с Linux 2.5.46)
    Данный флаг имеет смысл только вместе с MAP_POPULATE. Не выполнять упреждающее чтение: только создать записи в таблице страниц для страниц, которые уже есть ОЗУ. Начиная с Linux 2.6.23 этот флаг приводит к тому, что выполнение работы MAP_POPULATE отменяется. Когда-нибудь комбинация MAP_POPULATE и MAP_NONBLOCK может быть реализована заново.

MAP_NORESERVE
    Не резервировать страницы пространства подкачки для этого отображения. Если пространство подкачки резервируется, то для отображения гарантируется возможность изменения. Если оно не резервируется, то можно получить сигнал SIGSEGV при записи, если физическая память будет недоступна. Смотрите также описание файла /proc/sys/vm/overcommit_memory в proc(5). В ядрах до 2.6 этот флаг действовал только для частных доступных на запись отображений.

MAP_POPULATE (начиная с Linux 2.5.46)
    Наполнить (до возникновения страничного прерывания, prefault) страничные таблицы отображения. Для файлового отображения это приводит к опережающему чтению из файла. В дальнейшем это помогает сократить блокировку по отказу страниц. Флаг MAP_POPULATE поддерживается для закрытых отображений только начиная с Linux 2.6.23.

MAP_STACK (начиная с Linux 2.6.27)
    Разместить отображение по адресу, пригодному для процесса или для стека нити. В настоящее время для этого флага не выполняются какие-либо действия, но он используется для реализации нитей в glibc; если на какой-то архитектуре потребуются специальные действия по выделению стека, то позже их поддержка может быть прозрачно добавлена в glibc.

MAP_UNINITIALIZED (начиная с Linux 2.6.33)
    Не очищать анонимные страницы. Этот флаг предназначен для повышения производительности на встраиваемых устройствах. Он учитывается, только если ядро было собрано с поддержкой параметра CONFIG_MMAP_ALLOW_UNINITIALIZED. Так как этот параметр может привести к нарушению безопасности, обычно он устанавливается только на встраиваемых устройствах (то есть, устройствах, где только один человек имеет полный контроль над содержимым пользовательской памяти).

Из флагов, перечисленных выше, в POSIX.1-2001 и POSIX.1-2008 определён только MAP_FIXED. Однако, большинство систем также поддерживают MAP_ANONYMOUS (или его синоним MAP_ANON).

Память, отображённая с помощью mmap(), сохраняется при fork(2) с теми же атрибутами.

Файл отображается по кратному размеру страницы. Для файла, который не кратен размеру страницы, оставшаяся память при отображении заполняется нулями, и запись в эту область не приводит к изменению файла. Действия при изменении размера отображаемого файла на страницы, которые соответствуют добавленным или удалённым областям файла, не определены.

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

Адрес addr должен быть кратен размеру страницы (но значения length это не касается). Все страницы, содержащие часть указанного диапазона, удаляются из отображения и последующие ссылки на эти страницы приводят к генерации сигнала SIGSEGV. Это не ошибка, если указанный диапазон не содержит каких-либо отображённых страниц.

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
При успешном выполнении mmap() возвращается указатель на отображённую область. При ошибке возвращается значение MAP_FAILED (а именно, (void *) -1) и errno устанавливается в соответствующее значение.

При успешном выполнении munmap() возвращает 0. При сбои возвращается -1, и код ошибки кладётся в errno (скорее всего EINVAL).

ОШИБКИ

EACCES
    Файловый дескриптор указывает на не обычный файл. Или было запрошено отображение файла (mapping), но fd не открыт на чтение. Или был указан флаг MAP_SHARED и установлен бит PROT_WRITE, но fd не открыт в режиме чтения/записи (O_RDWR). Или был указан флаг PROT_WRITE, но файл доступен только для дополнения.

EAGAIN
    Файл заблокирован, или блокируется слишком много памяти (смотрите setrlimit(2)).

EBADF
    Значение fd не является правильным файловым дескриптором (и MAP_ANONYMOUS не установлен).

EINVAL
    Неправильное значение addr, length или offset (например, оно либо слишком велико, либо не выровнено по границе страницы).

EINVAL
    (начиная с Linux 2.6.12) Значение length равно 0.

EINVAL
    Значение flags не содержит MAP_PRIVATE или MAP_SHARED, или содержит сразу эта эти флага.

ENFILE
    Достигнуто максимальное количество открытых файлов в системе.

ENODEV
    Используемая файловая система для указанного файла не поддерживает отображение памяти.

ENOMEM
    Больше нет доступной памяти.

ENOMEM
    Процесс превысил бы ограничение на максимальное количество отображений. Эта ошибка также может возникнуть в munmap() при удалении отображения области в середине существующего отображения, так как при этом выполняется удаление отображения двух отображений меньшего размера на любом конце области.

ENOMEM
    (начиная с Linux 4.7) Было бы превышено ограничение процесса RLIMIT_DATA, описанное в getrlimit(2).

EOVERFLOW
    На 32-битной архитектуре вместе с расширением для больших файлов (т.е., используется 64-битный off_t): количество страниц, используемых для length плюс количество страниц, используемых для offset приводит к переполнению unsigned long (32 бита).

EPERM
    Аргументом prot запрашивается PROT_EXEC, но отображённая область принадлежит файлу на файловой системе, которая смонтирована с флагом no-exec.

EPERM
    Выполнение операции предотвращено опечатыванием (file seal); смотрите fcntl(2).

ETXTBSY
    Был задан флаг MAP_DENYWRITE, но объект, указываемый fd, открыт на запись.

При использовании отображаемой области памяти могут возникать следующие сигналы:

SIGSEGV
    Попытка записи в область, отображённую только для чтения.

SIGBUS
    Попытка доступа к части буфера, которая не совпадает файлом (например, она может находиться за пределами файла. Подобной является ситуация, когда другой процесс уменьшил длину файла).

АТРИБУТЫ
Описание терминов данного раздела смотрите в attributes(7).
Интерфейс | Атрибут | Значение
mmap(), munmap() | Безвредность в нитях | MT-Safe

СООТВЕТСТВИЕ СТАНДАРТАМ
POSIX.1-2001, POSIX.1-2008, SVr4, 4.4BSD.

ДОСТУПНОСТЬ
В системах POSIX, в которых есть вызовы mmap(), msync(2) и munmap(), значение _POSIX_MAPPED_FILES, определённое в <unistd.h>, больше 0 (смотрите также sysconf(3)).

ЗАМЕЧАНИЯ
На некоторых архитектурах (например, i386), флаг PROT_WRITE подразумевает флаг PROT_READ. Также от архитектуры зависит подразумевает ли PROT_READ флаг PROT_EXEC или нет. Переносимые программы должны всегда устанавливать PROT_EXEC, если они собираются выполнять код, находящийся в отображении.

Переносимый способ создания отображения: указать в addr значение 0 (NULL) и убрать MAP_FIXED из flags. В этом случае, система сама выберет адрес для отображения; адрес, выбранный таким образом, не будет будет конфликтовать с существующими отображениями и не будет равен 0. Если указан флаг MAP_FIXED и значение addr равно 0 (NULL), то адрес отображения будет равен 0 (NULL).

Некоторые константы flags определены только, если определён подходящий макрос тестирования свойств (возможно, по умолчанию): _DEFAULT_SOURCE в glibc 2.19 и новее; _BSD_SOURCE или _SVID_SOURCE в glibc 2.19 и старее (также достаточно использовать _GNU_SOURCE и требовать, этот макрос логично, так как данные флаги есть только в Linux). Соответственно, флаги: MAP_32BIT, MAP_ANONYMOUS (и синоним MAP_ANON), MAP_DENYWRITE, MAP_EXECUTABLE, MAP_FILE, MAP_GROWSDOWN, MAP_HUGETLB, MAP_LOCKED, MAP_NONBLOCK, MAP_NORESERVE, MAP_POPULATE и MAP_STACK.

Приложение может определить какие страницы отображены в данный момент в буфере/страничном кэше с помощью mincore(2).

  Изменение временных отметок для отображённых файлов
У отображённых файлов поле st_atime может измениться в любой момент между вызовом mmap() и соответствующим удалением отображения; первое обращение к отображённой странице приведёт к обновлению поля, если это ещё не было сделано.

Поля st_ctime и st_mtime у отображённого с помощью флагов PROT_WRITE и MAP_SHARED файла будут обновлены после записи отображённой области и перед последующим вызовом msync(2) с флагом MS_SYNC или MS_ASYNC, если он будет вызван.

  Отображения огромных страниц (Huge TLB)
Для отображений, работающих с огромными страницами, требования к аргументам mmap() и munmap() несколько отличаются от требований к отображениям, в которых используются страницы с системным размером.

Для mmap(), offset должно быть кратно размеру нижележащей огромной страницы. Система автоматически выравнивает length до кратного значения размера нижележащей огромной страницы.

Для munmap(), addr и length должны быть кратны размеру нижележащей огромной страницы.

  Отличия между библиотекой C и ядром
В данной странице описывается интерфейс, предоставляемый обёрточной функцией glibc mmap(). Раньше, эта функция обращалась к системному вызову с тем же именем. Начиная с ядра 2.4, данный системный вызов был заменён на mmap2(2). В настоящее время обёрточная функция glibc, mmap(), вызывает mmap2(2) с подходящим подкорректированным значением offset.

ДЕФЕКТЫ
В Linux не гарантируется результат флага MAP_NORESERVE, описанный выше. По умолчанию, любой процесс может быть принудительно завершён в любой момент, если в системе закончилась память.

В ядрах до версии 2.6.7 флаг MAP_POPULATE учитывается только, если значение prot равно PROT_NONE.

В SUSv3 указано, что mmap() должен завершаться с ошибкой, если length равно 0. Однако в ядрах до версии 2.6.12 вызов mmap() в этом случае выполняется успешно: отображение не создаётся и вызов возвращает addr. Начиная с ядра версии 2.6.12, в этом случае вызов mmap() завершается с ошибкой EINVAL.

В POSIX сказано, что система всегда должна заполнять нулями любую частичную страницу у конца объекта и что система никогда не должна вносить любые изменения вне пределов объекта. В Linux, если вы пишите данные в такую частичную страницу за концом объекта, то данные остаются в страничном кэше даже после закрытия и выключения отображения файла и хотя данные никогда не пишутся в сам файл, последующие отображения могут увидеть изменённое содержимое. В некоторых случаях это можно исправить вызвав msync(2) перед выключением отображения; однако это не работает на tmpfs(5) (например, когда используется интерфейс общей памяти POSIX, описанный в shm_overview(7)).

ПРИМЕР

Следующая программа выводит часть файла, указанного в первом аргументе командной строки, в стандартный вывод. Диапазон выдаваемых байт задаётся смещением и длиной во втором и третьем аргументах командной строки. Программа создаёт отображение требуемых страниц файла и затем использует write(2) для вывода запрошенных байт.

  Исходный код программы
    #include <sys/mman.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #define handle_error(msg) \
        do { perror(msg); exit(EXIT_FAILURE); } while (0)
    int
    main(int argc, char *argv[])
    {
        char *addr;
        int fd;
        struct stat sb;
        off_t offset, pa_offset;
        size_t length;
        ssize_t s;
        if (argc < 3 || argc > 4) {
            fprintf(stderr, "%s файл смещение [длина]\n", argv[0]);
            exit(EXIT_FAILURE);
        }
        fd = open(argv[1], O_RDONLY);
        if (fd == -1)
            handle_error("open");
        if (fstat(fd, &sb) == -1)           /* получение размера файла */
            handle_error("fstat");
        offset = atoi(argv[2]);
        pa_offset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1);
            /* для mmap() нужно выронить смещение */
        if (offset >= sb.st_size) {
            fprintf(stderr, "указанное смещение находится за концом файла\n");
            exit(EXIT_FAILURE);
        }
        if (argc == 4) {
            length = atoi(argv[3]);
            if (offset + length > sb.st_size)
                length = sb.st_size - offset;
                    /* Нельзя показать байты за концом файла */
        } else {    /* Не указана длина ==> показать всё до конца файла */
            length = sb.st_size - offset;
        }
        addr = mmap(NULL, length + offset - pa_offset, PROT_READ,
                    MAP_PRIVATE, fd, pa_offset);
        if (addr == MAP_FAILED)
            handle_error("mmap");
        s = write(STDOUT_FILENO, addr + offset - pa_offset, length);
        if (s != length) {
            if (s == -1)
                handle_error("write");
            fprintf(stderr, "частичная запись");
            exit(EXIT_FAILURE);
        }
        munmap(addr, length + offset - pa_offset);
        close(fd);
        exit(EXIT_SUCCESS);
    }

СМОТРИТЕ ТАКЖЕ
ftruncate(2), getpagesize(2), memfd_create(2), mincore(2), mlock(2), mmap2(2), mprotect(2), mremap(2), msync(2), remap_file_pages(2), setrlimit(2), shmat(2), userfaultfd(2), shm_open(3), shm_overview(7)

Описание в proc(5) следующих файлов: /proc/[pid]/maps, /proc/[pid]/map_files и /proc/[pid]/smaps.

B.O. Gallmeister, POSIX.4, O'Reilly, страницы 128–129 и 389–391.