自分でOSを1から作ってみる Advent Calendar 2024の3日目です。
そもそも昨日書いたコードは何をしているのか
昨日書いたコードが何をしているのかが分からなければ、これからのことはさらに分からなくなると思いますので、説明しておきます。
昨日書いたコードはアセンブラ(NASM)で作りましたが、ブートセクタはバイナリです。バイナリというのは2進数のみで書かれているコードで、PCが直接認識できるのはこの形式のみです。実際に、昨日作ったboot.bin
はバイナリで、バイナリエディタを使えば確認することができると思います。そして、普通のテキストエディタで開いても文字化けして見ることができないと思います。
そして、このコードがなぜ実行されているのかというと、コンピュータの電源が入ると最初にBIOSが実行され、まずシステムの基本的なハードウェアをチェックし、正常に動作するか確認します。そしてその後、BIOSはブートデバイス(HDD、USB、CD-ROMなど)の先頭セクタ(最初の512バイト)を探し、この512バイトのデータがブート可能なものであるかをチェックし、ブートデバイスに有効なブートセクタが見つかると、BIOSはこの512バイトの内容をメモリの0x7C00
番地にロードします。そして、CPUの制御を0x7C00
番地に移し、このアドレスからプログラムが実行されるように設定されるからです。ちなみに先頭セクタがブート可能かどうかは最後の2バイトが0xAA55
であるかどうかで判断しています。
boot.asmのコードの説明
ここでは機能書いたコードを1から解説していますが、アセンブリがわかる人とかにとってはあまり必要ない話だと思いますので、読み飛ばしてもらっても大丈夫です。
org 0x7C00
org
というのはアセンブリの「起点アドレス」を設定する命令で、BIOSはブートローダをメモリのアドレス0x7C00
に読み込むため、ここでプログラムの起点を0x7C00
に指定します。
mov ax, 0xB800 ; テキストモードのビデオメモリのアドレス
mov es, ax
0xB800
は、テキストモードでのビデオメモリのセグメントアドレスです。
mov ax, 0xB800
で、ビデオメモリのアドレスをax
レジスタに格納します。
mov es, ax
で、このアドレスをes
(Extra Segment)に設定します。es
を使うことで、以降のコードで[es:di]
に書き込みを行うと、ビデオメモリにアクセスできます。
mov di, 0
di
はes
と組み合わせてアドレス指定に使われます。ここでは、ビデオメモリの先頭位置(左上)を示す0を設定しています。
mov si, message
si
(Source Index)にmessage
のアドレスを格納します。これにより、message
で定義された文字列のデータを1文字ずつ読み取ることができます。
print_char:
ここから、message
内の各文字を順に読み出し、ビデオメモリに書き込むループが始まります。
lodsb
lodsb
は、si
が指しているメモリから1バイトをalレジスタにロードし、si
を自動的にインクリメントする命令です。
ここでは、message
から1文字ずつal
にロードし、al
に格納された文字をビデオメモリに表示します。
cmp al, 0 ; 終端をチェック
je hang ; 終端なら停止
cmp al, 0
で、al
の内容が0かどうかを確認します。
0は文字列の終端を示す特殊な文字で、もし0ならje hang
により、hang
ラベル(無限ループ)にジャンプして表示を終了します。
mov [es:di], al
inc di
mov [es:di], al
で、al
にロードした文字をビデオメモリの現在の位置に書き込みます。
inc di
で、ビデオメモリのカーソル位置を1バイト分進めます。
mov byte [es:di], 0x07
inc di
mov byte [es:di], 0x07
で、表示する文字の色属性を指定します。0x07
は白色を意味します。
inc di
で、再度カーソル位置を進めます(1文字分の位置を確保するため、2バイト進める必要があるのでinc di
を2回使用しています)。
jmp print_char
print_char
ラベルに戻り、次の文字を表示するためにループを継続します。
hang:
jmp $
メッセージの表示が終わった後、無限ループに入ります。このループは、プログラムが終了しないようにするためのものです。
jmp $
で現在のアドレスにジャンプすることで、プログラムを停止させています。
message db "hello, world", 0
0は終端文字で、この文字が出てきたらループを終了するようにしています。
times 510-($-$$) db 0
ブートセクタは必ず512バイトである必要があり、残りのバイトを0で埋めて512バイトにしています。
($-$$)
は現在の位置からプログラムの開始位置までのバイト数を表し、510-($-$$)
で埋めるべきバイト数を計算しています。
dw 0xAA55
ブートセクタの最後の2バイトには必ず0xAA55
という署名が必要です。この署名がないと、BIOSはこのコードをブートセクタとして認識しません。