やっつけIPL
512byteより大きなプログラムを実行してみよう。
前回は小さなプログラムを書いて,画面に文字を表示することが出来た。AT互換機はディスクの最初のセクタ,つまり512byteを読み込んで実行してくれる分けだから,ただディスクの先頭セクタにそのプログラムを書き込めばよかった。さて512byte以上のプログラムを実行させるにはどうすればいいだろうか?
まず大きなプログラムを先頭セクタ以外の場所に書き込んでおいて,先頭セクタに,ディスクからそのプログラムを読み込むプログラムを用意してやればいい。
このプログラムはInitial Program Loader (IPL)と呼ばれる。
アセンブラを使っているとわけもわからず泣けてくるので,
私たちは可能な限り高級言語を使用してプログラムを書いていきたい。
しかし,512byteに理想のプログラムを詰め込むには,高級言語が吐き出すバイナリでは大きすぎるようなので,ここではアセンブラで記述する。
コードは次の通り:
ipl.S
#define IPLBASE 0x7c00 #define IPLSTACK 0xffff #define ROOTDIR 0x7e00 #define KERBASE 0x4000 #define PUTS(x) mov si, offset x; call putstr #define WORD word ptr #define BYTE byte ptr .text .code16gcc // 16bit-realmode .intel_syntax noprefix // I hate AT&T-style .global _start _start: jmp begin nop OEM_name: .ascii "OS " // OEM name sect_size: .word 512 // Bytes per sector culst_size: .byte 1 // Sector per cluster rsvd_sects: .word 1 fat_cont: .byte 2 // number of FATs root_ents: .word 224 // max root enstrys total_sect: .word 2880 media_type: .byte 0xf0 fat_size: .word 9 trk_size: .word 18 head_cont: .word 2 hidd_sect: .long 0 huge_sect: .long 0 boot_dev: .byte 0 reserved: .byte 0 signature: .byte 0x29 vol_ID: .long 0xffffffff vol_label: .ascii "DISK " fat_name: .ascii "FAT12 " .space 18 begin: // code located at 0000:7C00, CS=0 and IP=0x7c00. cli // Disable interrupts xor ax, ax // Set up needed data segment reg mov ds, ax // CS = DS = 0 mov es, ax // ES = 0 mov ss, ax // Set up stack mov esp, IPLSTACK // stack 0x0000:0xffff sti mov [boot_dev], dl // save boot dev PUTS(msg_start) // start msg // reset disk mov dl, [boot_dev] xor ax, ax int 0x13 PUTS(msg_load_dir) // CX = size of root directory xor dx, dx mov ax, 32 // 32 byte directory entry mul WORD [root_ents] // total size of directory div WORD [sect_size] // size in sectors mov cx, ax mov [root_size], cx // save root dir size // AX = location of root directory movzx ax, BYTE [fat_cont] // number of FATs mul WORD [fat_size] // size in sectors add ax, WORD [rsvd_sects] // adjust for bootsector mov [root_base], ax // save root dir base // ES:BX = into memory of root directry mov bx, ROOTDIR // load root directry call readsectors // search file mov cx, WORD [root_ents] // load loop counter mov di, ROOTDIR // locate first root entry search_loop: push cx mov cx, 11 // check 11byte mov si, offset file_name // file name push di rep cmpsb // check file name pop di je load_file // found! pop cx // step dec cx add di, 32 loop search_loop // next entry PUTS(msg_not_found) // not found jmp loop load_file: PUTS(msg_load_file) // CX = set file size mov ax, [di + 0x1C] // file size in byte xor dx, dx div WORD [sect_size] inc ax // size in sectors mov cx, ax // CX = sectors count // AX = set file head mov ax, [di + 0x1A] // file head cluster sub ax, 0x02 movzx bx, BYTE [culst_size] mul bx // sector number add ax, WORD [root_size] // add data base add ax, WORD [root_base] // AX = sector head // ES:BX = location mov bx, KERBASE // load file call readsectors real2prot: cli // disable intrreput lgdt gdtr // enable GDT // Enable A20 mov ax, 0x2401 int 0x15 // Enable protected mode mov eax, cr0 or eax, 1 mov cr0, eax // Reload CS, flush pileline jmp 0x08, offset code32 code32: .code32 // set segments mov eax, 0x10 mov ds, eax mov es, eax mov fs, eax mov gs, eax mov ss, eax //mov esp, 0x90000 // jmp c code jmp 0x08, KERBASE .code16gcc loop: jmp loop // read sectors // AX = sorce sector head. cx = sector count. // ES:BX = into memory location. readsectors: nextsect: push ax push bx push cx call readsect PUTS(msg_prog) pop cx pop bx pop ax // step inc ax add bx, WORD [sect_size] loop nextsect // read ok PUTS(msg_ok) ret // read sector via BIOS // params: AX = sorce sector head. ES:BX = into memory loation. readsect: xor dx, dx // prepare dx:ax for operation div WORD [trk_size] // AL = tracks. mov cl, dl inc cl // CL = sector xor dx, dx // prepare dx:ax for operation div WORD [head_cont] mov ch, al // CH = sylinder mov dh, dl // DH = head mov dl, [boot_dev] // DL = disk mov ax, 0x0201 // read per 1 sect int 0x13 ret // purint string via BIOS // params: SI = address of string. putstr: mov ah, 0x0E mov bx, 0x0007 put_loop: lodsb or al, al jz put_end int 0x10 jmp put_loop put_end: ret // data msg_start: .string "IPL:\r\n" msg_load_dir: .string "load root dir" msg_load_file: .string "load file" msg_not_found: .string "not found" msg_prog: .string "." msg_ok: .string "ok!\r\n" file_name: .ascii "KERNEL BIN" root_size: .word 0 root_base: .word 0 .align 8 gdt: // GDT .word 0,0,0,0 // 0x00:null .word 0xFFFF,0,0x9800,0x00fd // 0x08:flat code .word 0xFFFF,0,0x9200,0x00fd // 0x10:flat data gdtr: .word . - gdt - 1 .long gdt // boot signetur .org 510 .word 0xaa55
解説
このプログラムは,FAT16ファイルシステムでフォーマットされたディスクから,目的のファイル名がつけられたプログラムを探し出し,メモリへ読み込み実行する。ついでにプロテクトモードの移行もしている。
私たちはOSのカーネルの開発に興味があるわけだから,IPLの作成に専念するのは面白く無い。しかし,OSの起動プロセスを理解するためにいい機会だと思うので,非常に単純なIPLを作ることにした。単純だが一つのファイルで出来たOSを起動するには十分で,しばらくはこのIPLを使う。
OSの作成の進行の必要に応じて,高機能なGBUBといったIPLに乗り換えることにする。
コメントを十分に付けたので,コメントを見ればだいたいの動作が解ると思うので,ここでは詳しくコードについては述べない。下記に示すURLも非常に参考になる。
プロテクトモードなど重要な点については後で解説することにする。