Cíle a rozsah vlastního jednoduchého OS
Vytvoření vlastního jednoduchého operačního systému je náročný, ale mimořádně poučný projekt. Cílem není konkurovat produkčním systémům, nýbrž pochopit jádro konceptů: zavádění, správu paměti, přerušení, plánování procesů, systémová volání, základní ovladače a jednoduché uživatelské prostředí. Tento článek nabízí technickou roadmapu, návrhové vzory a praktické rady, jak se z nuly dopracovat k minimalistickému, ale funkčnímu OS běžícímu na emulátoru či reálném železe.
Volba cílové platformy a architektury
Než napíšete jediný řádek kódu, zvolte CPU architekturu a bootovací ekosystém. Pro studijní projekt se osvědčují:
- x86_64 + UEFI/BIOS: nejvíce materiálů, snadné spuštění v QEMU/Bochs, možnost GRUB a specifikace Multiboot.
- RISC-V (rv64): čistá ISA, moderní ekosystém, transparentní stránkování, dobré pro výuku.
- AArch64: reálné nasazení v embedded, složitější start (Device Tree, firmware), bohaté možnosti.
Začněte s jednou platformou a deterministickým cílem: „boot na konzoli, výpis textu, inicializace paměti, jednoduchý plánovač vláken, několik systémových volání“.
Nástroje a build řetězec
- Cross-kompilátor: vlastní GCC/Clang toolchain (např.
x86_64-elf-gcc) pro čisté binárky bez vazby na libc hostitele. - Binutils:
ld(linker),objcopy,objdump,nmpro práci s artefakty. - Emulátor: QEMU pro rychlé iterace, Bochs pro pedantskou emulaci; GDB pro vzdálené ladění.
- Build systém: Make/CMake + skripty, reprodukovatelný build, verzování konfigurace.
- Kontinuální testy: skripty, které OS nabootují v QEMU, validují výstup na sériové konzoli a vrací návratový kód.
Boot proces: od firmware po vaše jádro
Boot flow se liší podle firmware a architektury:
- BIOS (legacy): CPU startuje v real módu (16bit), MBR načte bootloader, ten přepne do protected/long módu a předá řízení jádru.
- UEFI: firmware načítá PE/COFF binárku, poskytuje služby (protokoly), vaše EFI aplikace zavolá ExitBootServices a předá kontrolu jádru.
- GRUB/Multiboot(2): robustní možnost, kdy GRUB připraví prostředí a předá parametry (mapu paměti, moduly, cmdline) jádru dle specifikace.
V obou případech potřebujete linker script (mapování sekcí do paměti), startovní kód (pro nastavení stacku, segmentů, skoku do C) a konvenci pro předání parametrů (Multiboot, Device Tree, UEFI hand-off struktura).
Režimy CPU a přepnutí do long módu (x86_64)
Pro x86_64 je typický postup: inicializace GDT, povolení PAE, nastavení tabulek stránek pro identické mapování, zapnutí CR0/CR4 příznaků, přepnutí do long módu přes EFER.LME a skok do 64bit kódu. Následuje inicializace IDT a maskování/konfigurace přerušení.
Správa paměti: fyzická, virtuální a alokátory
- Mapa paměti: získejte od firmware (UEFI memory map) nebo bootloaderu (Multiboot). Vyřaďte rezervované oblasti.
- Fyzický alokátor rámců: bitmapa nebo buddy systém pro přidělování 4 KiB (příp. větších) rámců.
- Virtuální paměť: zavedení 4-úrovňového stránkování (x86_64: PML4 → PDPTE → PDE → PTE), mapování jádra do vyšších adres.
- Kernel heap: jednoduchý slab/zone alokátor nebo buddy + slab pro malé objekty; pozor na fragmentaci a zacyklení při alokaci během obsluh přerušení.
Přerušení, výjimky a časování
- IDT: instalace bran pro výjimky (dělení nulou, page fault) a hardware IRQ.
- Řadiče přerušení: PIC (8259) pro legacy, APIC/IOAPIC a LAPIC timer pro moderní SMP; v UEFI prostředí preferujte HPET/HPET MSI.
- Časovače: PIT (8253) pro jednoduchost, HPET pro přesnost, TSC deadline timer pro nízkou režii.
Ovladače základních zařízení
Startovní OS si vystačí s minimem:
- Konzole: textový režim (VGA) nebo sériová linka (16550 UART) pro výpis logů a příkazovou řádku.
- Úložiště: začněte s RAM diskem a později přidejte AHCI (SATA) nebo virtio-blk (v QEMU).
- Vstup: PS/2 klávesnice nebo virtio-input; mapování scancode → klíč.
- Síť: odložte na později, případně virtio-net pro jednoduché rámce.
Souborové systémy: od RAMFS k FAT
Pro první iteraci je praktický RAMFS – jednoduchý strom v paměti s pevně zabudovaným obsahem. Pro persistenci přidejte čtečku FAT12/16/32 (snadná specifikace, dobrá pro boot média). Interně definujte VFS vrstvu (inode, dentry, superblock) a oddělte ji od ovladačů blokových zařízení.
Procesy, vlákna a plánování
- Model adresního prostoru: jeden globální kernel space + oddělené user space mapy pro procesy.
- Vlákna: kernelové kontexty s vlastními stacky; přepínání kontextu na tick/událost.
- Plánovač: nejdříve round-robin s kvantem, později priority/scheduler s vrstvami (interactive vs. batch).
- Synchronizace: spinlocky v jádře, semafory/mutexy pro subsystémy; pozor na priority inversion.
Systémová volání a ABIs
Návrh malého, čistého API je klíčový. Začněte s voláními: write (na konzoli/soubor), read, open/close, fork/exec/exit (nebo jednodušší spawn), sleep, gettime, mmap/brk. Zvolte volací konvenci (x86_64 SysV: registry rdi, rsi, rdx, r10, r8, r9 a rax pro číslo volání) a bránu (instrukce syscall/sysret nebo int 0x80 pro start).
Uživatelský prostor a minimalistická knihovna
Pro spuštění uživatelských programů vytvořte základní libc subset (např. printf, malloc/free, string.h), jednoduchý loader (ELF64 parser) a shell – i kdyby pouze se sadou interních příkazů. Programy kompilujte cross-kompilátorem proti vaší libc.
Bezpečnostní pilíře od první iterace
- Izolace: uživatelský režim vs. kernel (ring3/ring0), NX bit, oddělené stránkování, kontrola přístupu do I/O portů.
- Validace vstupů: pečlivě kontrolujte parametry systémových volání a hranice bufferů.
- Neprivilegované API: rozhraní, která nevyžadují raw přístup k hardwaru (VFS, device files).
- Audit a logování: sériová konzole, kruhové buffery, jednoduché úrovně logů.
Debugging a testování
- GDB remote: spuštění QEMU s příznaky pro ladění a napojení GDB; breakpointy ve startu i v jádře.
- Symboly a mapy: generujte symboly, udržujte mapu adres pro rychlé vyhledávání pádů.
- Smoke testy: skripty, které při každém buildu ověří „boot → prompt → spuštění testu → návratový kód“.
Modularita a správná separace vrstev
Udržujte jasné hranice: architektura (arch-specifické kódy), HAL (abstrakce hardwaru), jádro (správa paměti, plánovač, IPC), VFS/ovladače, uživatelský prostor. Striktní rozdělení minimalizuje coupling, usnadňuje portování a testy.
IPC a synchronizace procesů
Pro jednoduchý OS stačí pipes a signály. Později přidejte message queues nebo sdílenou paměť s pojmenovanými semafory. Důsledně zvažte preempci a priority, aby IPC nevedlo k hladovění vláken.
Správa času a časových pásem
Zavést monotónní čas (na plánování a timeouty) a reálný čas (RTC) pro časové značky. Udržujte tickless režim, kde je to možné, abyste šetřili CPU.
Jednoduché grafické prostředí (volitelné)
Po stabilizaci jádra lze přidat framebuffer driver (VBE/UEFI GOP) a minimalistický kompozitor. Začněte s „Hello pixel“ a kreslením fontu; grafické UI však významně komplikuje spravování vstupu, kompozici oken a bezpečnost.
Formáty binárek a loader
Doporučený formát je ELF64. Loader mapuje segmenty do paměti, nastaví entry point, předá argv/envp a vytvoří počáteční vláknový kontext. Přidejte ASLR až později; vyžaduje entropii a adekvátní mapování knihoven.
Rozšíření: SMP a více jader
Po zprovoznění jádra na jednom CPU aktivujte ostatní jádra (AP bring-up), nastavte per-CPU struktury a lokální časovače. Plánovač musí být škálovaný (run-queue per CPU) a synchronizace musí počítat s lock contention.
Minimalistická politika napájení a úsporné režimy
I v jednoduchém OS můžete implementovat instrukci pro usínání CPU mezi tick-y (např. hlt na x86), případně základ ACPI pro vypnutí/restart. Pokročilejší P-states/C-states ponechte na pozdější iterace.
Distribuce, balení a spouštění
- ISO/IMG: vytvořte bootovatelné ISO s GRUB nebo UEFI aplikací; přidejte kernel a initramfs/RAM disk.
- Konfigurační soubory:
grub.cfgči UEFI boot entries; volby jádra v cmdline (např.root=ramfs). - Artefakty: publikujte debug symboly, mapy, dokumentaci ABI.
Licencování a třetí strany
I pro studijní OS zvažte licenci (MIT/BSD/GPL) a původ kódu (včetně fontů, mikroknihoven či obrázků). Vyhněte se kopírování produkčních zdrojáků bez souladu s licencemi.
Roadmapa implementace krok za krokem
- Boot minimum: text na konzoli, vlastní linker skript, start v long módu.
- IDT a výjimky: obsluha page fault, časovač, základní IRQ.
- Fyzická a virtuální paměť: alokátor rámců, mapování kernelu, jednoduchý heap.
- Konzole a I/O: sériová linka, základní ovladač klávesnice.
- Vláknový plánovač: kontextové přepínání, timer preempce, synchronizace.
- Systémová volání: jádro API, přechod do user space, „Hello world“ proces.
- VFS a RAMFS: soubory, adresáře, file descriptors; později FAT reader.
- Loader ELF: spouštění binárek, jednoduchá libc, shell.
- Testy a ladění: CI skripty, GDB scénáře, regresní sady.
- Rozšíření: SMP, virtio-blok/síť, IPC, základní bezpečnostní politiky.
Výkonnost a profilace
Měřte čas přepnutí kontextu, latenci přerušení, propustnost I/O a overhead synchronizačních primitiv. Přidejte jednoduchý profiler (počítadla cyklů, časové stopky). Mikrooptimalizace odkládejte až po funkční stabilitě.
Spolehlivost a zotavení z chyb
Implementujte panic handler s výpisem registrů, stack trace a bezpečný reboot. Chybové kódy vracejte konzistentně, alokace obalujte guard objekty, dbejte na restartovatelnost subsystémů (např. ovladačů) bez tvrdého resetu.
Dokumentace a udržitelnost
Každý subsystém (MM, IRQ, VFS, scheduler) doplňte krátkým design dokumentem, diagramem datových struktur a popisem veřejných rozhraní. Vytvořte vývojářskou příručku pro přispěvatele a styl kódu (názvosloví, formátování, testovací standardy).
Závěr
Vývoj jednoduchého OS je o disciplíně malých kroků, čistých rozhraních a neustálém testování. Držte se minimalistického jádra, přidávejte funkce iterativně a zajišťujte, aby každá etapa byla samostatně spustitelná a testovatelná. Tím získáte solidní pochopení principů operačních systémů a robustní výchozí bod pro další experimenty a rozšiřování.