Блог "Дети ОИТа"
/поиск

Операционная система (ОС)

Операционная система (ОС) представляет собой сложный программный комплекс, управляющий ресурсами компьютера и предоставляющий прикладным программам унифицированный интерфейс. По мере роста функциональ

Операционная система (ОС) представляет собой сложный программный комплекс, управляющий ресурсами компьютера и предоставляющий прикладным программам унифицированный интерфейс. По мере роста функциональности и объёма кода (современные ядра содержат миллионы строк) возникла необходимость в системном подходе к организации компонентов. Структурирование ОС — это способ разбиения системы на части (модули, уровни, серверы) с чётко определёнными интерфейсами и правилами взаимодействия.

Основные принципы структурирования:

  • Модульность — разделение на логически завершённые компоненты с минимизацией связей.
  • Иерархическая организация (уровни абстракции) — каждый нижележащий уровень предоставляет услуги вышележащему, скрывая детали реализации.
  • Скрытие информации (инкапсуляция) — компонент предоставляет интерфейс, но не раскрывает внутренние структуры.
  • Виртуализация ресурсов — преобразование физических ресурсов в удобные логические абстракции (процессы, виртуальная память, файлы).

В этой статье мы рассмотрим классические и современные архитектуры ОС, их организацию, достоинства и недостатки, а также проиллюстрируем ключевые принципы диаграммами и примерами.

Английские сокращения

Сокращение Английское обозначение Русское обозначение
OS Operating System операционная система
API Application Programming Interface интерфейс прикладного программирования
ABI Application Binary Interface интерфейс двоичного уровня
IPC Inter-Process Communication межпроцессное взаимодействие
SMP Symmetric Multiprocessing симметричная многопроцессорная обработка
HAL Hardware Abstraction Layer уровень абстракции от аппаратуры
LKM Loadable Kernel Module загружаемый модуль ядра
VM Virtual Machine виртуальная машина
VMM Virtual Machine Monitor монитор виртуальных машин (гипервизор)
RPC Remote Procedure Call удалённый вызов процедур

Русские сокращения

Сокращение Обозначение
ОС операционная система
Ядро центральная часть ОС
Модуль загружаемый компонент ядра
ПЗУ память только для чтения (ROM)
В/В ввод-вывод

Модульность

Модуль — это функционально законченная часть системы, имеющая чёткий интерфейс (набор функций или системных вызовов). Модули могут компилироваться отдельно, загружаться динамически (LKM в Linux) и заменяться без перекомпиляции всей системы.

Преимущества модульности:

  • Упрощение разработки и отладки.
  • Повышение надёжности (сбой в одном модуле реже приводит к краху всей системы).
  • Переносимость (аппаратно-зависимые модули изолированы).

Иерархическая организация (слои)

Принцип, впервые строго реализованный в системе THE (Dijkstra, 1968). Система разбивается на уровни (layers), где уровень ‘n‘ использует услуги только уровней ‘0…n−1‘. Каждый уровень скрывает детали нижележащих уровней.

Пример иерархии (классическая схема ОС):

Уровень Назначение
0 Аппаратура (процессор, память, устройства)
1 Диспетчеризация процессов, базовое управление памятью
2 Механизмы IPC, синхронизация
3 Драйверы устройств (прерывания, ввод-вывод)
4 Файловые системы, виртуальная память
5 Системные вызовы (интерфейс для приложений)

Скрытие информации и абстракция

Каждый компонент ОС должен предоставлять абстракцию ресурса:

  • Физический процессор → абстракция «процесс» или «поток».
  • Физическая память → абстракция «виртуальное адресное пространство».
  • Дисковое устройство → абстракция «файл» или «блочное устройство».

Виртуализация

Виртуализация расширяет принцип абстракции: на одном физическом ресурсе создаётся несколько логических (виртуальных) экземпляров, изолированных друг от друга. Современные гипервизоры (VMware ESXi, KVM, Xen) — пример полной виртуализации аппаратуры.

Монолитное ядро (Monolithic Kernel)

Вся ОС (управление процессами, памятью, файловыми системами, драйверы) работает в одном адресном пространстве ядра, в режиме супервизора (ring 0 на x86). Компоненты вызывают друг друга напрямую.

@startuml
rectangle "Пользовательский режим" {
  [Приложения]
}
rectangle "Режим ядра (одно адресное пространство)" {
  component "Управление процессами" as proc
  component "Управление памятью" as mem
  component "Файловая система" as fs
  component "Драйверы устройств" as drv
  component "Сетевой стек" as net
  proc -[hidden]- mem
  mem -[hidden]- fs
  fs -[hidden]- drv
  drv -[hidden]- net
}
[Приложения] --> proc : системные вызовы
[Приложения] --> mem
[Приложения] --> fs
@enduml

Преимущества:

  • Высокая производительность (отсутствие переключений контекста между компонентами ядра).
  • Простота реализации (по сравнению с микроядром).

Недостатки:

  • Отсутствие изоляции: ошибка в любом драйвере может разрушить всё ядро (паника системы).
  • Плохая расширяемость (замена драйвера требует перекомпиляции ядра, хотя в современных монолитных ядрах есть модули).
  • Переносимость требует переработки больших частей кода.

Примеры: традиционные Unix (до микроядренных экспериментов), Linux (однако Linux поддерживает загружаемые модули, что частично решает проблему расширяемости), FreeBSD, MS-DOS, классические Windows 9x.

Многоуровневая (слоевая) архитектура (Layered)

Строгое соблюдение иерархии уровней. Каждый уровень реализован как отдельная абстрактная машина. Система THE (Technische Hogeschool Eindhoven) — эталон.

Преимущества:

  • Простота отладки (можно тестировать уровень за уровнем).
  • Высокая надёжность.

Недостатки:

  • Снижение производительности из-за многократного пересечения границ уровней.
  • Трудность определения «правильной» иерархии (циклические зависимости возможны).

Примеры: THE, некоторые исследовательские ОС (например, Hydra). В чистом виде сейчас почти не применяется, но принцип уровней используется при проектировании подсистем (например, стек TCP/IP — уровни, драйверы слой за слоем).

Микроядро (Microkernel)

Микроядро содержит только самые минимальные механизмы: управление процессами (базовое), межпроцессное взаимодействие (IPC), базовую адресацию памяти и обработку прерываний. Все остальные службы (файловые системы, драйверы, сетевой стек) работают как серверы в пользовательском режиме. Взаимодействие происходит через IPC.

@startuml
rectangle "Пользовательский режим" {
  component "Приложение" as app
  component "Сервер файловой системы" as fs_server
  component "Сервер драйверов" as drv_server
  component "Сервер сети" as net_server
  app -[hidden]r- fs_server
  fs_server -[hidden]r- drv_server
  drv_server -[hidden]r- net_server
}
rectangle "Режим ядра" {
  component "Микроядро\n(IPC, базовая планировка,\nуправление адресными пространствами)" as micro
}
app --> micro : системные вызовы
fs_server --> micro
drv_server --> micro
net_server --> micro
app <--> fs_server : IPC
fs_server <--> drv_server : IPC
@enduml

Преимущества:

  • Высокая надёжность и отказоустойчивость: сбой сервера не разрушает микроядро, сервер можно перезапустить.
  • Модульность и расширяемость: добавлять серверы легко.
  • Безопасность: серверы изолированы (каждый в своём адресном пространстве).

Недостатки:

  • Низкая производительность: частые переключения контекста (пользователь ↔ ядро) и копирование данных при IPC.
  • Сложность разработки (необходимость в хорошем дизайне IPC, избегании лишних копирований).

Примеры: QNX (используется в автомобилях, медицинском оборудовании), L4 (семейство высокопроизводительных микроядер), Minix 3 (учебная, но надёжная), Mach (основа для OS X/macOS, но там гибрид).

Гибридное ядро (Hybrid kernel)

Компромисс: часть драйверов и подсистем работает в пространстве ядра (для производительности), а часть — в пользовательском (для безопасности или лицензионных причин). Часто имеет архитектуру «монолитное ядро с модулями», но с вынесенными компонентами.

Примеры:

  • Windows NT (и все последующие Windows): ядро гибридное, но большая часть драйверов в режиме ядра, есть также подсистемы в пользовательском режиме (CSRSS, Win32k.sys частично).
  • XNU (ядро macOS, iOS): основано на Mach (микроядро) + компоненты BSD (монолитные) + драйверы I/O Kit в пользовательском режиме.

Экзоядро (Exokernel)

Минимальное ядро, которое только мультиплексирует аппаратные ресурсы (без их абстрагирования). Библиотеки операционной системы (libOS) работают в пользовательском режиме и напрямую, безопасно через экзоядро, управляют ресурсами. Обеспечивает максимальную производительность за счёт низких накладных расходов, но требует переписывания приложений.

Примеры: ExOS (MIT), Nemesis (Кембридж). В основном исследовательские.

Наноядро (Nanokernel)

Ядро ещё меньше, чем микроядро: предоставляет только аппаратные прерывания, таймеры, минимальный планировщик. Всё остальное — на более высоких уровнях. Практического распространения не получило.

Независимо от общей архитектуры, внутри ОС выделяют следующие подсистемы, которые могут быть организованы модульно:

  1. Управление процессами и потоками
    • Структура: планировщик, диспетчер, синхронизация (мьютексы, семафоры).
    • Принципы: вытесняющая многозадачность, поддержка SMP, приоритеты.
  2. Управление памятью
    • Физическое управление (страничные кадры, сегменты).
    • Виртуальная память (отображение, подкачка, кэш TLB).
    • Абстракция: адресное пространство процесса.
  3. Файловая система
    • Организация: VFS (Virtual File System) — уровень абстракции над конкретными ФС (ext4, FAT, NTFS).
    • Кэширование, журналирование, права доступа.
  4. Подсистема ввода-вывода и драйверы
    • Иерархия: обработчики прерываний, драйверы (блочные, символьные, сетевые), универсальные интерфейсы (ioctl, read, write).
    • Модель plug-and-play, управление питанием.
  5. Сетевая подсистема
    • Стек протоколов (обычно слойный: сокеты → TCP/UDP → IP → драйвер).
    • Модульность: загружаемые протоколы, фильтры пакетов.
  6. Безопасность и защита
    • Разграничение доступа (дискреционное / мандатное).
    • Аудит, шифрование, механизмы изоляции (капсулы, контейнеры).

Каждая подсистема проектируется с использованием тех же принципов (модульность, интерфейсы, уровни). Например, VFS — это классический слой абстракции.

Монолитные ядра (Linux, FreeBSD) реализуют динамическую загрузку модулей для устранения недостатка жёсткой связности. Модули могут добавлять драйверы, файловые системы, системные вызовы без перекомпиляции ядра.

Принципы:

  • Модуль компилируется отдельно, содержит таблицу экспортируемых символов.
  • Загрузка: команды `insmod` (Linux), `kldload` (FreeBSD).
  • Выгрузка: `rmmod`, `kldunload`.
  • Защита от выгрузки используемых модулей (счётчик ссылок).

Недостаток: модуль работает в адресном пространстве ядра и может его разрушить (нет изоляции).

Гипервизоры (VMM) добавляют новый уровень — виртуальные машины. Аппаратные ресурсы виртуализируются, и на одной физической машине работают несколько «гостевых» ОС. Гипервизоры делятся на два типа:

  • Тип 1 (bare-metal): гипервизор работает непосредственно на аппаратуре (VMware ESXi, Xen, Microsoft Hyper-V).
  • Тип 2 (хостированные): гипервизор — приложение хост-ОС (VirtualBox, VMware Workstation, QEMU).

Принципы структурирования гипервизора: прослойка между аппаратурой и гостевыми ОС, перехват привилегированных инструкций, управление памятью с аппаратной поддержкой (EPT, NPT).

@startuml
rectangle "Гостевая ОС 1" as guest1
rectangle "Гостевая ОС 2" as guest2
rectangle "Гипервизор (тип 1)" as hyper
rectangle "Аппаратура" as hw
guest1 -down-> hyper
guest2 -down-> hyper
hyper -down-> hw
@enduml
Характеристика Монолитное ядро Микроядро Гибридное
Производительность высокая низкая (из-за IPC) средняя
Надёжность низкая (один сбой — паника) высокая (серверы перезапускаются) средняя
Расширяемость средняя (модули) высокая высокая
Сложность разработки средняя высокая (IPC, синхронизация) высокая
Размер ядра (минимальный) ~1-10 МБ (Linux) ~10-100 КБ (L4) ~500 КБ – несколько МБ
Примеры Linux, FreeBSD, Windows 9x QNX, L4, Minix 3 Windows NT, XNU

Напишем простую программу, имитирующую вызовы «уровней» ОС: от приложения через системный вызов к драйверу. Это абстракция, не настоящие syscalls.

class Hardware:
    @staticmethod
    def write_console(msg):
        print(f"[HW] Вывод: {msg}")

class KernelDriver:
    def write(self, msg):
        Hardware.write_console(msg)

class SyscallInterface:
    def __init__(self):
        self.driver = KernelDriver()
    def sys_write(self, msg):
        self.driver.write(msg)

class Application:
    def __init__(self):
        self.sys = SyscallInterface()
    def run(self):
        self.sys.sys_write("Hello, OS structure!")

app = Application()
app.run()

Этот пример иллюстрирует скрытие информации: приложение не знает о драйвере и аппаратуре, оно вызывает только системный интерфейс.

  1. Унификация ядер для разных архитектур (Device Tree, ACPI) — вынос описания аппаратуры из ядра.
  2. Контейнеризация — изоляция на уровне ОС (Linux namespaces, cgroups) без полноценной виртуализации; добавляет новый слой абстракции между процессами и ядром.
  3. Микроядреные тенденции в embedded и IoT (Zephyr RTOS, Fuchsia — Google использует микроядро Zircon).
  4. Распределённые ОС и одноуровневые архитектуры (Plan 9, Inferno) — все ресурсы представлены как файлы в едином пространстве имён.
  5. Формальная верификация ядер (seL4 — первое микроядро с математическим доказательством корректности).