OS開発

x86-64(EM64, AMD64)アーキテクチャ向けのOSを開発していく上で集めた情報をまとめます.

Custom Search

BIOS・リアルモード

1.2. フロッピーディスク・ハードディスクからの起動

前節で説明したように,BIOSはブートドライブ(フロッピーディスクやハードディスク)の先頭1セクタ(512バイト)を読み込み,最後の2バイトのシグニチャが0x55 0xAAであれば,正しいMBRと判定します.MBRは,メモリの0x7c00--0x7dff番地に読み込まれ,プロセッサは%cs=0%ip=0x7c00として,MBRの先頭からプログラムをリアルモードで実行します.このMBRに含まれるプログラムは,最初に実行されるプログラムであることから,イニシャルプログラムローダ(IPL: Initial Program Loader)と呼ばれています.本節では,メッセージを表示してCPUを停止状態にするIPLを実装します.

実装に入る前に,ブートドライブとメモリのマッピングについてまとめておきます.

ブートドライブの構成

本節ではブートドライブの先頭512バイトのみを使用します.シグニチャを除いた先頭510バイトをIPLに利用します.

開始アドレス 終了アドレス サイズ 用途
0000 0000 0000 01fd 510B IPL
0000 01fe 0000 01ff 2B MBR signature (0x55 0xaa)
0001 1200 -- 空き:フロッピーの場合0x00167fffまで

メモリのマッピング

次にメモリの使い方ですが,MBRは前述の通り,0x7c00--0x7dff番地に読み込まれます.この他にサブルーチン呼び出しやレジスタの値の一時保存などに使用するスタック領域を確保する必要があります. Memory_Map (x86) [OSDev WiKi] によると,0x500から0x7bffまではRAMとして使えるので,ここをIPLのスタック領域として使用することにします.

開始アドレス 終了アドレス サイズ 用途
0000 04ff 1280B 利用不可
0500 7bff ~30KiB スタック
7c00 7dff 512B MBR
7e00 -- 予約(利用不可)・空き

実装

以下のソースコードはメッセージを表示して,CPUを停止状態にするだけのシンプルなIPLです.詳しい内容はコメントに書いてありますが,内容は非常にシンプルで,スタックの設定,セグメントレジスタの初期化,ブートドライブ番号の保存(今回は保存した値は使われませんが,この値はブートモニタを読み出す際に使用する予定です.),BIOSのサービス割り込みを利用したメッセージの表示です.BIOSはその名前 (Basic Input/Output System)の通り,ディスプレイへの文字の表示やドライブからのデータ読み込みなどの基本的な操作をサービスルーチンとして提供していて,プログラマは割り込みを通じてこの機能を使うことができます.ただし,BIOSのサービスルーチンはリアルモードからしか呼べませんので,プロテクテッドモードに移行後は,適切なデバイスドライバを実装してアクセスする必要があります.

        .file   "diskboot.s"

/* Text セクション */
        .text

        .code16                 /* 16ビットリアルモード */
        .globl  start           /* エントリーポイント */

start:
/* スタックとセグメントレジスタの設定 */
        cld                     /* Clear direction flag */
                                /* (ストリング操作命令でdi/siをインクリメントする) */
        xorw    %ax,%ax         /* %ax = 0 */
/* スタックの設定 */
        movw    %ax,%ss
        movw    $start,%sp
/* データセグメントの設定(%ds=0, %es=0) */
        movw    %ax,%ds
        movw    %ax,%es
/* BIOSの起動ドライブを保存 */
        movb    %dl,drive
/* Welcomeメッセージの表示 */
        movw    $msg_welcome,%si        /* %ds:(%si) -> Welcomeメッセージ */
        call    putstr

/* CPUの停止 */
halt:
        hlt
        jmp     halt

/* Null-terminated文字の表示 */
putstr:
        pushw   %bx
putstr.load:
        lodsb                   /* %ds:(%si)から%alに1バイト読み込み,%siをインクリメントする */
        testb   %al,%al         /* Null文字か判定する */
        jnz     putstr.putc     /* Null文字でなければこれを表示する */
        popw    %bx
        ret                     /* Null文字ならreturn */
putstr.putc:
        call    putc            /* 文字%alを表示する */
        jmp     putstr.load     /* 次の文字へ */
putc:
        movw    $0x7,%bx        /* %bh: ページ番号(テキストモードのとき) */
                                /* %bl: カラーコード(グラフィックスモードのとき) */
        movb    $0xe,%ah        /* BIOSのint $0x10に対し,TTY文字出力命令(0xe)を指定 */
        int     $0x10           /* BIOSを呼び出し%alの文字を表示する */
        ret


/* Dataセクション */
        .data

/* ブートドライブの保存先 */
drive:
        .byte   0

/* メッセージ */
msg_welcome:
        .asciz  "Welcome!\r\n\nLet's get it started.\r\n\n"

コンパイル・実行

まずアセンブラコードをコンパイルしてオブジェクトファイルにします.

$ as -nostdlib -o diskboot.o diskboot.s

次に,リンカを用いて,オブジェクトファイルからバイナリファイルに変換しています. -Ttext=0x7c00を指定することで,.textセクションが0x7c00にロードされることを指定しています.これを指定しないと,$startなどのラベルの値が違う値になってしまいます.ラベル参照は絶対値が入るので.-Tdata=0x7d00のようにデータセクションのアドレスも指定できますが,指定しない場合,テキストセクションの直後に置かれるので指定しなくて良いです.余談ですが,NASMではこのアドレス指定をORG命令を使っています.gasの.org命令は全く別の命令ですので,gasを使う場合は,リンカでこのアドレスを指定する必要があります.

$ ld -N -e start -Ttext=0x7c00 --oformat binary -o diskboot diskboot.o

最後に,バイナリファイルをフロッピーディスクのサイズに変換して,ディスクイメージにしています.

$ truncate -s 1474560 diskboot
$ mv diskboot myos.img

ブートモニタの実装と読み込み(執筆中)

MBRの最後の66バイトには,パーティションテーブル(64バイト)とシグニチャ(2バイト)が含まれるため, IPLには,446バイトのプログラムしか入らないので, IPLからブートモニタと呼ばれる大きめの起動プログラムを読み込み, そのブートモニタからカーネルプログラムを読み込みます.

この時点でのブートドライブの構成は

開始アドレス 終了アドレス サイズ 用途
0000 0000 0000 01bd 446B IPL
0000 01be 0000 01fd 64B Partition table (4 partitions)
0000 01fe 0000 01ff 2B MBR signature (0x55 0xaa)
0000 0200 0000 11ff 4KiB ブートモニタ
0000 1200 0001 11ff 64KiB カーネル
0001 1200 0016 7fff ~1.34MiB (空き)

開始アドレス 終了アドレス サイズ 用途
0500 7bff ~30KiB スタック(%ss=0,%sp=7c00)
7c00 7dff 512B MBR
0000 7e00 0000 7fff 512B 空き
0000 8000 0000 8fff 4KiB 起動情報
0000 9000 0000 9fff 4KiB ブートモニタ(MBRから読み込み)
0000 a000 0000 ffff 24KiB (空き)
0001 0000 0001 ffff 64KiB カーネル(ブートモニタから読み込み)

まずは,ブートローダを書きます. ブートローダはMBR内のイニシャルプログラムローダ(IPL: Initial Program Loader)とMBRから読み込むブートモニタの2つのプログラムから構成されます. IPLには510バイト(+2バイトのシグニチャ)のプログラムしか入りません.このプログラムはイニシャルプログラムローダ(IPL: Initial Program Loader)とも呼ばれています.そのため,MBRからブートモニタを読み, そのブートモニタからカーネルプログラムを読み込みます. CDには対応していませんが, ハードディスクでもフロッピーでもいけます. フロッピーディスクにしている理由はVirtualBoxでハードディスクを 扱おうとすると,毎回qemu-convertでvdi形式に変更する必要があるためです.

起動時は%cs=0.

int $0x10はBIOSを呼び出す割り込み命令で,%ahレジスタの値によって実行される内容が変わります.今回は%ah=0xeを指定していて,これは%bhで指定されたページ番号に%blで指定されたカラーモードで1文字追加書き込みをします.ここでは%bxに0x0007を指定することで,ページ番号=0,カラーモード=7となります.その他の%ahレジスタの値と実行内容は, Wikipedia:INT 10H を参照してください.

BIOSから取得できる情報

システムメモリアドレスマップ int 0x15 eax=0xe820

補足:MBRとパーティション

本ページでは,MBRにはシグニチャを除いて510バイトのIPLを格納できると書きましたが,一般的にはMBRのシグニチャの前にはパーティションテーブル(64バイト=16バイトx4)が含まれるため,IPLは446バイト以下にする必要があります.

パーティションに関連した話では,MBRパーティションではパーティションテーブルで指定されるセクタ番号が4バイトで表されるため,512バイトのセクタ長のデバイスでは,2TiBの領域しか管理できません.近年ではGUIDパーティションテーブル(GPT: GUID Partition Table)も使用されているが,GPTも互換性のため,先頭にMBRを保持している.

なお,現時点ではパーティションは使用しないので,この差は気にしなくて良い.