Операционные системы

Linux Internals

Каждую секунду Linux принимает миллионы решений: какому процессу дать CPU, откуда выделить память, как распределить I/O между дисками. CFS балансирует нагрузку между 1000 потоков, VFS кэширует петабайты данных, page allocator борется с фрагментацией. Это не абстракция - это живой организм, который работает на 96% серверов мира. Понимание Linux internals - разница между "знаю как пользоваться" и "знаю как оно работает внутри".

  • **Google переписала TCP стек для 100Gbps дата-центров.** Стандартный Linux TCP не справлялся с микросекундными задержками. Команда Google патчила kernel, оптимизируя sk_buff allocation, TCP congestion control (BBR), NIC offloading. Результат: 10× throughput. Без понимания network stack в ядре это невозможно.
  • **Kubernetes управляет миллионами контейнеров.** Каждый pod - набор Linux namespaces (PID, NET, MNT) + cgroups для лимитов CPU/memory. Scheduler Kubernetes решает, на какой ноде запустить pod, но Linux CFS решает, сколько CPU дать каждому процессу внутри. Два уровня планирования, оба критичны.
  • **Database performance tuning - 90% про kernel.** PostgreSQL тюнят через vm.swappiness, vm.dirty_ratio, transparent huge pages. MongoDB требует отключить NUMA balancing. Все эти параметры - настройки memory management в ядре. Понимание zones, page cache, slab allocator - обязательно для DBA.

Цели урока

  • Архитектура Linux: monolithic kernel, loadable modules (insmod/modprobe)
  • CFS: vruntime, red-black tree, nice values, cgroup-aware планирование
  • Memory zones: DMA, DMA32, Normal, HighMem; buddy allocator
  • VFS: единый API над ext4, xfs, btrfs, fuse через struct file_operations
  • Применять /proc и /sys для introspection ядра

Архитектура ядра Linux

**Linux kernel** - монолитное ядро с модульной архитектурой. 30+ миллионов строк кода C, поддержка ~30 архитектур процессоров (x86, ARM, RISC-V, PowerPC). Ключевое отличие от других UNIX-систем: **всё в kernel space**, но с возможностью динамической загрузки модулей.

Каждая подсистема - отдельный слой с чётким API. **VFS** абстрагирует файловые системы, **процессный менеджер** управляет task_struct, **memory manager** оперирует page frames. Компоненты связаны через структуры данных: `struct task_struct`, `struct mm_struct`, `struct inode`.

**Ключевые структуры ядра:** - `task_struct` - дескриптор процесса (~1KB). Всё о процессе: PID, состояние, приоритет, открытые файлы, виртуальная память - `mm_struct` - дескриптор адресного пространства процесса. Page tables, VMA (virtual memory areas) - `file` - открытый файл. Указатель на inode, текущая позиция, флаги (O_RDONLY, O_APPEND) - `dentry` - directory entry в кэше. Связывает имя файла с inode для быстрого поиска

**Модули ядра (Loadable Kernel Modules, LKM)** - динамически подгружаемый код. Драйверы можно загрузить/выгрузить на лету без перезагрузки. Каждый модуль имеет `init` и `exit` функции, экспортирует символы через `EXPORT_SYMBOL()`.

Загрузка модуля Nvidia в runtime

Сценарий: система работает, но видеокарта Nvidia не распознана. Достаточно загрузить модуль `nvidia.ko` - и GPU мгновенно доступен. Никакой перезагрузки! Модуль регистрирует себя в PCI bus, создаёт `/dev/nvidia0`, инициализирует GPU. Всё это в kernel space, но без пересборки ядра.

**Kernel Space vs User Space:** разделение по уровням привилегий CPU (x86: Ring 0 для kernel, Ring 3 для user). Переход через системные вызовы (syscall) - дорогая операция: сохранение контекста, переключение page tables, проверка прав. Современные ядра оптимизируют через vDSO (virtual dynamic shared object) - некоторые syscalls выполняются без перехода в kernel.

Почему в Linux kernel используется `task_struct` размером ~1KB на процесс, хотя можно было бы сделать минимальную структуру в 100 байт?

Completely Fair Scheduler (CFS)

**CFS (Completely Fair Scheduler)** - планировщик процессов в Linux с 2007 года (kernel 2.6.23). Основная идея: каждый процесс должен получить **справедливую долю CPU** пропорционально приоритету. Использует красно-чёрное дерево для O(log N) операций вставки/удаления.

Вместо традиционных time slices (каждому процессу по 10ms CPU), CFS оперирует **виртуальным временем (vruntime)**. Процесс с наименьшим vruntime - самый "голодный", он получает CPU. Когда процесс выполняется, его vruntime растёт. Когда vruntime становится больше других - процесс вытесняется.

**CFS параметры:** - `sched_latency` (обычно 6ms) - период, за который все процессы должны получить CPU хотя бы раз - `min_granularity` (обычно 0.75ms) - минимальное время выполнения процесса перед вытеснением - `vruntime` - виртуальное время, накопленное процессом - `weight` - вес процесса, зависит от nice (-20..19). Вес определяет скорость роста vruntime

CFS в действии: 2 процесса, разные nice

**Процесс A (nice=0, нормальный приоритет)** и **Процесс B (nice=10, низкий приоритет)** конкурируют за CPU. Процесс A получает CPU на 1ms реального времени → vruntime += 1ms Процесс B получает CPU на 1ms реального времени → vruntime += ~1.25ms (штраф за низкий приоритет) Результат: через 10ms процесс A выполнился ~6ms, процесс B ~4ms. **Справедливость** достигнута: пропорция 60/40 соответствует разнице в nice.

**Преимущества CFS:** автоматическая адаптация к нагрузке. Если 100 процессов, каждый получает ~1% CPU. Если 2 процесса - каждый ~50%. Нет фиксированных time slices, нет приоритетных очередей (как в старом O(1) scheduler). Простота и предсказуемость.

**Проблемы CFS:** не идеален для real-time задач. Для RT используют `SCHED_FIFO` или `SCHED_RR` классы (не CFS). CFS оптимизирован для throughput и fairness, но не для low latency. Например, audio processing требует гарантированного времени отклика - там нужен RT scheduler.

Почему CFS использует красно-чёрное дерево вместо простого массива процессов?

Memory Zones и Page Allocator

**Memory zones** - разделение физической памяти на области с разными характеристиками. На x86-64: ZONE_DMA (0-16MB), ZONE_DMA32 (16MB-4GB), ZONE_NORMAL (>4GB). Разделение нужно из-за ограничений старого железа: некоторые устройства могут адресовать только первые 16MB.

**Page allocator (buddy system)** - аллокатор физических страниц в ядре. Оперирует блоками степени двойки: 1 страница (4KB), 2 страницы (8KB), 4 страницы (16KB), ..., до 1024 страниц (4MB). При запросе 12KB выделяет 16KB (4 страницы). При освобождении пытается слить соседние блоки - борьба с фрагментацией.

**Buddy System - как работает:** 1. Память разбита на блоки степени 2: [1, 2, 4, 8, ..., 1024 pages] 2. Запрос 3 страниц → округляется до 4. Ищем свободный блок 4-страниц 3. Если блока 4 нет, берём блок 8, разбиваем на два блока по 4 4. Один блок 4 выдаём, второй добавляем в free list 5. При освобождении проверяем: свободен ли "buddy" (соседний блок)? Если да - сливаем в блок 8

Фрагментация памяти: почему нельзя выделить 4MB?

Система проработала неделю. RAM 16GB, свободно 8GB, но запрос на 4MB непрерывной физической памяти **FAIL**. Почему? Фрагментация! Свободные страницы разбросаны: тут 100KB, там 200KB, нигде нет непрерывного блока 4MB. Buddy allocator пытается найти 1024 подряд идущих страниц - не может. Решение: **memory compaction** (kcompactd) - ядро переносит используемые страницы, освобождая непрерывные блоки. Медленно, но работает.

**Slab allocator** - надстройка над page allocator для объектов фиксированного размера. Выделяет страницы, разбивает на объекты (например, task_struct), кэширует их. Быстрая аллокация без накладных расходов buddy system. Три реализации: SLAB (классический), SLUB (modern default), SLOB (embedded).

**NUMA (Non-Uniform Memory Access):** в многопроцессорных системах память разделена по узлам. CPU0 имеет быстрый доступ к памяти Node0, медленный - к Node1. Ядро старается выделять память из локальной ноды, минимизируя cross-node traffic.

Почему при 8GB свободной RAM запрос kmalloc(4*1024*1024) может вернуть NULL?

Virtual File System (VFS)

**VFS (Virtual File System)** - слой абстракции над файловыми системами. Позволяет работать с ext4, XFS, Btrfs, NFS, FAT32 через единый API. Программа вызывает `open()` - VFS определяет, какая FS отвечает за файл, вызывает соответствующий обработчик. Пользователь не знает, на каком диске файл: SSD, HDD, сетевая шара.

Ключевые объекты VFS: 1. **inode** - метаданные файла (размер, права, timestamps, указатели на блоки данных). Идентифицируется inode number 2. **dentry (directory entry)** - связывает имя файла с inode. Кэшируется для быстрого path lookup 3. **file** - открытый файл. Содержит текущую позицию, флаги (O_RDONLY), указатель на inode 4. **superblock** - корневая структура FS (block size, total inodes, magic number)

**Dentry Cache - критичная оптимизация:** Когда открываешь `/home/user/docs/report.txt`, ядро должно: 1. Найти inode для `/` 2. Прочитать директорию `/`, найти `home` 3. Прочитать `/home`, найти `user` 4. Прочитать `/home/user`, найти `docs` 5. Прочитать `/home/user/docs`, найти `report.txt` Каждый шаг - disk I/O! Dentry cache сохраняет результаты: второе открытие того же файла - мгновенно, из памяти.

Как работает read() через VFS

1. Userspace: `read(fd, buf, 1024)` 2. Syscall переход в ядро, находим `struct file` по fd 3. VFS вызывает `file->f_op->read()` 4. Для ext4 это `ext4_file_read_iter()` 5. ext4 проверяет page cache - данные уже в памяти? 6. Если нет - читает блоки с диска через block layer 7. Копирует данные в userspace buffer 8. Возврат в userspace Программа не знает, что файл на ext4. Завтра переложишь на XFS - код работает без изменений.

**Page Cache** - кэш данных файлов в RAM. Когда читаешь файл, блоки попадают в page cache. Повторное чтение - мгновенно, из памяти. Запись тоже буферизуется: `write()` возвращается сразу, данные синхронизируются на диск позже (writeback). Управляется через `struct address_space`.

**VFS и производительность:** современные FS агрессивно оптимизируют. XFS использует delayed allocation (выделяет блоки только при flush). Btrfs поддерживает copy-on-write snapshots. Но все они работают через VFS API: `read()`, `write()`, `mmap()`. Универсальность VFS позволяет монтировать ext4, XFS, NTFS одновременно - и всё работает.

VFS - это просто обёртка, которая вызывает функции конкретной файловой системы. Никакой магии

VFS - сложная подсистема с dentry cache, inode cache, page cache, управлением блокировками, coherency между процессами

VFS не только маршрутизирует вызовы. Он: 1. **Кэширует path lookup** - dentry cache избавляет от повторных disk reads при открытии `/home/user/file` 2. **Управляет page cache** - буферизует данные файлов, синхронизирует write-back 3. **Синхронизирует доступ** - блокирует одновременную запись в один inode 4. **Поддерживает coherency** - если процесс A пишет, процесс B читает свежие данные (через shared page cache) 5. **Абстрагирует устройства** - `/dev/sda1`, `/proc/cpuinfo`, `/sys/class/net` выглядят как файлы, но это не диск Всё это делает VFS. Без него каждая программа писала бы свой код для ext4, XFS, NFS - кошмар.

Ключевые идеи

  • **Linux kernel - монолитное ядро с модульной архитектурой.** 30M строк кода, но можно загрузить/выгрузить драйвер без перезагрузки. task_struct - дескриптор процесса (~1KB), содержит всё: PID, приоритет, память, файлы. Kernel space vs user space разделение через CPU privilege levels.
  • **CFS (Completely Fair Scheduler) - fair share CPU через vruntime.** Процессы с меньшим vruntime получают CPU. Red-black tree для O(log N) операций. nice влияет на скорость роста vruntime. Отлично для throughput, но не для real-time (там SCHED_FIFO/RR).
  • **Memory zones - разделение RAM по возможностям железа.** ZONE_DMA (0-16MB), ZONE_DMA32 (16MB-4GB), ZONE_NORMAL (>4GB). Buddy allocator выделяет физически непрерывные блоки степени 2. Slab allocator - для объектов фиксированного размера. Фрагментация - враг больших аллокаций.
  • **VFS - универсальный интерфейс к файловым системам.** Dentry cache для path lookup, inode cache для метаданных, page cache для данных. Программа вызывает read() - VFS маршрутизирует в ext4/XFS/Btrfs. Файл удалён, но место не освободилось? Процесс держит fd.

Связанные темы

Linux internals - основа для системного программирования и performance engineering:

  • Scheduling Algorithms — CFS - эволюция от O(1) scheduler. Понимание vruntime помогает оптимизировать latency-sensitive приложения (audio, video)
  • Memory Management — Page allocator, slab, vmalloc - инструменты для kernel programming. User space malloc() использует brk/mmap syscalls
  • File Systems — VFS абстрагирует ext4, XFS, Btrfs. Понимание inode, dentry cache критично для I/O-intensive приложений (databases)
  • System Calls — Системные вызовы - мост между user space и kernel. read() → VFS, fork() → scheduler, mmap() → memory management

Вопросы для размышления

  • Scheduler для real-time audio processing: что изменить в CFS? Какие гарантии нужны, которых CFS не даёт?
  • Сервер с 1TB RAM и 10000 процессов: какие проблемы возникнут с buddy allocator? Как memory compaction поможет?
  • Почему `echo 3 > /proc/sys/vm/drop_caches` убивает производительность? Какие workloads пострадают больше всего, какие - меньше?

Связанные уроки

  • arch-01-binary
Linux Internals

0

1

Войти

Почему после удаления 100GB файла `df -h` показывает, что место на диске не освободилось?