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.

Published 19.8.2009