Перейти до вмісту

Куди рухаються ОС

Усе попереднє в курсі усталилося десятиліття тому. Тут ітиметься про те, що змінюється просто зараз, і про те, які припущення класичної теорії перестають бути вірними.

Мотив у більшості пунктів спільний: межа між ядром і простором користувача, така чітка в модулі 3, поступово розмивається. Код користувача потрапляє всередину ядра, ядро віддає роботу в спільну пам’ять, а частину гарантій забирає собі апаратура.

eBPF: ядро стало програмованим

Section titled “eBPF: ядро стало програмованим”

Раніше змінити поведінку ядра можна було двома способами: написати модуль, який має право повалити систему, або чекати наступного релізу.

eBPF дозволяє завантажити в ядро маленьку програму й приєднати її до події — системного виклику, функції ядра, мережевого пакета, планувального рішення. Перед завантаженням програму перевіряє верифікатор: доводить, що вона завершиться, не звернеться за межі пам’яті й не зациклиться. Не довів — не завантажив.

Ось у цьому й полягає ключова відмінність від модуля ядра. eBPF-програма не може повалити ядро за побудовою, а не за домовленістю.

Застосування Що робить
Трасування bpftrace, профілювання ядра без перезбирання і без зупинки
Мережа XDP обробляє пакет ще до мережевого стека — фільтрація й балансування на мільйонах пакетів за секунду
Безпека політики на рівні виклику з повним контекстом, чого seccomp не вміє
Планування sched_ext: планувальник CPU як eBPF-програма

Останній рядок таблиці варто перечитати уважно. sched_ext, який з’явився в ядрі 6.12, дозволяє замінити планувальник — той самий, що описаний у модулі 7, — завантажуваною програмою, а якщо вона поводиться погано, перемкнутися назад. Експеримент, який раніше вимагав перезбирання ядра й перезавантаження, тепер триває секунди.

Кожен системний виклик коштує сотні наносекунд (модуль 3), і за мільйона операцій на секунду ця вартість стає основною статтею витрат.

io_uring прибирає виклик із гарячого шляху. Програма й ядро діляться двома кільцевими чергами в спільній пам’яті: заявки кладуться в одну, результати забираються з іншої, а в режимі опитування межу можна взагалі не перетинати.

За цим стоїть ширший принцип, який видно і тут, і в XDP: не робити перехід швидшим, а прибрати його.

Більшість критичних уразливостей у ядрі Linux є помилками роботи з пам’яттю: звернення після звільнення, вихід за межі, гонитви даних. C їх не ловить.

Від ядра 6.1 Rust є офіційно підтримуваною мовою для драйверів. Перевірки часу компіляції прибирають цілий клас помилок і нічого не коштують під час виконання. Написане ядро ніхто переписувати не збирається, зате нові драйвери можуть бути безпечними за побудовою.

Зміна ця йде повільно й супроводжується суперечками, причому причина не технічна. Змішувати дві мови в проєкті на тридцять мільйонів рядків із двома тисячами розробників дорого суто організаційно.

Коли в машині працює один застосунок, а в хмарі так буває часто, поділ на режими не захищає ні від чого. Уніядро збирає застосунок разом із потрібними частинами ядра в один образ, який працює в одному адресному просторі.

Системних викликів немає, перемикань контексту теж, образ вимірюється мегабайтами, старт мілісекундами. Ізоляцію бере на себе гіпервізор (модуль 17).

Платити доводиться всім іншим: налагодженням, звичними інструментами, можливістю підключитися по SSH і подивитися, що там відбувається. Тому ніша й лишається вузькою.

Реальний час і вбудовані системи

Section titled “Реальний час і вбудовані системи”

Пристроїв зовсім без ОС стає дедалі менше: навіть дешевий мікроконтролер сьогодні отримує Zephyr чи FreeRTOS, тобто планувальник, драйвери й мережевий стек у кілька десятків кілобайтів.

Паралельно й сам Linux став придатнішим для реального часу. PREEMPT_RT, який десятиліттями жив набором латок, увійшов в основне ядро у версії 6.12. Він робить витісненими майже всі ділянки ядра й зменшує найгіршу затримку до передбачуваних одиниць мікросекунд, щоправда ціною невеликої втрати пропускної здатності.

Конфіденційні обчислення

Section titled “Конфіденційні обчислення”

Класична модель захисту (модуль 16) виходить із того, що ядру й гіпервізору можна довіряти. У хмарі це припущення виглядає сумнівно, бо ваші дані обробляються на чужій машині під чужим гіпервізором.

Конфіденційні обчислення прибирають господаря з кола довіри. AMD SEV-SNP та Intel TDX шифрують пам’ять віртуальної машини ключем, недоступним гіпервізору, і дають змогу зробити атестацію — криптографічний доказ, що запущено саме той образ, який ви очікували.

Межа довіри так переїжджає з програмного забезпечення в апаратуру. Обмеження при цьому визнані чесно: побічні канали за часом (модуль 16) нікуди не зникають, а довіряти доводиться виробникові процесора.

Прискорювачі зламали одразу кілька припущень класичної ОС.

Почнімо з планування. Планувальник операційної системи керує процесорним часом (модуль 7), а GPU має власну чергу, до якої ядро має мало стосунку. Хто вирішує, чий шматок обчислень піде першим, і що робити з чесністю — питання, на яке усталеної відповіді поки немає.

Далі пам’ять. У прискорювача вона є окремим адресним простором. Уніфікована пам’ять і HMM намагаються це приховати так само, як віртуальна пам’ять приховує диск (модуль 12), з тими самими наслідками: те, що виглядає як звернення до пам’яті, цілком може означати передавання по шині.

І нарешті масштаб. Модель, яка не вміщається в одну машину, робить одиницею планування кластер, а не процес. На цьому рівні класична теорія ОС відповідей не має взагалі.

Варто розділяти те, що змінюється, і те, що ні.

Не змінюються процес як одиниця ізоляції, віртуальна пам’ять, файлові абстракції, необхідність синхронізації й компроміс між пропускною здатністю та часом відгуку. Це наслідки фізики й математики, а не моди.

Змінюються натомість інші речі: де саме проходить межа ядра, скільки вона коштує, кому доводиться довіряти й що вважати одиницею планування.

Тому курс і побудований навколо механізмів, а не інтерфейсів. Інтерфейси застаріють, а питання «хто чекає, хто платить і чому саме так» лишиться.

Перевір себе

1. Чим eBPF-програма принципово відрізняється від модуля ядра?
2. Що дозволяє sched_ext?
3. Який спільний принцип обʼєднує io_uring і XDP?
4. Навіщо в ядро Linux додали підтримку Rust?
5. Що прибирають з кола довіри конфіденційні обчислення?
6. Чим жорсткий реальний час відрізняється від мʼякого?

Курс закінчився, а тема ні. Природних напрямків три:

  • Углиб ядра. Зібрати власне ядро з PREEMPT_RT, написати простий модуль, розібрати якусь підсистему за документацією й вихідним кодом.
  • Убік розподілених систем. Туди, де класична ОС закінчується: консенсус, реплікація, відмовостійкість поза межами однієї машини.
  • Убік продуктивності. perf, bpftrace, аналіз затримок, тобто застосувати механізми з цього курсу до реальних систем.