Linux

Предпосылки: аппаратное обеспечение (CPU, кеш, RAM, хранилище, DMA).

Одна программа на одном процессоре — простая задача: загрузить код в память и выполнять инструкции. Десятки программ на нескольких ядрах — задача другого порядка: кто-то должен изолировать их друг от друга, распределить процессорное время и память, предоставить единый интерфейс к сотням устройств. Операционная система (ОС) превращает набор транзисторов в платформу для программ. Ядро Linux изолирует процессы друг от друга, абстрагирует оборудование через файловые дескрипторы и системные вызовы, распределяет CPU и память между сотнями задач. Каждый механизм — ответ на конкретную проблему: виртуальная память защищает процессы от чужих данных, планировщик обеспечивает отзывчивость при фоновой нагрузке, механизмы взаимного исключения работают быстро без перехода в ядро при отсутствии конкуренции, сервер масштабируется на десятки тысяч соединений, отслеживая готовность дескрипторов вместо опроса каждого по очереди.

Порядок изучения

Серия организована как ядро курса и несколько углублений. Ядро — линейная последовательность, каждая заметка опирается на предыдущие. Углубления тоже выстроены в рекомендуемом порядке: конкурентность, системное программирование, ядро, инфраструктура, контейнеры. Каждый модуль опирается на разные части ядра курса, но последовательное прохождение позволяет накапливать контекст — например, системное программирование использует знания о синхронизации из конкурентности, а модуль ядра опирается на API из системного программирования.

Ядро курса

Фундаментальные абстракции ОС от первого системного вызова до прав доступа. Читать по порядку — каждый файл использует понятия из предыдущих.

Углубления

Три модуля. Рекомендуемый порядок: конкурентность, затем системное программирование, затем ядро. Конкурентность и системное программирование зависят от ядра курса, а модуль ядра дополнительно опирается на системное программирование.

Конкурентность

Предпосылки: потоки, виртуальная память.

Синхронизация потоков и модель памяти.

  • Синхронизация — что ломается при одновременном доступе и какие примитивы это предотвращают
  • Модель памяти — почему компилятор и процессор переупорядочивают операции с памятью и как это контролировать
  • Lock-free структуры — параллельные структуры без блокировок: выигрыш, скрытые ловушки и когда они не нужны

Системное программирование

Предпосылки: ядро курса.

Практический API: сигналы, mmap, файловый ввод-вывод, сокеты, мультиплексирование.

  • Сигналы — механизм уведомления процесса о событиях и почему обработка сигналов сложнее, чем кажется
  • Отображение памяти — как отобразить файл или анонимную область в адресное пространство процесса
  • Файловый ввод-вывод — гарантии записи, обход кеша и блокировка файлов
  • Сокеты — создание соединений, локальные сокеты и передача данных без копирования
  • Мультиплексирование ввода-вывода — как обрабатывать тысячи соединений одним потоком и почему каждое поколение API вытесняет предыдущее
  • Управление памятью — управление физической памятью: большие страницы, переподписка и неоднородность
  • Межпроцессное взаимодействие — способы обмена данными между процессами: от семафоров до передачи файловых дескрипторов

Ядро

Предпосылки: ядро курса, системное программирование.

Внутреннее устройство: как работают syscall, прерывания, драйверы, сетевой стек.

  • Механизм системных вызовов — что происходит внутри ядра от инструкции syscall до возврата в пользовательский код
  • Прерывания — как ядро обрабатывает аппаратные события, не блокируя остальную работу
  • Устройства и драйверы — как ядро находит оборудование, загружает драйверы и предоставляет интерфейс через виртуальные файловые системы
  • Сетевой стек — путь сетевого пакета от сетевой карты до приложения
  • Управление памятью ядра — как ядро выделяет, возвращает и перераспределяет физическую память

Инфраструктура

Четыре заметки в рекомендуемом порядке. Каждая самодостаточна после ядра курса, но последовательное чтение даёт дополнительные связи: ELF-формат объясняет, откуда берутся адреса в виртуальной памяти; терминалы используют знания о дескрипторах и сессиях; трассировка опирается на системные вызовы и прерывания; загрузка системы завершает картину.

  • ELF и линковка — как исполняемый файл попадает в память и находит библиотеки
  • Терминалы — как терминал связывает клавиатуру с процессом и почему это сложнее, чем кажется
  • Трассировка — как наблюдать за системой: от трассировки вызовов до профилирования
  • Загрузка системы — цепочка загрузки: от нажатия кнопки питания до первого пользовательского процесса

Контейнеры

Синтез серии: namespaces, cgroups, overlay FS, seccomp, capabilities складываются в один docker run.

Предпосылки: ядро курса, системное программирование, ядро.

Как всё связано

Изоляция vs производительность: создание процесса (fork) изолирует его полностью (~50 мкс), создание потока (clone) дешевле (~10 мкс), но потоки разделяют память и требуют синхронизации. Выбор определяет архитектуру: Redis — однопоточный с мультиплексированием ввода-вывода, PostgreSQL — процесс на соединение, Go — горутины (кооперативные потоки Go) поверх пула потоков.

Безопасность vs скорость: переход из пользовательского режима в ядро стоит 100–300 нс без защит от Spectre/Meltdown (аппаратные уязвимости спекулятивного исполнения) и 200–700 нс с KPTI (Kernel Page Table Isolation) и другими митигациями; конкретное значение зависит от поколения CPU и включённых защит. Механизмы, позволяющие обойтись без перехода в ядро (vDSO — виртуальный разделяемый объект), сокращают латентность на порядок. Общая память между ядром и приложением убирает системный вызов на критическом пути выполнения. Каждый шаг — компромисс между защитой и латентностью.

Надёжность (durability) vs латентность: запись в страничный кеш мгновенная, fsync() гарантирует запись на диск но стоит ~50 мкс (SSD) — ~10 мс (HDD). Базы данных балансируют между потерей данных при сбое и пропускной способностью (throughput).

Справедливость vs отзывчивость: планировщик балансирует через виртуальное время (механизм планировщика) — интерактивные потоки с низким виртуальным временем мгновенно получают CPU. Потоки реального времени нарушают справедливость ради гарантий латентности.

Атомарность vs пропускная способность: мьютексы (механизмы взаимного исключения) сериализуют доступ, структуры без блокировок дают параллелизм за счёт сложности (проблема повторного использования адресов, безопасное освобождение памяти). На практике мьютекс + правильная структура данных побеждает lock-free в большинстве случаев.

Абстракция vs контроль: мониторинг готовности дескрипторов абстрагирует ожидание, пакетная отправка запросов ядру (io_uring) даёт пропускную способность ценой сложности API. Обход страничного кеша (direct I/O) даёт контроль ценой потери упреждающего чтения.

См. также

Sources