カーネルをブートするまで
OSの勉強のためにOSを実装していく事にしました。
電源投入してから何の処理もしないカーネルの処理を呼び出すところまで書いたのでまとめます。
今回作成したファイル:
- boot.nasm FDDから起動した際に最初に呼び出されるコード。loader.nasmとカーネルをメモリにコピーして、loader.nasmの処理にジャンプする。
- loader.nasm カーネルを起動するための準備をするコード。カーネルをメモリにコピーして、ジャンプする。
- kernel.c カーネル本体。中身はまだ無限ループのみ。
- common.inc 共通の関数や変数を定義します。
- Makefile Makefileです。
- lnk.ls カーネルコンパイルに使うリンカスクリプト。
実装した処理の概要はこんな感じです。
- FDの中身をメモリにコピーする (boot.nasm)
- カーネルの起動の準備をするコードにジャンプする (boot.nasm)
- PIC の割り込みを無効化する (loader.nasm)
- A20ラインを有効にする (loader.nasm)
- GDT を登録する (loader.nasm)
- IDT を登録する (loader.nasm)
- プロテクトモードになる (loader.nasm)
- パイプラインのフラッシュをする (loader.nasm)
- セグメント間ジャンプでCSレジスタにGDTのセレクタ値を設定する (loader.nasm)
- カーネル本体を1M以上の領域にコピーする (loader.nasm)
- カーネルにジャンプする (loader.nasm)
- 無限ループ (kernel.c)
動作はQEMUで確認しました。
QEMUは動作中に Ctr+Alt+2 を押すと、QEMUコマンドが入力出来ます。
QEMUのコマンドで "x/20i アドレス" を実行すると、アドレスのデータをディスアセンブルしたものが表示されます。
アドレスはベースアドレスを補正した値を指定しないとダメですね。当たり前ですが。
boot.nasm と loader.nasm はこんな感じです。
他のファイルはまとめてgithubの ここ に置かせてもらってます。
; boot.nasm [org 0] [bits 16] jmp 0x07c0:start %include "coroutine.inc" start: ; セグメントレジスタの初期化 mov ax, cs mov ds, ax mov es, ax ; とりあえず文字列表示する mov si, bgnMsg call print resetFdc: mov ax, 0 mov dl, 0 ; Aドライブを指定して int 0x13 ; FDコントローラをリセットする jc resetFdc ; エラーが出たらやり直し mov ax, loaderHead ; loaderHeadに読み込む mov es, ax ; ES = loaderHead mov bx, 0x0 ; ES:BX = loaderHead:0000 mov ah, 0x02 ; ES:BX を指定して mov ch, 0 ; シリンダ0 mov al, 1 ; 1セクタ分 mov cl, 0x02 ; 読み込み開始セクタ2 mov dh, 0 ; ヘッド0 mov dl, 0 ; ドライブA loopHead: ; FDを読み込む mov ah, 0x02 ; ES:BX を指定して mov al, 1 ; 1セクタ分 mov dl, 0 ; ドライブA int 0x13 ; FDD読み込み ; 次のセクターを読むために変数を更新する mov ax, es add ax, 0x20 ; 512byte読み込むにあたり、リアルモードのアドレス指定だと +0x20 mov es, ax add cl, 0x1 cmp cl, 19 ; セクターが1~18番まで jb loopHead mov cl, 1 ; 必要になるまでコピーしない ;inc dh ;cmp dh, 2 ; ヘッドは0 or 1なので ;jb loopHead ;mov dh, 0 ;inc ch ;cmp ch, 80 ; シリンダは各面に0~79番まである ;jb loopHead loopEnd: jmp loaderHead:0 bgnMsg db 'BOOT ', 0x0 times 510 - ($ - $$) db 0 dw 0xaa55
; loader.nasm [org 0] [bits 16] jmp short start %include "coroutine.inc" start: mov ax, cs mov ds, ax mov es, ax xor ax, ax mov ss, ax mov si, kmsg call print ; AT互換機はCLIを呼ぶ前にこれをやらないといけないらしい ; と、30日でOS作る本に書いてあった mov al, 0xff out 0x21, al nop out 0xa1, al ; PIC の割り込みを無効化する cli ; A20ラインを有効にする call enableA20 ; GDT を登録する lgdt [gdtr] ; IDT を登録する lidt [idtr] ; プロテクトモードになる mov eax, cr0 or eax, 1 mov cr0, eax ; パイプラインのフラッシュをする jmp pipeFlush pipeFlush: ; セグメント間ジャンプでCSレジスタにGDTのセレクタ値を設定する jmp dword codeSgmntSlctr:prtctModeBegin prtctModeBegin: [bits 32] mov bx, dataSgmntSlctr mov ds, bx mov es, bx mov fs, bx mov gs, bx mov ss, bx mov esi, 0x0 kernelCopy: mov eax, kernelFrom add eax, esi mov ebx, kernelTo add ebx, esi mov ecx, [eax] mov [ebx], ecx add esi, 0x2 cmp esi, kernelSize jbe kernelCopy jmp dword codeSgmntSlctr:kernelTo [bits 16] ;<defun enableA20> ; A20 ラインを有効にする enableA20: call kbdclr mov al, 0xd1 out 0x64, al ; これからコマンド送信をすると8042に伝える call kbdclr mov al, 0xdf out 0x60, al ; A20ラインを有効にする call kbdclr mov al, 0xff out 0x64, al call kbdclr ret ;<defun kbdclr> ;キーボードコントローラ8042の状態ポート 0x64 と入力バッファ0x60 を空にする kbdclr: waitkbdin: ; 8042の入力バッファが空になるまで読む in al, 0x60 ; 0x60 を読む test al, 0x2 ; バッファが空? jnz waitkbdin waitkbdout: ; 8042の状態ポートが空になるまで読みまくる in al, 0x64 ; 0x64を読み込む test al, 0x2 ; バッファが空? jnz waitkbdout ret ;</defun kbdclr> ;</defun enableA20> kmsg db 'LOAD ', 0x0 ; IDT idtr dw 0 ; IDTのサイズ dw 0, 0 ; IDTのアドレス ; GDT gdtr: dw gdtEnd - gdt - 1 ; GDT全体のサイズ dd gdt + loaderHead * 0x10 ; GDTのアドレス gdt: dw 0, 0, 0, 0 ; NULLセレクタ codeSgmntSlctr equ 0x08 dw 0xffff ; リミット 0xffff dw 0x0 ; baseaddress 0~15 db 0x01 ; baseaddress 16~23 db 0x9a ; P:1 DPL:0 S:1 TYPE:10 Code non-conforming readable db 0xcf ; G:1, D:1, limit 16~19: 0xf db 0x0 ; baseaddress 24~31: 0x0 ; base = 0x00010000 dataSgmntSlctr equ 0x10 dw 0xffff ; リミット 0xffff dw 0x0 ; baseaddress 0~15 db 0x01 ; baseaddress 16~23 db 0x92 ; P:1 DPL:0 S:1 TYPE: Data, expand-up, writable db 0xcf ; G:1 D:1 limit 16~19: 0xf db 0x0 ; baseaddress 24~31: 0x0 ; base = 0x00010000 gdtEnd: times 512 - ($ -$$) db 0
参考図書:
30日でできる! OS自作入門
はじめて読む486
作りながら学ぶOSカーネル
アセンブリ言語の教科書
Binary Hacks
まるまるC MAGAZINE 2004年度版より7月号記事 オリジナルOSを作ろう!