Идея
Обычно работа с файлом: open → read(fd, buf, n) в свой буфер →
работа с буфером. Это копирование: page cache → user buffer.
С mmap: open → mmap(fd) → получаешь указатель → читаешь/пишешь
как обычный массив. Никакого read(), ядро лениво подгружает
страницы при page-fault'е.
int fd = open("data.bin", O_RDONLY);void *p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
// теперь p[42] = первый прочитанный байт; страница загрузится при первом обращении
Виды mappings
File-backed:
MAP_PRIVATE- приватная копия. Запись не уходит в файл. Используется для загрузки бинарей/библиотек.MAP_SHARED- изменения видны всем кто маппил тот же файл, в т.ч. сохраняются на диск. Это и есть shared memory через файл.
Anonymous (MAP_ANONYMOUS, без fd):
MAP_PRIVATE | MAP_ANONYMOUS- обычный heap; так работаетmalloc()для больших аллокацийMAP_SHARED | MAP_ANONYMOUS- shared между fork-чайлдами
Зачем использовать
- БД и search engines - постгрес, лусен, sqlite используют mmap для своих data-файлов. Page cache работает за них.
- Загрузка бинарей - все ELF-файлы и
.soмаппятся (/proc/<pid>/maps) - Большие файлы random access - mmap'ить, прыгать по offset'ам. Ядро прогружает только нужные страницы.
- IPC между процессами -
MAP_SHAREDна одном файле = быстрый shared region между независимыми процессами. Без копирований. - Memory-mapped I/O для устройств (
/dev/mem)
/dev/shm - POSIX shared memory
Это tmpfs, специально для shared mappings:
int fd = shm_open("/myseg", O_CREAT | O_RDWR, 0600);ftruncate(fd, 1024 * 1024);
void *p = mmap(NULL, 1024*1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
// → p - указатель на 1 MB; другой процесс делает shm_open("/myseg") и видит то же самоеФайл по факту в /dev/shm/myseg - можно увидеть через ls /dev/shm.
Размер /dev/shm обычно 50% RAM (tmpfs). Менять через mount:
sudo mount -o remount,size=8G /dev/shm
Postgres использует /dev/shm для shared_buffers? Нет - обычно
System-V shared memory или mmap файла напрямую. /dev/shm чаще
используют redis, scientific computing, video processing.
Когда mmap ВРЕДЕН
- Сетевые ФС - NFS/CIFS могут давать стрёмные результаты (consistency через сеть)
- Огромные файлы > VAS на 32-bit - на 32-bit системах VAS = 3-4 GB
- Append-heavy workload - постоянно расширять mmap'нутый файл
дорого; обычный
write()лучше - fault-storm - random access к холодному файлу = тысячи major faults, может задохнуться
madvise - подсказки ядру
madvise(addr, len, MADV_SEQUENTIAL); // буду читать последовательно - большой readahead
madvise(addr, len, MADV_RANDOM); // случайно - отключить readahead
madvise(addr, len, MADV_DONTNEED); // эти страницы больше не нужны - можно выкинуть
madvise(addr, len, MADV_HUGEPAGE); // склеить в huge pages если возможно
madvise(addr, len, MADV_DONTFORK); // не дублировать в child при fork
Дебаг и наблюдение
cat /proc/<pid>/maps # все mappings процесса
pmap -x <pid> # с размерами/RSS
cat /proc/<pid>/smaps | head -30 # на каждый regional блок:
# Size: 4 kB ← VSZ
# Rss: 4 kB ← реально в RAM
# Pss: 2 kB ← proportional (делится на shared)
# Shared_Clean: 4 kB ← shared, чистая
# Private_Dirty: 0 kB
# Swap: 0 kB
PSS (Proportional Set Size) - лучшая метрика «реально использует»: shared библиотека на 100MB между 10 процессами даст каждому PSS = 10MB, RSS у каждого = 100MB.
Связь с другими частями
- mmap файла + shared = page cache (page-cache). Та же страница
видна и в обычном
read(), и черезmmap. - Anonymous mmap = heap = swap'ится при нехватке RAM (swap)
- Все process-and-pid процессы разделяют
libc.so.6черезMAP_PRIVATE-mappings (read-only код shared)