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 základní koncepty: 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 od nuly dopracovat k minimalistickému, ale funkčnímu OS běžícímu v emulátoru či na skutečném hardwaru.
Volba cílové platformy a architektury
Než napíšete jediný řádek kódu, zvolte architekturu CPU a ekosystém pro zavádění systému. Pro studijní projekt se osvědčují:
- x86_64 + UEFI/BIOS: nejvíce materiálů, snadné spuštění v QEMU/Bochs, možnost použití GRUBu a specifikace Multiboot.
- RISC-V (rv64): čistá ISA, moderní ekosystém, transparentní stránkování, vhodná architektura pro výuku.
- AArch64: reálné nasazení v embedded systémech, složitější zavádění (Device Tree, firmware), bohaté možnosti.
Začněte s jednou platformou a konkrétním, deterministickým cílem: „zavedení systému do konzole, výpis textu, inicializace paměti, jednoduchý plánovač vláken a několik systémových volání“.
Nástroje a řetězec pro sestavení
- Křížový kompilátor: vlastní toolchain GCC/Clang (např.
x86_64-elf-gcc) pro čisté binární soubory bez vazby na libc hostitelského systému. - Binutils:
ld(linker),objcopy,objdump,nmpro práci s artefakty. - Emulátor: QEMU pro rychlé iterace, Bochs pro důkladnou emulaci; GDB pro vzdálené ladění.
- Systém sestavení: Make/CMake a skripty, reprodukovatelné sestavení, verzování konfigurace.
- Průběžné testy: skripty, které zavedou OS v QEMU, ověří výstup na sériové konzoli a vrátí návratový kód.
Proces zavádění: od firmwaru k vašemu jádru
Průběh zavádění se liší podle firmwaru a architektury:
- BIOS (legacy): CPU se spustí v reálném režimu (16bit), MBR načte zavaděč, který přepne procesor do protected/long módu a předá řízení jádru.
- UEFI: firmware načte binární soubor PE/COFF, poskytne služby (protokoly), vaše aplikace EFI zavolá ExitBootServices a předá řízení jádru.
- GRUB/Multiboot(2): robustní možnost, při níž GRUB připraví prostředí a předá jádru parametry (mapu paměti, moduly, příkazový řádek) podle specifikace.
V obou případech potřebujete linker script (mapování sekcí do paměti), startovací kód (pro nastavení zásobníku, segmentů a skoku do C) a konvenci pro předávání parametrů (Multiboot, Device Tree, předávací strukturu UEFI).
Režimy CPU a přepnutí do dlouhého režimu (x86_64)
Pro x86_64 je typický následující postup: inicializace GDT, povolení PAE, nastavení tabulek stránek pro identické mapování, zapnutí příznaků CR0/CR4, přepnutí do dlouhého režimu přes EFER.LME a skok do 64bitového kódu. Následuje inicializace IDT a maskování či konfigurace přerušení.
Správa paměti: fyzická, virtuální a alokátory
- Mapa paměti: získejte ji od firmwaru (mapa paměti UEFI) nebo zavaděče (Multiboot). Rezervované oblasti vyřaďte.
- Alokátor fyzických rámců: bitmapa nebo buddy systém pro přidělování rámců o velikosti 4 KiB (případně větších).
- Virtuální paměť: zaveďte čtyřúrovňové stránkování (x86_64: PML4 → PDPTE → PDE → PTE) a mapování jádra do vyšších adres.
- Halda jádra: jednoduchý alokátor slab/zone nebo kombinace buddy a slab alokátoru pro malé objekty; pozor na fragmentaci a zacyklení při alokaci během obsluhy přerušení.
Přerušení, výjimky a časování
- IDT: instalace bran pro výjimky (dělení nulou, page fault) a hardwarová IRQ.
- Řadiče přerušení: PIC (8259) pro legacy systémy, APIC/IOAPIC a LAPIC timer pro moderní SMP; v prostředí UEFI upřednostněte 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í
Pro začátek si OS 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: klávesnice PS/2 nebo virtio-input; mapování scancode → klávesa.
- Síť: nechte ji na později, případně přidejte 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 předem zabudovaným obsahem. Pro trvalé ukládání dat přidejte čtečku FAT12/16/32 (snadná specifikace, vhodná pro zaváděcí média). Interně definujte vrstvu VFS (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í prostor jádra a oddělené mapy uživatelského prostoru pro jednotlivé procesy.
- Vlákna: kontexty jádra s vlastními zásobníky; přepínání kontextu při tiknutí časovače nebo události.
- Plánovač: nejprve round-robin s časovým kvantem, později plánovač s prioritami a vrstvami (interaktivní vs. dávkové úlohy).
- Synchronizace: spinlocky v jádře, semafory/mutexy pro subsystémy; pozor na inverzi priorit.
Systémová volání a ABI
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 vstupní bránu (instrukce syscall/sysret nebo pro začátek int 0x80).
Uživatelský prostor a minimalistická knihovna
Pro spouštění uživatelských programů vytvořte základní podmnožinu libc (např. printf, malloc/free, string.h), jednoduchý zavaděč (parser ELF64) a shell – i kdyby obsahoval pouze sadu interních příkazů. Programy kompilujte křížovým kompilátorem proti vlastní libc.
Bezpečnostní pilíře od první iterace
- Izolace: uživatelský režim vs. jádro (ring3/ring0), bit NX, oddělené stránkování, kontrola přístupu k I/O portům.
- Validace vstupů: pečlivě kontrolujte parametry systémových volání a hranice bufferů.
- Neprivilegované API: rozhraní, která nevyžadují přímý přístup k hardwaru (VFS, soubory zařízení).
- Audit a logování: sériová konzole, kruhové buffery, jednoduché úrovně logování.
Ladění a testování
- Vzdálené ladění v GDB: spuštění QEMU s příznaky pro ladění a připojení GDB; breakpointy při startu i v jádře.
- Symboly a mapy: generujte symboly a udržujte mapu adres pro rychlé vyhledávání příčin pádů.
- Smoke testy: skripty, které při každém sestavení ověří „zavedení systému → výzva shellu → spuštění testu → návratový kód“.
Modularita a správné oddělení vrstev
Udržujte jasné hranice mezi jednotlivými vrstvami: architektura (kód specifický pro danou architekturu), HAL (abstrakce hardwaru), jádro (správa paměti, plánovač, IPC), VFS/ovladače a uživatelský prostor. Striktní rozdělení minimalizuje provázanost, usnadňuje portování i testování.
IPC a synchronizace procesů
Pro jednoduchý OS stačí roury a signály. Později přidejte fronty zpráv 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
Zaveďte monotónní čas (pro plánování a časové limity) a reálný čas (RTC) pro časová razítka. Pokud je to možné, udržujte režim tickless, abyste šetřili CPU.
Jednoduché grafické prostředí (volitelné)
Po stabilizaci jádra lze přidat ovladač framebufferu (VBE/UEFI GOP) a minimalistický kompozitor. Začněte s „Hello pixel“ a vykreslováním fontu; grafické uživatelské rozhraní však výrazně komplikuje správu vstupu, kompozici oken a zabezpečení.
Formáty binárních souborů a zavaděč
Doporučeným formátem je ELF64. Zavaděč mapuje segmenty do paměti, nastaví vstupní bod, předá argv/envp a vytvoří počáteční kontext vlákna. ASLR přidejte až později; vyžaduje entropii a odpovídající 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 struktury pro jednotlivá CPU a lokální časovače. Plánovač musí být škálovatelný (fronta připravených úloh pro každé CPU) a synchronizace musí počítat s kolizemi při získávání zámků.
Minimalistická správa napájení a úsporné režimy
I v jednoduchém OS můžete implementovat instrukci pro uspání CPU mezi tiknutími časovače (např. hlt na x86), případně základní podporu ACPI pro vypnutí/restart. Pokročilejší stavy P-states/C-states ponechte na pozdější iterace.
Distribuce, balení a spouštění
- ISO/IMG: vytvořte zaváděcí ISO s GRUBem nebo aplikací UEFI; přidejte jádro a initramfs/RAM disk.
- Konfigurační soubory:
grub.cfgnebo položky spouštění UEFI; volby jádra v příkazovém řádku (např.root=ramfs). - Artefakty: zveřejněte ladicí symboly, mapy a dokumentaci ABI.
Licencování a třetí strany
I u studijního 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í zdrojového kódu produkčních systémů bez ohledu na podmínky licencí.
Roadmapa implementace krok za krokem
- Minimum pro zavedení: text na konzoli, vlastní linker script, spuštění v dlouhém režimu.
- IDT a výjimky: obsluha page fault, časovač, základní IRQ.
- Fyzická a virtuální paměť: alokátor rámců, mapování jádra, jednoduchá halda.
- Konzole a I/O: sériová linka, základní ovladač klávesnice.
- Plánovač vláken: přepínání kontextu, preempce pomocí časovače, synchronizace.
- Systémová volání: API jádra, přechod do uživatelského prostoru, proces „Hello world“.
- VFS a RAMFS: soubory, adresáře, deskriptory souborů; později čtečka FAT.
- Zavaděč ELF: spouštění binárních souborů, jednoduchá libc, shell.
- Testování a ladění: skripty CI, scénáře pro GDB, regresní sady.
- Rozšíření: SMP, virtio-blk/síť, IPC, základní bezpečnostní politiky.
Výkonnost a profilování
Měřte dobu přepnutí kontextu, latenci přerušení, propustnost I/O a režii synchronizačních primitiv. Přidejte jednoduchý profiler (počítadla cyklů, časové stopky). Mikrooptimalizace odložte až na dobu po dosažení funkční stability.
Spolehlivost a zotavení z chyb
Implementujte obslužnou rutinu panic s výpisem registrů, trasováním zásobníku a bezpečným restartem. Chybové kódy vracejte konzistentně, alokace obalujte ochrannými objekty a dbejte na možnost restartování subsystémů (např. ovladačů) bez tvrdého resetu.
Dokumentace a udržitelnost
Každý subsystém (MM, IRQ, VFS, plánovač) doplňte stručným návrhovým dokumentem, diagramem datových struktur a popisem veřejných rozhraní. Vytvořte vývojářskou příručku pro přispěvatele a definujte styl kódu (názvosloví, formátování, standardy testování).
Závěr
Vývoj jednoduchého OS vyžaduje disciplínu při práci v malých krocích, čistá rozhraní a neustálé testování. Držte se minimalistického jádra, přidávejte funkce postupně a dbejte na to, aby každá etapa byla samostatně spustitelná a testovatelná. Získáte tak solidní pochopení principů operačních systémů a robustní výchozí bod pro další experimenty a rozšiřování.
