mmap, разделяемая память и копирование при записи
Системный вызов mmap
#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);
| Аргумент | Значение |
|---|---|
addr |
желаемый адрес; NULL — ядро выберет само |
length |
размер (округляется вверх до целого числа страниц) |
prot |
права: PROT_READ, PROT_WRITE, PROT_EXEC, PROT_NONE |
flags |
MAP_SHARED или MAP_PRIVATE, плюс MAP_ANONYMOUS, MAP_FIXED… |
fd, offset |
файл и смещение в нём; для анонимной памяти -1 и 0 |
Возвращает адрес начала отображения или MAP_FAILED ((void *)-1) при ошибке — не NULL!
Четыре вида отображений
MAP_PRIVATE |
MAP_SHARED |
|
|---|---|---|
| Файл | код и данные программ и библиотек; изменения — личные (COW), в файл не попадают | «файл в памяти»: изменения видны всем и записываются в файл |
Анонимная (MAP_ANONYMOUS) |
обычная память: большие блоки malloc, стеки потоков |
общая память родителя и потомков после fork |
Разделяемая память для родственных процессов
Строки из задачи 8:
mem = mmap(NULL, sizeof(Mem), PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (mem == MAP_FAILED) _exit(1);
mem->counter = 0;
mem->state = 0;
pid_t pid = fork();
MAP_ANONYMOUS— память без файла, заполнена нулями.MAP_SHARED— послеforkу родителя и потомка в таблицах страниц будут ссылки на одни и те же физические кадры, без copy-on-write. Запись одного процесса сразу видна другим.- Потомки потомков тоже наследуют отображение — все процессы цепочки работают с одной структурой
Mem.
Общая память — общие проблемы
Раз все пишут в одну структуру, возникают гонки: mem->counter++ у двух процессов одновременно, перезапись mem->pid и mem->ppid разными процессами вперемешку. Для корректной работы нужна синхронизация — модуль «Взаимодействие процессов и синхронизация».
Что делает ядро при mmap
- Проверяет аргументы и права (для файла — открыт ли он на чтение/запись).
- Выбирает свободный диапазон виртуальных адресов нужного размера (в страницах).
- Создаёт новую VMA с правами и флагами; для файла — ссылку на файл (его inode и кеш страниц); для
MAP_SHARED|MAP_ANONYMOUS— внутренний объект разделяемой памяти (shmem). - Таблицы страниц не заполняет — физическая память выделяется лениво, при первом обращении (страничный отказ).
- Возвращает адрес.
Отображение файлов
int fd = open("data.bin", O_RDWR);
struct stat st;
fstat(fd, &st);
char *p = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd); /* отображение остаётся! */
p[0] = 'X'; /* изменяем файл как массив */
msync(p, st.st_size, MS_SYNC); /* принудительно записать грязные страницы */
munmap(p, st.st_size);
Чтение страницы файла — страничный отказ и чтение с диска в кеш страниц; запись — бит D, и позже writeback запишет страницу в файл.
Copy-on-write ещё раз
MAP_PRIVATE и fork используют один механизм:
- страница помечается «только чтение» в таблицах всех, кто её разделяет;
- попытка записи → страничный отказ защиты;
- ядро видит, что VMA доступна для записи, а страница — COW: копирует кадр, ставит новую ссылку с правом записи;
- остальные процессы продолжают видеть старый кадр.
Другие виды разделяемой памяти
Для неродственных процессов анонимная память не подходит — нужно имя:
/* POSIX */
int fd = shm_open("/myshm", O_CREAT | O_RDWR, 0600);
ftruncate(fd, sizeof(Mem));
Mem *m = mmap(NULL, sizeof(Mem), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
/* ... */
shm_unlink("/myshm");
/* System V */
int id = shmget(key, sizeof(Mem), IPC_CREAT | 0600);
Mem *m2 = shmat(id, NULL, 0);
shmdt(m2);
shmctl(id, IPC_RMID, NULL);
POSIX-объекты видны в /dev/shm, System V — в ipcs -m.
Итог
mmapсоздаёт область адресного пространства; ошибка —MAP_FAILED; размер кратен странице.MAP_SHARED— изменения общие (и в файл),MAP_PRIVATE— копирование при записи.MAP_SHARED | MAP_ANONYMOUS+fork— общая память родителя и всех потомков.- Ядро при
mmapтолько создаёт VMA; кадры выделяются при страничных отказах. - Общая память требует синхронизации.
Практика решено 0 из 5
Выделите разделяемую анонимную память под структуру Mem (чтение и запись) — так, чтобы её видели
родитель и потомки после fork. Проверьте результат на MAP_FAILED (при ошибке — _exit(1)).
Mem *mem;
mem = mmap(NULL, sizeof(Mem), PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (mem == MAP_FAILED) {
_exit(1);
}
Что напечатает родитель?
int *p = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
*p = 1;
if (fork() == 0) { *p = 2; _exit(0); }
wait(NULL);
printf("%d\n", *p);
MAP_SHARED или MAP_PRIVATE?
| MAP_SHARED | MAP_PRIVATE | |
|---|---|---|
| Изменения видны другим процессам, отобразившим тот же объект | ||
| Изменения файла записываются обратно в файл | ||
| Запись создаёт личную копию страницы (copy-on-write) | ||
| Так загрузчик отображает секцию данных .data программы | ||
| Подходит для обмена данными между родителем и потомками |
Что делает ядро при вызове mmap(NULL, 16, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0)?
Сколько байт физической памяти реально займёт mmap(NULL, 16, ...), после того как в неё записали?
Войдите, чтобы отмечать пройденные уроки.