Использование флага MAP_ANONYMOUS и файла /dev/zero не предусмотрено стандартом SUSv3, хотя большинство реализаций UNIX поддерживает обе методики. Причина существования разных подходов с одним и тем же результатом заключается в том, что один из них пришел из мира BSD (MAP_ANONYMOUS), а другой — из System V (/dev/zero).
Анонимные отображения типа MAP_PRIVATE служат для выделения блоков памяти, принадлежащих только одному процессу и заполненных нулями. Чтобы создать такое отображение, можно воспользоваться файлом устройства /dev/zero:
fd = open("/dev/zero", O_RDWR);
if (fd == -1)
errExit("open");
addr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
if (addr == MAP_FAILED)
errExit("mmap");
Реализация вызова malloc(), входящая в библиотеку glibc, использует анонимные отображения типа MAP_PRIVATE для выделения блоков памяти, превышающих MMAP_THRESHOLD байт. Это позволяет эффективно уничтожать такие блоки (с помощью munmap()), если передать их вызову free() (таким образом снижается и вероятность фрагментации памяти при многократном выделении и уничтожении больших блоков). Константа MMAP_THRESHOLD по умолчанию равна 128 байтам, но данное значение можно откорректировать, задействуя библиотечную функцию mallopt().
Анонимные отображения типа MAP_SHARED позволяют родственным процессам (например, родителю и потомку) работать с одним и тем же участком памяти, не используя связанный с ним отображенный файл.
Анонимные отображения типа MAP_SHARED доступны в Linux, начиная с версии 2.4.
Методика, которую мы применяли для отображений типа MAP_ANONYMOUS, подходит и для MAP_SHARED:
addr = mmap(NULL, length, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, — 1, 0);
if (addr == MAP_FAILED)
errExit("mmap");
Если вслед за кодом, приведенным выше, выполнить вызов fork(), то новый дочерний процесс унаследует отображение и получит доступ к тому же участку памяти, что и родитель.
Программа, представленная в листинге 45.3, демонстрирует использование MAP_ANONYMOUS или файла /dev/zero (на выбор) для разделения отображенного участка между родительским и дочерним процессами. Выбор методики зависит от того, был ли определен макрос USE_MAP_ANON на этапе компиляции программы. Перед вызовом fork() родитель инициализирует разделяемый участок с помощью значения 1. Затем потомок инкрементирует это общее целое число и завершается; дождавшись завершения потомка, родитель выводит получившееся число. При выполнении данной программы мы увидим следующее:
$ ./anon_mmap
Child started, value = 1
In parent, value = 2
Листинг 45.3. Разделение анонимного отображения между родительским и дочерним процессами
mmap/anon_mmap.c
#ifdef USE_MAP_ANON
#define _BSD_SOURCE /* Получаем определение MAP_ANONYMOUS */
#endif
#include
#include
#include
#include "tlpi_hdr.h"
int
main(int argc, char *argv[])
{
int *addr; /* Указатель на общий участок памяти */
#ifdef USE_MAP_ANON /* Используем MAP_ANONYMOUS */
addr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, — 1, 0);
if (addr == MAP_FAILED)
errExit("mmap");
#else /* Отображаем /dev/zero */
int fd;
fd = open("/dev/zero", O_RDWR);
if (fd == -1)
errExit("open");
addr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED)
errExit("mmap");
if (close(fd) == -1) /* Больше не нужен */
errExit("close");
#endif
*addr = 1; /* Инициализируем целое число на отображенном участке */
switch (fork()) { /* Родитель и потомок разделяет отображение */
case -1:
errExit("fork");
case 0: /* Потомок инкрементирует общее число и завершается */
printf("Child started, value = %d\n", *addr);
(*addr)++;
if (munmap(addr, sizeof(int)) == -1)
errExit("munmap");
exit(EXIT_SUCCESS);
default: /* Родитель ждет завершения потомка */
if (wait(NULL) == -1)
errExit("wait");
printf("In parent, value = %d\n", *addr);
if (munmap(addr, sizeof(int)) == -1)
errExit("munmap");
exit(EXIT_SUCCESS);
}
}
mmap/anon_mmap.c
В большинстве UNIX-систем нельзя изменить местоположение и размер существующего отображения отображения. Однако Linux предоставляет (недоступный в других реализациях) вызов mremap(), который делает возможными такие изменения.
Аргументы old_address и old_size обозначают местоположение и размер существующего отображения, которое мы хотим расширить или уменьшить. Адрес, указанный в old_address, должен быть выровнен по странице; обычно это значение, возвращенное предыдущим вызовом mmap(). Новый запрашиваемый размер указывается с помощью аргумента new_size. Значения old_size и new_size округляются до следующего кратного размеру страницы памяти в системе.
#define _GNU_SOURCE
#include