Архитектуры ядер: монолитное, микроядро, гибридное, экзоядро, unikernel
Спор об архитектуре ядра выглядит как схоластика — пока вы не начинаете отлаживать kernel panic из-за драйвера сетевой карты, не выясняете, почему ваш прокси не тянет 10 Гбит/с, или не пытаетесь понять, зачем в автомобиле стоит QNX, а не Linux. Все эти вопросы упираются в одно решение: где провести границы защиты внутри ОС.
Эта статья — про то, как разные семейства ОС отвечают на этот вопрос, чем платят за свой ответ, и что из этого следует лично для вас, когда вы пишете прикладной код.
Единственный вопрос, который решает архитектура ядра
Начнём с первых принципов. Процессор умеет работать в нескольких режимах привилегий: на x86-64 их формально четыре («кольца», ring 0–3), реально используются два — ring 0 (ядро) и ring 3 (приложения); на ARM это EL0/EL1, на RISC-V — U-mode/S-mode. В ring 0 можно всё: править таблицы страниц, обращаться к портам ввода-вывода, маскировать прерывания. В ring 3 привилегированная инструкция вызывает исключение. Плюс к этому есть виртуальная память: процесс физически не может дотянуться до чужих страниц.
Итого у нас два инструмента изоляции — уровень привилегий и адресное пространство. И ровно один вопрос: какие части ОС положить внутрь защищённого периметра, а какие вынести наружу? Ответы порождают семейства архитектур:
- Монолит — всё в ring 0 и в одном адресном пространстве: быстро, но одна ошибка валит систему.
- Микроядро — в ring 0 минимум, ФС, сетевой стек и драйверы вынесены в процессы ring 3: надёжно, но каждое взаимодействие превращается в IPC.
- Гибрид — микроядерная структура, но всё в одном адресном пространстве ради скорости.
- Экзоядро — ядро не абстрагирует ресурсы, а только безопасно их разделяет; абстракции живут в библиотеках приложения.
- Unikernel — границы нет вообще: приложение и ОС слинкованы в один бинарь, изоляция вынесена на уровень гипервизора.
Сколько стоит граница: измеряем
Прежде чем спорить об архитектурах, надо понять цену вопроса. Напишем микробенчмарк, который сравнивает обычный вызов функции и системный вызов:
#define _GNU_SOURCE
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <sys/syscall.h>
static long noop(long x) { return x + 1; }
static struct timespec a, b;
static const long N = 3000000;
#define BENCH(label, body) do { \
volatile long s = 0; \
clock_gettime(CLOCK_MONOTONIC, &a); \
for (long i = 0; i < N; i++) { body; } \
clock_gettime(CLOCK_MONOTONIC, &b); \
printf("%-22s %6.1f нс\n", label, \
((b.tv_sec-a.tv_sec)*1e9 + (b.tv_nsec-a.tv_nsec)) / (double)N); \
} while (0)
int main(void) {
struct timespec t;
BENCH("вызов функции:", s += noop(i)); /* границы нет */
BENCH("syscall(SYS_getpid):", s += syscall(SYS_getpid)); /* граница есть */
BENCH("clock_gettime (vDSO):", clock_gettime(CLOCK_MONOTONIC, &t); s += t.tv_nsec);
return 0; /* формально syscall, фактически чтение памяти */
}
$ gcc -O2 -o bench bench.c && ./bench
вызов функции: 0.5 нс
syscall(SYS_getpid): 466.9 нс
clock_gettime (vDSO): 27.3 нс
Замер сделан внутри виртуальной машины — на голом железе getpid() обычно укладывается
в 50–90 нс. Но соотношение важнее абсолютных чисел, и оно устойчиво: вызов функции —
единицы наносекунд (часто инлайнится в ноль), пересечение границы ядра — на два-три
порядка дороже, обход границы через vDSO возвращает почти всю разницу обратно.
Что именно раздувает syscall на вашей машине, видно тут:
# смягчения спекулятивных уязвимостей — каждое добавляет такты к входу в ядро
$ grep -r . /sys/devices/system/cpu/vulnerabilities/ | sed 's|.*vulnerabilities/||'
spectre_v1:Mitigation: usercopy/swapgs barriers and __user pointer sanitization
spectre_v2:Mitigation: Retpolines; IBPB: conditional; IBRS_FW; RSB filling
meltdown:Not affected # будь тут "Mitigation: PTI" — это смена CR3 и частичный
# сброс TLB на каждом входе и выходе из ядра
Теперь ключевая мысль. В монолите read() пересекает границу один раз туда и один
обратно. В микроядре тот же запрос идёт приложение → ядро → сервер ФС → ядро →
драйвер диска → ядро → сервер ФС → ядро → приложение: границ уже не две, а восемь.
Вот и вся суть трёх десятилетий спора.
обычные вызовы функций внутри ring 0 K-->>A: sysret, данные уже в buf end rect rgba(168,85,247,0.10) Note over A,D: МИКРОЯДРО — 4 круга IPC A->>K: IPC send: "прочитай файл" K->>FS: доставка, переключение адр. пространства FS->>K: IPC send: "дай блок 4711" K->>D: доставка сообщения D->>K: IPC reply: блок получен по DMA K->>FS: доставка ответа FS->>K: IPC reply: данные K-->>A: доставка ответа end
Монолитное ядро
Идея. Ядро — одна большая программа, подсистемы вызывают друг друга напрямую как функции. Так устроены Linux, FreeBSD, NetBSD, OpenBSD, illumos и (с оговорками) AIX. Слово «монолитное» звучит как «немодульное», и это неверно: Linux аккуратно расслоён (VFS, block layer, netdev, driver model), у него есть внутренние API и загружаемые модули. Монолитность — не про отсутствие структуры, а про отсутствие аппаратной изоляции между слоями.
$ uname -r
6.8.0-124-generic
$ lsmod | head -3
Module Size Used by
unix_diag 12288 0
ib_core 507904 0
$ lsmod | wc -l # то же, что /proc/modules — только загружаемые модули
132
$ ls /sys/module | wc -l # + все вкомпилированные подсистемы с параметрами
258
$ cat /sys/module/kvm/parameters/nx_huge_pages # параметры доступны на лету
auto
$ sudo modprobe -v nbd max_part=8 # загрузка с параметром
insmod /lib/modules/6.8.0-124-generic/kernel/drivers/block/nbd.ko max_part=8
Загруженный модуль исполняется в том же адресном пространстве, что и всё ядро,
с полными привилегиями. Разыменование NULL в драйвере — это не segfault процесса,
это Oops, а часто и полная остановка системы.
или несовпадение vermagic Отказ --> [*] Проверка --> Связывание: разрешение символов по kallsyms Связывание --> Live: вызов module_init Live --> Live: работа в ring 0, полный доступ к памяти ядра Live --> Unloading: rmmod, если refcount равен нулю Unloading --> [*]: module_exit Live --> Panic: разыменование NULL в ring 0 Panic --> [*]: система остановлена
Состояние «загрязнения» ядра (cat /proc/sys/kernel/tainted, где 0 — чистое ядро) —
первое, что спросит kernel-разработчик при разборе паники: проприетарный модуль
в трассировке снимает с апстрима всю ответственность.
Различия внутри лагеря монолитов
Тут семейства ОС расходятся сильнее, чем принято думать.
| Система | Загружаемые модули | Команды | Особенность |
|---|---|---|---|
| Linux | да, повсеместно | lsmod, modprobe, rmmod, modinfo |
автозагрузка через udev/modalias |
| FreeBSD | да, KLD | kldstat, kldload, kldunload |
ядро GENERIC + loader.conf |
| NetBSD | да | modstat, modload |
плюс rump-ядра: подсистемы ядра как userspace-библиотеки |
| OpenBSD | нет, убрали в 5.7 | — | статическое ядро; KARL перелинковывает ядро при каждой загрузке |
| illumos | да | modinfo, modload |
DTrace как штатный инструмент интроспекции ядра |
$ kldstat # FreeBSD
Id Refs Address Size Name
1 14 0xffffffff80200000 1f5c1a8 kernel
2 1 0xffffffff8216d000 3a3e18 zfs.ko
Решение OpenBSD показательно: они сознательно отказались от гибкости ради того, чтобы
уменьшить поверхность атаки и сделать адреса функций ядра непредсказуемыми для
эксплойтов — ядро /bsd при каждой загрузке пересобирается из объектных файлов
в случайном порядке. Подробнее о философии семейства — в статье
https://courses.digitable.life/post/operating-systems/15-bsd-family/.
Trade-offs монолита. ✅ Внутренние вызовы бесплатны, поэтому легко делать сквозные оптимизации (zero-copy, общий на все подсистемы page cache); экосистема драйверов не имеет конкурентов. ❌ Баг в любом драйвере компрометирует всё ядро — а драйверы это ~60–70% кода Linux; обновление подсистемы требует перезагрузки (частично лечится live patching); формально верифицировать 30+ млн строк невозможно даже теоретически.
Микроядро
Идея. В ядре оставляем только то, что принципиально невозможно вынести наружу: управление адресными пространствами, планирование потоков и передачу сообщений (IPC). Всё остальное — ФС, сетевой стек, драйверы, даже подкачку — реализуем обычными процессами в ring 3. Выигрыш очевиден: упавший драйвер USB — это упавший процесс, а не мёртвая машина, и его можно перезапустить. В MINIX 3 этим занимается специальный reincarnation server:
Такое в монолите невозможно в принципе: упавший модуль оставляет после себя захваченные блокировки, испорченные структуры и утёкшую память ядра — восстанавливать нечего.
Почему микроядра «проиграли» и почему это не совсем так
Первое поколение (Mach, CMU, 1985–1994) действительно было медленным. Йохен Лидтке в работе «Improving IPC by Kernel Design» (SOSP ‘93) измерил на i486/50 МГц: кросс-адресный IPC в Mach — около 115 мкс, в его собственном L3 — около 5 мкс. Двадцатикратная разница. Вывод Лидтке был не «микроядра плохи», а «Mach плохо реализован»: IPC надо проектировать под конкретную архитектуру CPU, минимизируя промахи кэша и переключения TLB. Его следующая работа, «On µ-Kernel Construction» (SOSP ‘95), сформулировала принцип минимальности: в ядре должно быть только то, что при вынесении наружу сделало бы невозможной реализацию требуемой функциональности.
Второе поколение (семейство L4) довело IPC до сотен тактов. Кульминация — seL4: микроядро на ~10 тысяч строк C, для которого машинно проверено соответствие реализации формальной спецификации (Klein et al., SOSP 2009). Это означает буквально: доказано отсутствие переполнений буфера, разыменований NULL и утечек в ядре.
И микроядра никуда не «проигрывали» — просто они живут там, где вы их не видите:
- QNX Neutrino — автомобильные мультимедийные системы (сотни миллионов машин), медицинское оборудование, промышленные контроллеры;
- seL4 — авионика, военные и космические применения, где нужна сертификация;
- MINIX 3 — исполнялся внутри Intel Management Engine начиная с ME 11: на большинстве x86-машин мира крутится микроядро, о котором владелец не знает;
- L4/OKL4 — модемные процессоры миллиардов телефонов; Zircon — ядро Google Fuchsia.
Полноценных «настольных» микроядерных ОС общего назначения действительно почти нет: GNU Hurd, начатый в 1990 на базе Mach, за 35 лет так и не стал продакшн-системой — скорее по организационным причинам и из-за сложности отладки многосерверной архитектуры, чем из-за архитектурной несостоятельности.
Trade-offs микроядра. ✅ Отказ компонента локализован и лечится перезапуском; доверенная база (TCB) — десяток тысяч строк вместо десятков миллионов, что делает возможной формальную верификацию, обязательную для сертификации по DO-178C и IEC 61508. ❌ IPC на критическом пути, и даже быстрый IPC не бесплатен; сквозные оптимизации сложнее (нет общего page cache, zero-copy требует явных грантов на память); отладка ОС, размазанной по десятку процессов, тяжелее отладки одного бинаря; экосистема драйверов на порядки беднее.
Гибридные ядра: Windows NT и XNU
Это самая мифологизированная категория. Коротко: гибридное ядро — это микроядерная структура, размещённая внутри одного адресного пространства ring 0. Модульность на уровне дизайна есть, аппаратной изоляции между модулями нет.
Windows NT
Дэйв Катлер спроектировал NT под влиянием Mach и своей же VMS: есть Executive
(менеджеры объектов, процессов, памяти, I/O), есть микроядро KE, есть HAL,
есть «подсистемы окружения» (Win32, когда-то POSIX и OS/2) в userspace.
Ключевой поворот случился в NT 4.0 (1996): графическая подсистема и оконный
менеджер были перенесены из userspace-процесса CSRSS прямо в ядро — win32k.sys.
Причина чисто в производительности: рисование через IPC оказалось слишком медленным.
Плата пришла через двадцать лет — win32k.sys стал одним из главных источников
эскалации привилегий в Windows, отсюда Win32k Lockdown и Type Isolation в современных
версиях. Для драйверов Windows при этом двигается в обратную сторону: UMDF
(User-Mode Driver Framework) позволяет писать драйверы принтеров, USB-устройств
и камер как обычные userspace-компоненты — упавший UMDF-драйвер не роняет систему.
PS> driverquery /v # что загружено в режиме ядра
PS> sc query type= driver
XNU (macOS, iOS)
Название расшифровывается как «X is Not Unix». XNU состоит из трёх частей: Mach (на базе OSF/mk 7.3) даёт адресные пространства, порты, сообщения и планирование; BSD-слой (родословная от FreeBSD) — процессы, сигналы, VFS, сокеты, POSIX API; IOKit — объектную модель драйверов на подмножестве C++. Все три исполняются в одном адресном пространстве в ring 0. Mach-порты — реальный и активно используемый механизм IPC (на нём построены XPC, launchd, GCD), но микроядром XNU от этого не становится: APFS и сетевой стек живут внутри ядра.
$ kextstat | head -3 # расширения ядра (ring 0)
Index Refs Address Size Name (Version)
1 190 0 0 com.apple.kpi.bsd (24.5.0)
$ systemextensionsctl list # современная замена — userspace
$ sudo lsmp -p $(pgrep -n Safari) # Mach-порты конкретного процесса
Apple с macOS Catalina (10.15) объявила kext-ы устаревшими и продвигает DriverKit и System Extensions — драйверы в userspace, ровно микроядерная идея поверх гибридного ядра. Мотивация та же, что у Microsoft: сторонние драйверы в ring 0 — главный источник нестабильности. Подробнее об XNU и NT — в https://courses.digitable.life/post/operating-systems/16-other-operating-systems/.
Экзоядро: а что если не абстрагировать вообще?
В 1995 году группа из MIT (D. Engler, M. F. Kaashoek, J. O’Toole) опубликовала работу «Exokernel: An Operating System Architecture for Application-Level Resource Management» с радикальным тезисом: любая абстракция ОС кому-то вредит. Ядро даёт вам «файл», но СУБД хочет управлять раскладкой блоков на диске сама; даёт «сокет», но нагруженный сервер хочет свою реализацию TCP; даёт «страницу виртуальной памяти», но сборщик мусора знает о своей куче больше, чем алгоритм вытеснения ядра.
Экзоядро (Aegis, затем XOK) не предоставляет абстракций. Оно только безопасно мультиплексирует физические ресурсы: раздаёт конкретные блоки диска, конкретные фреймы физической памяти, конкретные интервалы процессорного времени — и следит, чтобы приложения не залезли на чужое. Абстракции живут в библиотечной ОС (libOS), слинкованной с приложением; хотите POSIX — линкуете libOS с POSIX, хотите свой TCP — пишете свой. Веб-сервер Cheetah на XOK со специализированным стеком обгонял обычные серверы в разы именно за счёт отказа от универсальных абстракций.
Экзоядер как продукта не существует, но идея победила полностью — просто под другими названиями, и все они есть в вашем Linux прямо сейчас:
$ ls /dev/vfio/ # VFIO: PCI-устройство напрямую процессу или виртуалке
vfio
$ ls /sys/class/uio/ # UIO: userspace-драйвер через отображение BAR-регионов
uio0
$ grep -i hugepages_total /proc/meminfo # приложение само управляет раскладкой памяти
HugePages_Total: 0
$ grep -c io_uring /proc/kallsyms # кольцевые буферы вместо syscall на операцию
187
Всё это — экзоядерные решения внутри монолита: DPDK и SPDK выносят сетевой и NVMe-стек в userspace, обходя ядро; io_uring убирает системный вызов с горячего пути; eBPF позволяет загрузить в ядро свою логику вместо встроенной, не рискуя стабильностью благодаря верификатору байткода. Подробнее — в https://courses.digitable.life/post/operating-systems/06-io-and-drivers/ и https://courses.digitable.life/post/operating-systems/14-observability-and-performance/.
Unikernel: убрать границу совсем
Финальный шаг в логике экзоядра: если абстракции всё равно в библиотеке — зачем
вообще нужны два уровня привилегий? Unikernel — это ваше приложение, слинкованное
с библиотечной ОС в один загружаемый образ, стартующий прямо на гипервизоре: один
процесс, ring 0, единое адресное пространство, write() вырождается в обычный вызов
функции. Изоляция обеспечивается снаружи гипервизором (KVM, Xen, Firecracker),
а не кольцами внутри. Ключевая работа — Madhavapeddy et al., «Unikernels: Library
Operating Systems for the Cloud» (ASPLOS 2013), представившая MirageOS на OCaml.
на этапе сборки"} B -->|нужен TCP/IP| C1["Линкуем библиотечный сетевой стек"] B -->|ФС не нужна| C2["Кода ФС в образе просто нет"] B -->|потоков нет| C3["Кооперативный планировщик или ничего"] C1 --> F["Компоновка единого образа"] C2 --> F C3 --> F F --> G["Образ 2–20 МБ, старт за 5–50 мс"] G --> H["Загрузка прямо на KVM / Xen / Firecracker"] H --> I["Поверхность атаки — только слинкованный код:
нет shell, нет ptrace, нет /proc, нет лишних syscall"]
| Проект | Язык | Особенность |
|---|---|---|
| MirageOS | OCaml | типобезопасность всего стека, включая TCP/IP и TLS |
| IncludeOS | C++ | ориентация на сетевые сервисы |
| OSv | C, запуск JVM/Go | совместимость с существующими Linux-бинарями |
| Unikraft | C, модульный | EuroSys 2021, лучшие показатели по производительности |
| rumprun | C | поверх rump-ядер NetBSD — настоящие драйверы NetBSD как библиотеки |
Отдельно стоит rump-ядро из NetBSD (Антти Кантее, «anykernel architecture»): драйверы, ФС и сетевой стек собираются как переносимые библиотеки, запускаемые в userspace, в unikernel или внутри другого ядра — редчайший пример монолита, спроектированного так, чтобы его части можно было вынимать.
Trade-offs unikernel. ✅ Старт за десятки миллисекунд и образ в мегабайтах —
идеально для serverless; радикально меньшая поверхность атаки; нулевые накладные
расходы на переход между приложением и «ОС». ❌ Отладка: нет strace, нет привычного
gdb, некуда сделать ssh; один процесс — нет fork(), часто нет вытеснения;
эксплойт в приложении сразу даёт ring 0 внутри виртуалки, то есть изоляция от соседей
есть, а внутренней нет; инструментарий несопоставим с Linux.
Практика 2020-х расставила всё по местам: там, где обещания unikernel были нужны (быстрый старт и малая поверхность атаки для serverless), победили микро-VM вроде Firecracker с обычным урезанным Linux внутри — совместимость оказалась важнее последних процентов эффективности. Подробнее — в https://courses.digitable.life/post/operating-systems/18-virtualization-and-containers/.
Эволюция: как менялись ответы
Обратите внимание на последнюю строку: современный тренд — не переносить границы, а делать код внутри границы безопасным другими средствами. Rust в ядре Linux и верификатор eBPF — попытки получить надёжность микроядра без его накладных расходов.
Типичные заблуждения
«Монолитное ядро — значит немодульное» и «Linux гибридный, ведь у него есть модули» — обе ошибки об одном. Linux максимально модулен на уровне исходников и умеет загружать модули на лету, но модуль попадает в то же адресное пространство ring 0 и вызывается напрямую. Монолитность — про изоляцию, а не про структуру кода; гибрид — про микроядерную декомпозицию с Mach-портами, реализованную без изоляции.
«Микроядра медленные». Это утверждение из 1990-х про Mach. Современный IPC в seL4 занимает сотни тактов. Замедление реально, но оно в разы, а не в десятки раз, и на многих задачах его вообще не видно за I/O.
«Микроядро надёжнее, значит выберем его для нашего сервиса». Надёжность микроядра — свойство изоляции драйверов, а не гарантия отсутствия ваших багов. Для типичного веб-сервиса разница нулевая, а вот отсутствие зрелых ФС и инструментов ударит сразу.
«Unikernel безопаснее, ведь поверхность атаки меньше». Меньше поверхность входа, но нет внутренней изоляции: RCE в вашем HTTP-парсере даёт полный контроль над всем образом, включая ключи TLS. Это компромисс, а не однозначный выигрыш.
«Архитектура ядра определяет производительность приложения». Почти всегда вы упираетесь в алгоритмы, базу и сеть, а не в стоимость syscall. Считать такты входа в ядро имеет смысл при миллионах операций в секунду — тогда приходят io_uring и kernel bypass.
Что из этого следует на практике
Если вы пишете обычный прикладной софт. Главный вывод: пересечение границы стоит
денег, поэтому батчируйте. Один writev() вместо десяти write(), sendmmsg()
вместо цикла sendmsg(), буферизованный ввод-вывод вместо посимвольного.
/* плохо: N пересечений границы */
for (int i = 0; i < n; i++)
write(fd, chunks[i].base, chunks[i].len);
/* хорошо: одно пересечение на весь набор */
struct iovec iov[IOV_MAX];
for (int i = 0; i < n; i++) {
iov[i].iov_base = chunks[i].base;
iov[i].iov_len = chunks[i].len;
}
writev(fd, iov, n);
$ strace -c -f ./myserver # сводка: сколько границ и каких
% time seconds usecs/call calls errors syscall
41.2 0.084112 2 42055 epoll_wait
28.7 0.058604 1 58210 write
# то же без замедления от ptrace, через eBPF
$ sudo bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'
Если вы выбираете ОС под задачу. Сертифицируемое встраиваемое устройство с жёстким реальным временем — QNX или seL4. Сетевое оборудование или гипервизор — Linux с DPDK или обходом ядра. Всё остальное — Linux, потому что экосистема побеждает архитектурную чистоту почти всегда.
Если вы пишете что-то, что живёт в ядре. Вы отказались от всех привычных гарантий:
нет malloc() в привычном виде, нет плавающей точки без специальных обёрток, стек
8–16 КБ, любая разыменованная ошибка — паника. Прежде чем писать модуль, проверьте,
не решается ли задача через eBPF, FUSE, uio/vfio или netlink. В девяти случаях
из десяти решается — и это ровно микроядерный подход внутри монолита.
# FUSE — драйвер файловой системы в userspace: падение не роняет ядро
$ sshfs user@host:/data /mnt/data && mount | grep fuse
user@host:/data on /mnt/data type fuse.sshfs (rw,nosuid,nodev,user_id=1000)
Мини-итог
| Критерий | Монолит | Микроядро | Гибрид | Экзоядро | Unikernel |
|---|---|---|---|---|---|
| Размер TCB | огромный | минимальный | огромный | малый | средний |
| Цена внутреннего вызова | вызов функции | IPC | вызов функции | вызов функции | вызов функции |
| Отказ драйвера | паника | перезапуск | паника | зависит | паника |
| Верифицируемость | нет | да | нет | частично | частично |
| Экосистема | максимальная | узкая | большая | нет | зачаточная |
| Примеры | Linux, BSD | seL4, QNX | NT, XNU | Aegis, XOK | MirageOS, Unikraft |
Ни одна архитектура не «правильная» — все они разные точки на кривой размена между производительностью, надёжностью и удобством разработки, и реальные системы давно перестали быть чистыми: Linux заимствует микроядерные идеи (FUSE, eBPF, vfio), Windows и macOS выносят драйверы в userspace, unikernel растворился в микро-VM. Знать исходные принципы нужно не для того, чтобы выбрать лагерь, а чтобы читать эти заимствования и понимать, чем именно за них платят.
Источники
Первоисточники:
- J. Liedtke. Improving IPC by Kernel Design. SOSP ‘93 — https://dl.acm.org/doi/10.1145/168619.168633
- J. Liedtke. On µ-Kernel Construction. SOSP ‘95 — https://dl.acm.org/doi/10.1145/224056.224075
- D. Engler, M. F. Kaashoek, J. O’Toole. Exokernel: An OS Architecture for Application-Level Resource Management. SOSP ‘95 — https://pdos.csail.mit.edu/6.828/2008/readings/engler95exokernel.pdf
- G. Klein et al. seL4: Formal Verification of an OS Kernel. SOSP ‘09 — https://sel4.systems/Research/pubs.pml
- A. Madhavapeddy et al. Unikernels: Library Operating Systems for the Cloud. ASPLOS ‘13 — https://anil.recoil.org/papers/2013-asplos-mirage.pdf
- A. Kantee. Flexible Operating System Internals (rump-ядра) — https://www.fixup.fi/misc/rumpkernel-book/
- A. Baumann et al. The Multikernel. SOSP ‘09 (Barrelfish) — https://barrelfish.org/publications/
Книги: A. Tanenbaum, H. Bos, Modern Operating Systems (5-е изд., включая разбор дебатов с Торвальдсом) · R. Love, Linux Kernel Development · M. K. McKusick et al., The Design and Implementation of the FreeBSD Operating System · P. Yosifovich, M. Russinovich et al., Windows Internals (7-е изд.) · Arpaci-Dusseau, Operating Systems: Three Easy Pieces — бесплатно: https://pages.cs.wisc.edu/~remzi/OSTEP/
Документация: man 8 modprobe, man 2 syscall, man 7 vdso, man 8 kldstat ·
https://www.kernel.org/doc/html/latest/ · https://sel4.systems/Info/Docs/seL4-manual-latest.pdf ·
https://unikraft.org/docs/ · https://mirage.io/docs ·
https://developer.apple.com/documentation/driverkit
Что дальше
Мы разобрались, где проходят границы ядра и во что они обходятся. Дальше заглянем внутрь — к самой заметной для программиста абстракции: как ОС создаёт процессы и потоки, как переключает их и по какому принципу решает, кому отдать процессор.