Genesis
Překlad tutoriálů pro začínající OS vývojáře od Jamese Molloye (www.jamesmolloy.co.uk).
Chtěl bych poděkovat Jamesi Molloyovi, který mi dovolil jeho tutoriál(y) přeložit. Tuším, že se tak stanou vůbec prvními svého druhu na českém Internetu.
Kontakt na autora:
- www.jamesmolloy.co.uk - autorův "internetový domov", najdete zde jednak tutoriály v originálním anglickém podání, jednak "Jimix" - jádro psané v C++
- james<at>jamesmolloy.co.uk - emaily na tuto adresu pište prosím POUZE v angličtině
OK, přišel čas kódit! Ačkoli hlavní část jádra bude napsána v C, v určitých oblastech se bez assembleru zkrátka neobejdeme. Jedním z těchto případů je počáteční bootovací kód.
Tady ho máme:
; ; boot.s -- Startovní pozice jádra. Rovněž definuje multiboot hlavičku. ; Založeno na Bran's kernel development tutorial souboru start.asm ; MBOOT_PAGE_ALIGN equ 1<<0 ; Nahraje jádro a moduly na začátek stránky MBOOT_MEM_INFO equ 1<<1 ; Poskytne jádru informace o paměti MBOOT_HEADER_MAGIC equ 0x1BADB002 ; hodnota Multiboot Magic ; POZNÁMKA: Nepoužíváme MBOOT_AOUT_KLUDGE. To znamená, že nám GRUB ; nepředá tabulku symbolů. MBOOT_HEADER_FLAGS equ MBOOT_PAGE_ALIGN | MBOOT_MEM_INFO MBOOT_CHECKSUM equ -(MBOOT_HEADER_MAGIC + MBOOT_HEADER_FLAGS) [BITS 32] ; Všechny instrukce by měli být 32-bitové [GLOBAL mboot] ; Zpřístupní 'mboot' z C. [EXTERN code] ; Start '.text' sekce. [EXTERN bss] ; Start .bss sekce. [EXTERN end] ; Konec poslední načítané sekce. mboot: dd MBOOT_HEADER_MAGIC ; GRUB bude hledat tuto hodnotu na ; začátku každé stránky jádra dd MBOOT_HEADER_FLAGS ; Jak by měl GRUB nahrát váš soubor / nastavení dd MBOOT_CHECKSUM ; Zaručí, že jsou horní hodnoty správné. dd mboot ; Pozice tohoto deskriptoru. dd code ; Začátek sekce '.text' (kód). dd bss ; Konec sekce '.data'. dd end ; Konec jádra. dd start ; Vstupní bod jádra (počáteční EIP). [GLOBAL start] ; Vstupní bod jádra. [EXTERN main] ; Vstupbní bod pro kód v C start: push ebx ; Předá pozici multiboot hlavičky ; Spuštění jádra: cli ; Zakáže přerušení. call main ; Zavolá naši funkci main(). jmp $ ; Vstoupíme do nekonečné smyčky, abychom zabránili ; procesoru ve spouštění všelijakého bordelu nacházejícího se ; v paměti za jádrem!
Je tam vlastně jen pár řádků kódu:
push ebx cli call main jmp $
To ostatní je multiboot hlavička.
Multiboot
GRUB předpokladá, že jádro dodržuje multiboot standard. Je to způsob, jakým může bootloader
- přesně zjistit, jaké prostředí bude jádro při zavádění potřebovat/požadovat
- poskytnout jádru informace o prostředí, které si pro něj připravil
Takže například pokud se bude vaše jádro potřebovat načíst ve specifickém VESA módu (což je mimochodem špatný nápad), informujete bootloader a on se už o to postará za vás.
Aby bylo jádro multiboot-kompatibilní, musíte do něj někam umístit multiboot hlavičku (přesněji, hlavička musí tvořit první 4KB jádra). Existuje NASM příkaz, který do kódu vloží potřebné konstanty - dd
.
dd MBOOT_HEADER_MAGIC dd MBOOT_HEADER_FLAGS dd MBOOT_CHECKSUM dd mboot dd code dd bss dd end dd start
Konstanty MBOOT_* jsme definovali na začátku.
MBOOT_HEADER_MAGIC
Magické číslo. Značí, že je jádro multiboot-kompatibilní.
MBOOT_HEADER_FLAGS
Pole příznaků. Požádáme GRUB o zarovnaní každé sekce jádra (MBOOT_PAGE_ALIGN
) a informace o paměti (MBOOT_MEM_INFO
). V některých tutoriálech je použito také MBOOT_AOUT_KLUDGE
. Jelikož my používáme formát ELF, tento hack není potřeba a jeho přidání by zabránilo GRUBu v předání tabulky symbolů při startu.
MBOOT_CHECKSUM
Slouží k detekci chyb. Součet magic number, příznaků a této hodnoty musí být nula.
mboot
Adresa struktury, kterou právě píšeme.
code,bss,end,start
Tyto symboly definuje linker. Pomocí nich předáváme GRUBu pozici jednotlivých sekcí jádra.
Při startu GRUB do registru EBX
uloží ukazatel na strukturu, ve které se nacházejí informací o prostředí, které pro nás připravil.
Zpátky ke kódu...
Bezprostředně po startu povíme CPU, aby přidal obsah EBX
na stack (v EBX
se nyní nachází ukazatel na multiboot informační strukturu), zakázal přerušení (CLI), zavolal naši main
funkci (kterou jsme zatím nedefinovali) a vstoupil do nekonečné smyčky.
To všechno je sice moc pěkné, ale kód se neslinkuje. Nemáme definovanou main()
!
Přidání trošky Céčkovského kódu
Propojování C a ASM je neuvěřitelně jednoduché. Stačí znát použitou "volací dohodu". GCC na architektuře x86 používá konvenci __cdecl
:
- Všechny parametry jsou funkci předávány přes stack.
- Parametry jsou přidávány zprava doleva.
- Návratová hodnota funkce se nachází v
EAX
.
...takže volání funkce:
d = func(a, b, c);
vypadá takto:
push [c] push [b] push [a] call func mov [d], eax
Nic na tom není! Jak můžete vidět v ASM kódu výše, to push ebx
vlastně jen předává hodnotu EBX
jako parametr funkci main()
.
C kód
// main.c -- definuje vstupní bod C kódu, provádí inicializaci
// JamesM's tutorials
int main(struct multiboot *mboot_ptr)
{
// tady budeme volat všechny inicializační funkce
return 0xDEADBABA;
}
Takhle tedy vypadá naše první funkce main()
. Jak můžete vidět, funkci je předán jeden parametr - ukazatel na multiboot strukturu. Tu si definujeme později (nepotřebujeme ji definovat k úspěšné kompilaci!).
Vše, co funkce dělá, je navrácení konstanty - 0xDEADBABA
. Ta je snad dost neobvyklá, abychom si ji všimli, až program za chvilku spustíme.
Kompilování, linkování a spuštění!
Teď, když jsme do našeho projektu přidali nový soubor, musíme ho přidat i do makefile. Tyto řádky:
SOURCES=boot.o CFLAGS=
nahraďte za:
SOURCES=boot.o main.o CFLAGS=-nostdlib -nostdinc -fno-builtin -fno-stack-protector
Musíme zastavit GCC, který se bude snažit přilinkovat linuxové C knihovny k našemu jádru - nebude to fungovat (zatím). K tomu slouží CFLAGS.
OK, teď byste měli být schopni zkompilovat, slinkovat a spustit vaše jádro!
cd src make clean # Ignorujte jakékoli chyby, které tu nastanou make cd .. ./update_image.sh ./run_bochs.sh # Možná budete vyzvání k zadání root hesla
To by mělo spustit bochs. Pár sekund uvidíte GRUB, následně se spustí samotné jádro, které vlastně neudělá nic jiného, než zamrzne s hláškou "starting up...".
Jestliže si prohlédnete bochsout.txt, vespod byste měli uvidět něco podobného:
00074621500i[CPU ] | EAX=deadbaba EBX=0002d000 ECX=0001edd0 EDX=00000001 00074621500i[CPU ] | ESP=00067ec8 EBP=00067ee0 ESI=00053c76 EDI=00053c77 00074621500i[CPU ] | IOPL=0 id vip vif ac vm rf nt of df if tf sf zf af pf cf 00074621500i[CPU ] | SEG selector base limit G D 00074621500i[CPU ] | SEG sltr(index|ti|rpl) base limit G D 00074621500i[CPU ] | CS:0008( 0001| 0| 0) 00000000 000fffff 1 1 00074621500i[CPU ] | DS:0010( 0002| 0| 0) 00000000 000fffff 1 1 00074621500i[CPU ] | SS:0010( 0002| 0| 0) 00000000 000fffff 1 1 00074621500i[CPU ] | ES:0010( 0002| 0| 0) 00000000 000fffff 1 1 00074621500i[CPU ] | FS:0010( 0002| 0| 0) 00000000 000fffff 1 1 00074621500i[CPU ] | GS:0010( 0002| 0| 0) 00000000 000fffff 1 1 00074621500i[CPU ] | EIP=00100027 (00100027) 00074621500i[CPU ] | CR0=0x00000011 CR1=0 CR2=0x00000000 00074621500i[CPU ] | CR3=0x00000000 CR4=0x00000000 00074621500i[CPU ] >> jmp .+0xfffffffe (0x00100027) : EBFE
Všimli jste si hodnoty EAX
? 0xDEADBABA
- návratová hodnota main()
. Gratuluji, už máte multiboot-kompatibilní "trampolínu", a tak vám nic nebrání začít psát na obrazovku!
Vzorový kód tutoriálu naleznete zde.