Страничная адресация: 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-последовательности:

  1. Родитель имеет anonymous page с данными и вызывает fork.
  2. Ребёнок получает mapping на тот же physical frame; обе PTE запрещают запись и указывают на COW-семантику.
  3. Ребёнок пишет байт: protection fault попадает в ядро.
  4. Ядро выделяет новый frame, копирует исходную страницу, меняет PTE ребёнка на writable и возобновляет инструкцию.
  5. Родитель продолжает видеть старую страницу; RSS и private dirty memory могут вырасти.

Типовые вопросы

  1. TLB miss и page fault — одно и то же?
    • Нет. При TLB miss процессор может найти валидный PTE page-table walk без входа в ядро. Page fault — архитектурное исключение, требующее обработки ядром или сигнала.
  2. Почему первая запись в большой make([]byte, n) может быть дороже выделения?
    • Runtime/allocator может зарезервировать адресное пространство, а demand paging создаёт zero pages по мере касания. Цена зависит от числа затронутых страниц и состояния памяти.
  3. Почему fork обычно быстрый, но иногда приводит к OOM?
    • Он делит страницы через COW. Если родитель и ребёнок интенсивно пишут в общий первоначально набор, копии съедают RAM; cgroup limit может сработать раньше, чем общий лимит хоста.
  4. Всегда ли major page fault означает чтение с диска?
    • Обычно он связан с необходимостью получить данные из backing store, но трактовка зависит от ядра и backing device. Смотрите одновременно latency, I/O, reclaim и trace, а не один счётчик.
  5. Как увидеть faults процесса в Linux?
    • Использовать ps -o min_flt,maj_flt, /proc/<pid>/stat, perf stat -p <pid> либо системные метрики. Снимать дельты за интервал под известной нагрузкой.
  6. Почему 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.