Страничная адресация: page fault и copy-on-write
Страничная адресация делит виртуальную память на страницы, а физическую — на фреймы одинакового размера. Таблицы страниц описывают, какой виртуальной странице соответствует какой физический фрейм и разрешены ли чтение, запись и исполнение. MMU обычно ускоряет перевод TLB-кэшем. Когда нужного валидного перевода или данных нет, CPU делает page fault, и ядро либо законно разрешает доступ, либо завершает процесс сигналом.
Зачем это на интервью
Эта модель объясняет latency spikes при cold start, медленное первое обращение к большой структуре, цену fork, неожиданный рост памяти после записи и разницу между SIGSEGV, major и minor fault. Для сервисов она важна в capacity planning: средняя память может быть нормальной, но burst записи после fork или reclaim приводит к OOM либо p99.
Минимум для E4
- Описывать путь virtual address → page-table entry → physical frame и роль TLB без привязки к одному формату архитектуры.
- Различать minor fault (страницу можно сопоставить без медленного диска) и major fault (обычно нужен I/O), понимая, что счётчики Linux — диагностический сигнал, а не абсолютная классификация задержки.
- Объяснять demand paging: физическая страница может быть выделена или прочитана при первом обращении, а не при
malloc/mmap. - Объяснять copy-on-write (COW): стороны сначала делят read-only страницу, а первая запись создаёт частную копию.
- Знать, что
forkне копирует всю RAM сразу, но последующие записи могут сделать его дорогим.
Углубление для E5/Senior
Перевод адреса и стоимость locality
Современные CPU используют многоуровневые page tables: полная плоская таблица для огромного 64-bit адресного пространства была бы чрезмерной. TLB хранит недавние translations; TLB miss может потребовать page-table walk, но это не обязательно page fault. Context switch, изменение mapping и некоторые операции защиты требуют согласования/инвалидации TLB на CPU, где процесс исполнялся (TLB shootdown). При очень интенсивном mmap/munmap или смене permissions это становится измеримым системным overhead.
Обычная страница часто 4 KiB, но huge pages уменьшают число TLB entries и page-table memory для больших последовательных наборов данных. Цена — грубее гранулярность, возможная внутренняя фрагментация, сложнее reclaim/compaction и менее гибкая latency. Transparent Huge Pages не «бесплатный ускоритель»: его нужно проверять на реальном workload, включая p99 и stalls.
Виды fault и давление памяти
- Minor fault может создать zero-filled anonymous page, установить mapping на уже имеющуюся page-cache page или выполнить COW; он всё равно использует CPU и может заметно накапливаться.
- Major fault обычно означает, что данные пришлось получить из backing store; I/O и очередь устройства могут сильно ухудшить tail latency.
- Protection fault возникает при нарушении прав, например записи в read-only COW page. Он может быть ожидаемым и обработанным ядром, либо привести к
SIGSEGV/SIGBUS. - При нехватке памяти ядро пытается reclaim page cache и anonymous pages; swap может сохранить работоспособность ценой задержки. Когда reclaim и swap недостаточны, Linux выбирает жертву OOM-killer с учётом контекста, в том числе cgroup.
COW в реальных системах
После fork родитель и ребёнок получают отдельные page tables, но shared physical pages помечаются так, чтобы запись вызвала копирование. Это быстро для fork + немедленный exec, поэтому Unix-процессы часто создают child именно так. Но fork многопоточного процесса особенно сложен: в ребёнке до exec безопасны лишь async-signal-safe операции, а большой объём записи после fork разрушает экономию COW.
MAP_PRIVATE даёт COW для file-backed mapping. Снимки файловых систем и VM могут использовать похожую идею на другом уровне, но не стоит автоматически переносить гарантии page-level COW ОС на storage snapshot. В базах данных pre-fork worker model и в container runtime полезно оценить dirty rate: одинаковый RSS до нагрузки не гарантирует одинаковое потребление памяти после независимой записи.
Ключевые понятия
| Понятие | Суть | Не путать с |
|---|---|---|
| Page table | Структура ядра/архитектуры для translation и permissions | Самими пользовательскими данными страницы |
| TLB | Кэш переводов виртуальных адресов | Page cache файловых данных |
| Page fault | Исключение CPU при отсутствии подходящего translation/разрешения | Обязательно фатальной ошибкой |
| Demand paging | Ленивое предоставление физической страницы или подгрузка | Немедленной аллокацией RAM при резерве адресов |
| COW | Разделение до первой записи, затем частная копия | Полной копией памяти при fork |
| Huge page | Страница большего размера | «Более быстрой RAM» без trade-offs |
Пример COW-последовательности:
- Родитель имеет anonymous page с данными и вызывает
fork. - Ребёнок получает mapping на тот же physical frame; обе PTE запрещают запись и указывают на COW-семантику.
- Ребёнок пишет байт: protection fault попадает в ядро.
- Ядро выделяет новый frame, копирует исходную страницу, меняет PTE ребёнка на writable и возобновляет инструкцию.
- Родитель продолжает видеть старую страницу; RSS и private dirty memory могут вырасти.
Типовые вопросы
- TLB miss и page fault — одно и то же?
- Нет. При TLB miss процессор может найти валидный PTE page-table walk без входа в ядро. Page fault — архитектурное исключение, требующее обработки ядром или сигнала.
- Почему первая запись в большой
make([]byte, n)может быть дороже выделения?- Runtime/allocator может зарезервировать адресное пространство, а demand paging создаёт zero pages по мере касания. Цена зависит от числа затронутых страниц и состояния памяти.
- Почему
forkобычно быстрый, но иногда приводит к OOM?- Он делит страницы через COW. Если родитель и ребёнок интенсивно пишут в общий первоначально набор, копии съедают RAM; cgroup limit может сработать раньше, чем общий лимит хоста.
- Всегда ли major page fault означает чтение с диска?
- Обычно он связан с необходимостью получить данные из backing store, но трактовка зависит от ядра и backing device. Смотрите одновременно latency, I/O, reclaim и trace, а не один счётчик.
- Как увидеть faults процесса в Linux?
- Использовать
ps -o min_flt,maj_flt,/proc/<pid>/stat,perf stat -p <pid>либо системные метрики. Снимать дельты за интервал под известной нагрузкой.
- Использовать
- Почему COW не является механизмом синхронизации?
- После первой записи стороны получают независимые копии; согласованного обмена обновлениями нет. Для IPC нужны shared memory с протоколом синхронизации, сокеты или другие явные примитивы.
Практика
- Наблюдение demand paging. Напишите Go-программу, которая резервирует большой
[]byte, ждёт ввода, затем пишет по одному байту на страницу. До и после каждого этапа снимите RSS иmin_flt/maj_flt.- Критерии готовности: указаны размер страницы и stride, приведены дельты счётчиков и RSS; вывод отличает резервирование от фактического касания.
- COW после fork. В изолированном Linux-окружении выполните небольшую C-программу: выделите и заполните буфер, вызовите
fork, затем измените разные страницы в parent и child. Снимитеsmaps_rollupобоих процессов.- Критерии готовности: есть исходный код, команды запуска, наблюдаемый рост private dirty/PSS и объяснение, почему он пропорционален затронутым страницам.
- Гипотеза о cold-start latency. Создайте тест, который читает большой file-backed mapping первым и повторным проходом; измерьте faults, I/O и p95/p99 времени обработки.
- Критерии готовности: cache state описан, измерения повторены, предложена безопасная мера — prefetch, лимит рабочего набора или изменение доступа — с trade-off.
Частые ошибки и ловушки
- Считать любой page fault ошибкой: demand paging и COW используют ожидаемые faults.
- Объявлять каждый TLB miss системным вызовом или major fault.
- Оценивать COW по числу байт, которые логически изменились: копируется гранулярность страницы, поэтому один байт может отделить целую страницу.
- Включать huge pages глобально без проверки compaction, reclaim и хвостовой задержки.
- Запускать
forkиз многопоточного процесса и выполнять в ребёнке произвольный код доexec. - Диагностировать OOM только по RSS: учитывать cgroup,
memory.events, swap, reclaim и страницу cache/anonymous memory.
Связанные темы
Модуль Foundations · Виртуальная память: stack, heap и mmap · Go: аллокации, GC и профили · Linux: процессы и OOM · Метрики и диагностика latency · System Design: capacity planning
Источники
- Linux kernel documentation: Memory Management Concepts и Transparent Hugepage Support.
- Linux manual pages:
fork(2)иproc_pid_stat(5). - Linux kernel documentation: Control Group v2.