- PVSM.RU - https://www.pvsm.ru -
Дисклеймер: Эта статья — не руководство по написанию ОС и не туториал. Это срез архитектуры работающего ядра, которое прошло путь от вечных Page Faults и Segmentation Faults (в ring 3) до системы с 95 системными вызовами, сетевым стеком, COW и MLFQ-планировщиком. Все исходники открыты под GPLv3.
CactOS — гибридное монолитное ядро для архитектуры i686 (32-битный x86, protected mode). Проект стартовал как исследование низкоуровневого программирования и за 5-6 месяцев превратился в полноценную операционную систему, способную загружаться на реальном железе за менее чем 4 секунды — быстрее, чем BIOS проходит POST на некоторых машинах.
Ключевые цифры:
95 системных вызовов
73 — предыдущая стабильная версия, текущая — 95 (так же стабильная)
2 языка: C + Assembly (низкоуровневый код, драйверы) + Rust (менеджер памяти, планировщик, синхронизация, TCP/IP-стек)
4 уровня MLFQ-планировщика
32 одновременные точки монтирования VFS
~40 КиБ in-tree реализация ext4 (чтение/запись)
~32 КиБ xHCI-стек (USB 3.x)
Ядро гибридное в том смысле, что все подсистемы работают в одном адресном пространстве (ring 0). Однако критические компоненты вынесены в изолированные Rust-крейты с жёсткими границами FFI:
cact_mm — PMM, VMM, куча, slab-аллокатор, mmap, COW, swap, разделяемая память
sched — MLFQ-планировщик, очереди сна, таймеры
sync — спинлоки, IRQ-безопасные спинлоки, мьютексы, семафоры
cact_net — TCP/UDP/DHCP/DNS на базе smoltcp
Это даёт дисциплину внутри монолитного ядра(хотя и не обсолютную): каждый крейт компилируется отдельно, тестируется изолированно и не имеет доступа к чужим внутренностям, кроме публичного API. Такой подход упрощает рефакторинг и минимизирует гонки данных.
Процесс загрузки разбит на три фазы, каждая из которых решает строго ограниченный круг задач.
Парсинг структуры Multiboot2 — карта памяти, тег фреймбуфера, модули GRUB
cctkfs staging — pci_modblob_load() копирует GRUB-модуль cctkfs из физической памяти в .bss ядра до включения пейджинга. Это принципиально: после включения страничной трансляции физический адрес модуля, переданный GRUB, становится недоступен
Инициализация фреймбуфера; если тег отсутствует или размер равен нулю — halt
Проверка магического числа 0x36D76289
Вызов kernel_setup_hardware() (фаза B)
Создание задачи kernel_bootstrap_main — отложенная работа, требующая планировщик
sti — загрузочный поток становится idle-задачей (HLT-цикл), таймер запускает вытеснение
|
# |
Подсистема |
Почему именно здесь |
|---|---|---|
|
1 |
GDT → PMM → VMM → kmalloc → пейджинг |
База всего |
|
2 |
Slab-аллокатор + page fault handler |
COW, demand zero, swap-маркеры |
|
3 |
PIC + IDT + COM1 serial |
Часть kprint/klog дублируется на хост |
|
4 |
Фреймбуфер + MTRR write-combining |
Опциональный shadow buffer с batched blit |
|
5 |
PS/2 клавиатура и мышь |
Предупреждения при 0xFF на порту 0x64 |
|
6 |
PIT 100 Гц |
Таймер до сканирования PCI (нужен GDD для таймаутов) |
|
7 |
blkdev_init → PCI scan → usb_init (xHCI) |
Блочные устройства до PCI, чтобы AHCI/NVMe могли зарегистрироваться |
|
8 |
Page cache + swap |
Swap-раздел опционален, ошибка не фатальна |
|
9 |
vfs_init + net_init |
knetd-поток на семафоре, net_poll / stack_poll |
|
10 |
task_init + init_scheduler |
MLFQ на Rust |
Этот поток создан специально для операций, которые нельзя выполнять на сыром загрузочном стеке:
pci_driver_probe_deferred_all() — подключает PCI-драйверы, небезопасные на этапе голой инициализации
mntfs_init — парсит таблицу монтирования, монтирует ext4 на NVMe/AHCI. Может заблокироваться на семафоре в ожидании IRQ от дискового контроллера. На загрузочном стеке это невозможно: прерывания всё ещё глобально запрещены (cli), планировщик не запущен, и обработчик IRQ никогда не выполнит sema_up — мёртвая блокировка гарантирована. Поэтому mntfs_init вынесен в отдельный поток ядра kernel_bootstrap_main, который стартует уже после sti и инициализации планировщика.
create_elf_task("bin/init") — первый userspace-процесс, загружаемый через binfs (ext4 /bin + cctkfs-оверлей)
Результат на последовательном порту / фреймбуфере:
text
Cact Kernel 1.0.0
--------------------------
[VER] commit=<Hash Commit> built=<Built data>
Kernel is ready. Launching init...
PMM адресует все 4 ГиБ физического адресного пространства под PCI-дырой как индексируемые фреймы. Фреймы внутри младших 32 МиБ (BIOS, образ ядра, статические таблицы страниц) помечены как занятые навсегда.
Ключевые константы:
|
Символ |
Значение |
Смысл |
|---|---|---|
|
|
0x00100000 |
Нижняя граница загрузки ядра |
|
|
0xE0000000 |
Первый адрес, не отдаваемый PMM (MMIO/PCI) |
|
|
~917 504 |
Количество 4K-фреймов |
|
|
~112 КиБ |
Размер битовой карты |
|
|
0x02000000 (32 МиБ) |
Нижняя память никогда не отдаётся ни ядру, ни пользователю |
Пользовательское виртуальное пространство (упрощённо):
text
0xC0000000 ┌──────────────────┐ Только ядро (ring 0)
0xBF000000 ├──────────────────┤ Дно пользовательского стека
│ user stack ↓ │
0xBEFFF000 ├──────────────────┤ sigreturn trampoline (int 0x80)
0xB0000000 ├──────────────────┤ Потолок SHM
0xA0000000 ├──────────────────┤ База SHM
0x80000000 ├──────────────────┤ Потолок пользовательской кучи
0x40000000 ├──────────────────┤ mmap + brk (до 256 регионов)
0x08048000 ├──────────────────┤ Типичная база ELF PT_LOAD
0x00000000 └──────────────────┘ NULL/guard
Флаги страниц: PAGE_PRESENT, PAGE_RW, PAGE_USER, PAGE_COW (0x200), PAGE_DEMAND (0x400 — demand-filled / zero-on-first-touch), PAGE_ZERO (0x800 — заполнение нулями по требованию), PAGE_SWAPPED (0x008 при PRESENT=0 — страница выгружена в swap), PDE_PRIVATE (0x200 в PDE — «эта таблица страниц своя у процесса», тег для fork/COW).
Планировщик полностью написан на Rust. 4 уровня очередей:
|
Уровень |
Название |
Квант |
Назначение |
|---|---|---|---|
|
0 |
Real-time |
5 тиков |
Наивысший приоритет |
|
1 |
Interactive |
1 тик |
Цель для boost (latency-sensitive) |
|
2 |
Normal |
2 тика |
По умолчанию для новых задач |
|
3 |
Background |
4 тика |
CPU-bound batch |
Правила:
Anti-starvation boost каждые 50 тиков: задачи с Normal и ниже поднимаются к Interactive
Voluntary block bonus: если задача блокируется дольше половины кванта, она может получить повышение при пробуждении
Sleep queue + alarms / setitimer обрабатываются на каждом тике
SCHEDULE_IN_PROGRESS — защита от вложенного входа в планировщик
Состояния задач: TASK_READY, TASK_RUNNING, TASK_SLEEPING, TASK_ZOMBIE, TASK_WAITING.
Сетевой стек реализован как гибрид C и Rust. Легаси-путь на C владеет Ethernet-демультиплексированием, ARP, частями IPv4/ICMP и временем жизни skb. TCP/UDP-сокеты для системных вызовов работают через smoltcp внутри крейта cact_net.
Ключевые компоненты:
stack_poll() — драйвер интерфейса
DHCPv4 обновляет runtime IPv4 + IP DNS-сервера
SYS_DNS_RESOLVE — блокирующий A-запрос через UDP/53
SYS_PING_ECHO — ICMP echo request
knetd — выделенный поток ядра: спит на семафоре, просыпается по RX прерыванию NIC, вызывает net_poll → stack_poll()
Ограничения: нет IPv6, нет TLS внутри ядра, NIC по умолчанию — virtio-net в QEMU, флаги send/recv могут игнорироваться в libc, DNS-резольвер — только A-записи.
Логические TCP-состояния (C-метаданные / VFS-представление; ingress TCP обрабатывается smoltcp):
text
CLOSED → LISTEN → SYN_SENT → SYN_RECEIVED
→ ESTABLISHED
→ FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT
→ CLOSE_WAIT → LAST_ACK → CLOSED
|
Область |
Компоненты |
|---|---|
|
Блочные устройства |
AHCI, NVMe, blkdev, page cache |
|
USB |
xHCI, HID, hub |
|
Ввод |
PS/2 клавиатура и мышь |
|
Видео |
Linear FB 32 bpp, шрифт 8×8 с масштабированием ×2, MTRR WC + shadow |
|
PCI |
Сканирование конфигурации, таблица драйверов, GDD, загрузчик ET_REL-модулей |
|
Сеть |
virtio-net (по умолчанию в QEMU) |
Внешние PCI-драйверы (например, Marvell Yukon) живут в отдельных *-for-Cact репозиториях, собираются как .cctk-модули и упаковываются в cctkfs.img.
|
ФС |
Статус |
Примечания |
|---|---|---|
|
ext4 |
Активна |
Компактная in-tree реализация: чтение/запись, inode-операции (~40 КиБ) |
|
VFS |
Активна |
До 32 одновременных точек монтирования, symlink-пул с защитой от ELOOP, биты rwx |
|
devfs, procfs, mntfs, etcfs, tmpfs |
Активны |
Полноценные реализации |
|
binfs, sbinfs, libfs, varfs |
Активны |
cctkfs-оверлей поверх ext4 |
|
pipes |
Активны |
|
|
btrfs, exFAT, ramfs |
Заглушки |
Placeholder-заголовки |
Авторитетный список — Cact/kernel/core/syscalls/syscalls.h, который должен побайтово совпадать с syscall.h в CactLib. Многие syscall'ы принимают struct syscall_frame* (полный снимок регистров) в диспетчере.
Группы:
Процессы: fork, exec, exit, waitpid, sleep, getpid/getppid
Сигналы: kill, signal, sigaction, sigprocmask, sigreturn, sigpending, sigsuspend, alarm, setitimer
Память: brk, mmap, munmap, mprotect, shmget/shmat/shmdt/shmctl
Сеть: socket, bind, connect, listen, accept, send/recv, sendto/recvfrom + PING_ECHO, NETCFG_SET, DNS_RESOLVE
Файлы: open/read/write/close, stat/fstat, getdents, rename, mkdir, rmdir, symlink, readlink, link/unlink
Система: mount, umount, reboot, uname, module_load/module_unload
Ring 0 — полный дамп регистров, сообщение, cli; hlt:
text
=== KERNEL PANIC ===
Exception: 14 (#PF) Error code: 0x00000003
EIP: 0xC010A3F2 CS: 0x00000008
EAX: 0x00000000 EBX: 0xDEADBEEF ECX: 0x00000001 EDX: 0x00000000
ESP: 0xC01FF9E0 EBP: 0xC01FFA10
System halted.
Ring 3 — исключения CPU преобразуются в Unix-подобные сигналы для задачи-нарушителя:
|
Исключение |
Сигнал |
Типичная причина |
|---|---|---|
|
#DE (vector 0) |
SIGFPE |
Целочисленное деление на ноль |
|
#MF (vector 16) |
SIGFPE |
Ошибка x87 FPU |
|
#GP (vector 13) |
SIGSEGV |
General protection fault |
|
Остальные |
SIGKILL |
Неподдерживаемый путь обработки |
Уход от устаревших интерфейсов общения с "железом"
Переход от текстового фреймбуфера к графическому режиму
Оконный менеджер с поддержкой мыши
Собственный набор виджетов
Каждая подсистема (менеджер памяти, планировщик, VFS, сетевой стек, драйверы) — изолированный модуль
Веб-сервер на собственном TCP/IP-стеке
Портирование инструментов: компилятор, редактор
Лицензия: GNU General Public License v3.0
Автор: QwaYer
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/system-programming/452290
Ссылки в тексте:
[1] Репозиторий ядра в GIt: https://github.com/QwaYer/CactKernel-x86_32
[2] Дистрибутив ОС в Git: https://github.com/QwaYer/CactOS-x86_32
[3] Источник: https://habr.com/ru/articles/1039670/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1039670
Нажмите здесь для печати.