MMAP(2) | Руководство программиста Linux | 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 ядро выберет ближайшую к границе страницу (но всегда выше или равною значению, заданному в /proc/sys/vm/mmap_min_addr) и попытается создать отображение. Если по этому адресу уже есть отображение, то ядро выберет новый адрес, который может и не зависеть от подсказки. Адрес нового отображения возвращается как результат вызова.
Содержимое файлового отображения (в отличие от анонимного отображения; смотрите MAP_ANONYMOUS далее) инициализируется данными из файла (или объекта), на который указывает файловый дескриптор fd, длиной length байт, начиная со смещения offset. Значение offset должно быть кратно размеру (возвращается sysconf(_SC_PAGE_SIZE)) страницы.
После возврата из вызова mmap() файловый дескриптор fd может быть немедленно закрыт без признания отображения недействительным.
В аргументе prot указывается желаемая защита памяти отображения (не должна конфликтовать с режимом открытого файла). Значением может быть PROT_NONE или побитово сложенные (OR) следующие флаги:
В аргументе flags задаётся будут ли изменения отображения видимы другим процессам, отображающим ту же область, и будут ли изменения перенесены в отображённый файл. Данное поведение определяется в flags одним из следующих значений:
Флаги MAP_SHARED и MAP_PRIVATE описаны в POSIX.1-2001 и POSIX.1-2008. Флаг MAP_SHARED_VALIDATE является расширением Linux.
Кроме этого в flags могут быть указаны (побитовым сложением):
#define MAP_HUGE_2MB (21 << MAP_HUGE_SHIFT) #define MAP_HUGE_1GB (30 << MAP_HUGE_SHIFT)
Из флагов, перечисленных выше, в POSIX.1-2001 и POSIX.1-2008 определён только MAP_FIXED. Однако, большинство систем также поддерживают MAP_ANONYMOUS (или его синоним MAP_ANON).
Память, отображённая с помощью mmap(), сохраняется при fork(2) с теми же атрибутами.
Файл отображается по кратному размеру страницы. Для файла, который не кратен размеру страницы, оставшаяся память при отображении заполняется нулями, и запись в эту область не приводит к изменению файла. Действия при изменении размера отображаемого файла на страницы, которые соответствуют добавленным или удалённым областям файла, не определены.
Системный вызов munmap() удаляет отображение для указанного адресного диапазона и это приводит к тому, что дальнейшее обращение по адресам внутри диапазона приводит к генерации неправильных ссылок на память. Также для диапазона отображение автоматически удаляется при завершении работы процесса. С другой стороны, закрытие файлового дескриптора не приводит к удалению отображения диапазона.
Адрес addr должен быть кратен размеру страницы (но значения length это не касается). Все страницы, содержащие часть указанного диапазона, удаляются из отображения и последующие ссылки на эти страницы приводят к генерации сигнала SIGSEGV. Это не ошибка, если указанный диапазон не содержит каких-либо отображённых страниц.
При успешном выполнении mmap() возвращается указатель на отображённую область. При ошибке возвращается значение MAP_FAILED (а именно, (void *) -1) и errno устанавливается в соответствующее значение.
При успешном выполнении munmap() возвращает 0. При сбои возвращается -1, и код ошибки кладётся в errno (скорее всего EINVAL).
При использовании отображаемой области памяти могут возникать следующие сигналы:
Описание терминов данного раздела смотрите в 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).
Единственным вариантом безопасного использования MAP_FIXED является предварительное резервирование адресного пространства, указываемого в addr и length, другим отображением; в остальных случаях использование MAP_FIXED опасно, так как оно выполняет принудительное удаление существующих отображений, что позволяет легко повредить собственное адресное пространство многонитевого процесса.
Предположим, например, что нить A просматривает /proc/<pid>/mapsв поиске неиспользуемого адресного диапазона, который она сможет отобразить используя MAP_FIXED, но одновременно с этим нить B захватывает часть или весь этот же адресный диапазон. Когда после этого нить A запустит mmap(MAP_FIXED), это, фактически, разобьёт отображение, созданное нитью B. В этом сценарии нити B не нужно создавать отображение явным образом; будет достаточно просто сделать библиотечный вызов, например, dlopen(3) для загрузки какой-то другой общей библиотеки. Вызов dlopen(3) отобразит библиотеку в адресное пространство процесса. Более того, почти каждый библиотечный вызов можно реализовать так, чтобы он добавлял отображения памяти в адресное пространство с помощью этого метода или просто выделяя память. Например, такими вызовами являются brk(2), malloc(3), pthread_create(3) и библиотеки PAM http://www.linux-pam.org.
Начиная с Linux 4.17, в многонитевых программах можно использовать флаг MAP_FIXED_NOREPLACE и, тем самым, избежать опасности, описанной выше, когда выполняется попытка создать отображение по фиксированному адресу, который не был зарезервирован существующим отображением.
У отображённых файлов поле st_atime может измениться в любой момент между вызовом mmap() и соответствующим удалением отображения; первое обращение к отображённой странице приведёт к обновлению поля, если это ещё не было сделано.
Поля st_ctime и st_mtime у отображённого с помощью флагов PROT_WRITE и MAP_SHARED файла будут обновлены после записи отображённой области и перед последующим вызовом msync(2) с флагом MS_SYNC или MS_ASYNC, если он будет вызван.
Для отображений, работающих с огромными страницами, требования к аргументам mmap() и munmap() несколько отличаются от требований к отображениям, в которых используются страницы с системным размером.
Для mmap(), offset должно быть кратно размеру нижележащей огромной страницы. Система автоматически выравнивает length до кратного значения размера нижележащей огромной страницы.
Для munmap(), addr и length должны быть кратны размеру нижележащей огромной страницы.
В данной странице описывается интерфейс, предоставляемый обёрточной функцией 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.
2019-02-27 | Linux |