カーネル制作体験
低レベル層に興味がある人にはインテルの、ここ(日本語資料)とか ここ(英語資料)とかにあるソフトウェア開マニュアルは非常に面白い読み物だと思う。でも実際に試してみないと分かった気がしないなーと言うことでカーネルを作ってみることにした。
とりあえず最小限の何の役にも立たないような物でいい。ややこしげなメモリアクセス、割り込み、特権レベル、タスクスイッチ、ページングとかをごくごく小規模で気軽にいじくり回せるようなのが良い。
作りながら学ぶOSカーネル保護モードプログラミングの基本と実践
- 作者: 金凡峻
- 出版社/メーカー: 秀和システム
- 発売日: 2009/04/15
- メディア: 単行本
- 購入: 7人 クリック: 89回
- この商品を含むブログ (14件) を見る
我々の使っているPC(x86)はだいたいこんな感じで起動する。
電源オン → ブートセクタをメモリの0x7C00に読み込む → 0x7C00へ制御を移す → カーネルをメモリに読み込む → カーネルへ制御を移す → その他もろもろ → 起動完了
ブートセクタというのはディスクの先頭セクタ(512byte)のことで、起動ディスクのここにコードを入れて置くとBIOSがそれをメモリの0x7C00に置いて実行してくれる。このコードが起動プロセスの中で最初に実行できる、自分たちで書けるコードということになる。普通はブートローダーと呼ばれるカーネールをロードするためのプログラムが入っている。
とりあえず、BIOSが勝手に読み込んでくれる部分をブートーローダ、ブートローダの処理でメモリにロードする部分をカーネルと見立ててハローワールド的なものを作ってみる。
■環境
アセンブラ: NASM 2.07
エミュレータ: qemu 0.12.2
ソース(bootsect.s)
01: ; bootsect.s 02: bits 16 03: org 0x0 04: jmp 0x07C0:start ; cs=0x07C0 05: start: 06: mov ax, cs 07: mov ds, ax ; ds=cs=0x07C0 08: mov si, msg 09: putc: 10: lodsb ; load a byte at "si" into "al", then inc "si" 11: cmp al, 0x00 12: jz load_kernel 13: mov ah, 0x0E ; print ascii 14: mov bl, 0x07 ; set color white 15: int 0x10 ; display service 16: jmp putc 17: 18: load_kernel: 19: mov ax, 0x1000 20: mov es, ax 21: mov bx, 0 ; dest is [es:bx] 22: mov ah, 2 ; read from disk 23: mov al, 1 ; disk num 24: mov ch, 0 ; cylinder 25: mov cl, 2 ; sector 2 26: mov dh, 0 ; head num 27: mov dl, 0 ; drive num 28: int 0x13 ; disk service 29: 30: jmp 0x1000:0 ; jump into the kernel 31: 32: msg: 33: db 'loading kernel image...', 0x0A, 0x0D, 0x00 34: padding: 35: times 510-($-$$) db 0x00 36: dw 0xAA55
IA32アーキテクチャではメモリアクセスは強制的にセグメント機構を経由することになる。セグメント未指定でデータへアクセスすると自動的にdsが指すセグメントを指定したことになる。なので、コードセグメント(cs)とデータセグメント(ds)は同じにしておいた方がコーディングが楽になる。
9行目〜10行目辺りはBIOSのビデオサービスを利用して文字をコンソールへ出力している。
18行目〜28行目でBIOSのディスクサービスを利用して、2番目のセクタの内容を0x1000:0に読み込んでいる。0x1000:0と言うのは <セグメント>:<実効アドレス> という形のアドレス指定。リアルアドレスモードではセグメント値は単に4ビット左シフトされて実効アドレスと足し合わすことでリニアアドレスを算出する。ページングを利用していない場合はリニアアドレス=物理アドレスとなるので 0x1000:0 は物理アドレス 0x10000 のこと。
36行目は、ブートセクタの最後の2バイトは 0x55, 0xAAでなければならないと言う決まりに従ったもの。
ソース(kernel.s)
01: ; kernel.s 02: bits 16 03: org 0x0 04: jmp 0x01000:start 05: start: 06: mov ax, cs 07: mov ds, ax 08: mov si, msg 09: putc: 10: lodsb ; load a byte at "si" into "al", then inc "si" 11: cmp al, 0x00 12: jz exit 13: mov ah, 0x0E ; print ascii 14: mov bl, 0x07 ; set color white 15: int 10h ; display access 16: jmp putc 17: exit: 18: jmp $ ; infinite loop 19: 20: msg: 21: db 'I am the Kernl!', 0x0A, 0x0D, 0x00 22: padding: 23: times 512-($-$$) db 0x00
やってることはbootsect.sとほとんど同じ。というか少ない。カーネルだよーと言う文字を出すだけ。上で紹介した本よりもずっと手抜き。
コンパイル&イメージ作成
$ nasm -f bin -o bootsect bootsect.s $ nasm -f bin -o kernel kernel.s $ cat bootsect kernel > floppy.img
qemuで実行
$ qemu -fda floppy.img -boot a
お手本のまんまじゃなくて、いろいろいじくって(手抜きして)たもんやから結構無限ループとかになって、実は手間がかかってたんです(^^;