Kesäloman viimeisen viikon kunniaksi päätin koodata oman käyttöjärjestelmän. En niinkään tehdäkseni omaa Linux-kloonia, vaan kokeillakseni millaista nykyään on ohjelmoida raakaa x86-arkkitehtuurin PC:tä.
Oman käyttöjärjestelmän tekeminen on yllättävän helppoa, jos osaa koodata hieman assemblerilla. PC:t käynnistyvät nykyään USB-tikulta ja vielä kätevämpää on tehdä kokeiluja esimerkiksi VirtualBoxissa. Debuggaamisessa toimii hyvin Bochs, jonka logeista näkee heti, mikä meni vikaan ja mitä prosessorin rekistereissä oli silloin.
Assembly-koodaaminen onnistuu kätevimmin Linuxilla tai Mac OS X:llä, jonka mukana tulee NASM (Netwide Assembler) sekä GCC ja muut tarvikkeet. Yksinkertaisen MBR-bootloaderin voi tehdä pelkästään NASMia käyttäen (esimerkki alempana).
BIOS ja koneen käynnistyminen
Tavallisessa PC:ssä BIOSin asetuksista voi valita, miltä massamuistilta käyttöjärjestelmä ladataan. Ennen vanhaan oli kätevintä käyttää 1.44MB korppuasemaa kokeilemiseen, mutta nykyään USB-tikku on paras optio.
Kun kone käynnistyy, BIOS lataa USB-tikulta ensimmäisen blokin (512 tavua) muistiosoitteeseen 0x07c0:0000. Tätä blokkia kutsutaan MBR:ksi (Master Boot Record). Tavallisesti siihen mahtuu 440 tavua konekielistä ohjelmakoodia, jota seuraa partitiotaulu. Jos ei tarvitse partitioita, ohjelmakoodi voi olla 510 tavua pitkä.
BIOS tarkistaa, että muistiin ladattu MBR-blokki päättyy maagisiin tavuihin 0xaa55. Sitten se vain hyppää osoitteeseen 0x07c0:0000, jolloin kontrolli siirtyy omalle ohjelmakoodille. Tässä vaiheessa x86-prosessori on Real Modessa, eli rekisterit ovat 16-bittisiä, segmenttirekisterit toimivat perinteiseen tapaan ja muistin maksimimäärä on 1 megatavu.
Bootloaderin ohjelmointi
Tässä on esimerkki bootloaderista, joka tulostaa vain ruudulle "Loading..." ja pysäyttää sitten prosessorin:
; NASM MBR boot loader by Kenneth Falck 2009
[bits 16]                               ; 16-bit code
[org 0x7c00]                            ; BIOS loads us at 0x07c0:0000
jmp 0x0000:initialize_bios              ; reset code segment to 0x0000 with long jump
initialize_bios:
xor ax, ax
mov ds, ax                      ; reset data segments to 0x0000
mov es, ax
mov [bootdrive], dl             ; store boot drive
mov si, welcome                 ; print welcome string
call print
;jmp load_kernel_header         ; proceed to load kernel
halt:
hlt                             ; halt CPU to save power
jmp halt                        ; loop if halt interrupted
print:                                  ; Print string in SI with bios
mov al, [si]
inc si
or al, al
jz exit_function                ; end at NUL
mov ah, 0x0e                    ; op 0x0e
mov bh, 0x00                    ; page number
mov bl, 0x07                    ; color
int 0x10                        ; INT 10 - BIOS print char
jmp print
exit_function:
ret
data:
welcome db 'Loading…', 0      ; welcome message
error db 'Error', 0             ; error message
bootdrive db 0x00               ; original BIOS boot drive
times 510 - ($ - $$) db 0               ; filler to 510 bytes
dw 0xaa55                               ; boot signature (fills to 512 bytes)
Olettaen että koodinpätkä on tallennettu nimellä bootloader.asm, sen voi kääntää binääri-imageksi komentamalla:
$ nasm -f bin -o bootloader.img bootloader.asm
Käynnistäminen USB-tikulta
Syntynyt 512 tavun mittainen bootloader.img voidaan kirjoittaa suoraan USB-tikulle. Sitä ei siis kopioida tikulla olevaan FAT16-tiedostojärjestelmään tiedostoksi, vaan tikun koko sisältö ylikirjoitetaan raa'asti. (HUOM: tikun aiempi sisältö tuhoutuu.) Olettaen että USB-tikun laitenimi on /dev/diskX, komento kuuluu:
$ dd if=bootloader.img of=/dev/diskX
$ diskutil eject /dev/diskX # Mac OS X
Nyt tikun voi kytkeä mihin tahansa PC:hen ja määritellä BIOSista koneen käynnistymään USB-tikulta.
Käynnistäminen VirtualBoxissa
VirtualBox ei osaa käynnistyä USB-tikulta, joten siinä on helpointa tehdä MBR-imagesta virtuaalinen kovalevy. Tämä onnistuu seuraavalla komennolla (*):
$ VBoxManage convertfromraw -format VDI bootloader.img bootloader.vdi
Lopuksi luodaan uusi virtuaalikone ja määritellään sille tavallinen IDE-kovalevy, jonka sisältö tulee tiedostosta bootloader.vdi. VBoxManage-komennolla voi skriptata kovalevyn päivittymään automaattisesti siten, että aina kun bootloaderista käännetään uusi versio, VirtualBox generoi ja ottaa käyttöön uuden imagen.
(*) Jos VBoxManage valittelee, että bootloader.img on liian pieni, sen perään voi lisätä vaikkapa megatavun nollia /dev/zerosta. Näillä ei ole vaikutusta, koska BIOS kuitenkin lataa muistiin vain ensimmäiset 512 tavua.
Seuraavassa osassa Protected Mode ja A20-linjan aktivointi.