自作OSに挑戦する日記 2日目
「30日でできる!OS自作入門」を読んで分かったことや、とりあえず書いておきたいことなどを書いていきます。
この本はChapterが1から30まであるので、各チャプター毎に1記事書いていきます。
Chapter 2 「アセンブラ学習とMakefile入門」
今回やった内容
環境
- メインPC
- MacBook Pro (13-inch, 2017, Four Thunderbolt 3 Ports)
- macOS Mojave 10.14
- 自作OSを動作させるPC
- Core 2 Duo
- 4GB RAM
- (ハードオフのジャンクコーナにいた子)
作業記録
前半は作業記録というより、学習ノートみたいなものです。後半から作業記録っぽくなります。
まず、アセンブリ言語について勉強します。
アセンブリ言語の命令
ORG (origin)
- OSがPCメモリのどこに配置されるかを指定する命令。
JMP (jump)
MOV (move)
- 代入命令。これが分かればアセンブリ言語の半分が理解できるらしい(!)。
MOV SS, AX
とあれば、SSにAXを代入するという処理を表す。- []を使うことで、メモリのアドレスを指定することができる。メモリに書き込む際にはBYTE(1バイト)やWORD(2バイト)などを付けてデータの大きさを表現してあげる必要がある。
ADD (add)
- 足し算を行う命令。
ADD SI, 1
のように書くと、SIレジスタの1を足すという処理を表す。
- 足し算を行う命令。
CMP (compare)
- 比較命令。
CMP AL, 0
のように書くと、ALレジスタの中身と0が等しいかを判断してくれる。 - 条件分岐を行うためには、CMPと次のJEを続けて書かなければいけない。(ちょっとめんどくさい)
- 比較命令。
JE (jump if equal)
- 比較した結果が真であるなら、指定ラベルにジャンプする。
- 下のように書くと、ALレジスタが0だった場合にfinラベルへ飛ぶ、というような処理を表す。
CMP AL, 0 JE fin
- INT (interrupt)
- ソフトウェア割り込み命令。
- BIOSの関数を呼ぶことができる。
- 割り込みについては、また詳しく説明があるみたいなので大体の理解でとどめておく。
BIOSは入出力などの関数の集まりです。その中には0x10
という関数があり、
という感じでレジスタに値をセットして0x10
関数をINT命令で呼び出すと、ALレジスタにセットされたキャラクターコードが1文字画面に表示されるのらしいです!!すごい!!!!
昨日のChapter1で書いた訳の分からないコードは、「hello, world」を1文字ずつ0x10
関数で表示するという処理を書いていた訳ですね!!この習ったことが全て繋がる瞬間が気持ちよすぎて1人で「すげぇ…最高…」って言ってました。
…ただ、まだ理屈を知っただけなので、実際にやってみます。実際にやってみるのが大事。
やった!!!!ちゃんと動きました!!!
あ、コードも貼っておきます。そういえば、昨日は全く分からなかったコードが読めるようになっています。自分すごいぞ。
; hello-os ORG 0x7c00 ; このプログラムがどこに読み込まれるのか ; FAT12ディスクのための記述 JMP entry DB 0x90 DB "HELLOIPL" ; ブートセクタの名前を自由に書いてよい(8バイト) DW 512 ; 1セクタの大きさ(512にしなければいけない) DB 1 ; クラスタの大きさ(1セクタにしなければいけない) DW 1 ; FATがどこから始まるか(普通は1セクタ目からにする) DB 2 ; FATの個数(2にしなければいけない) DW 224 ; ルートディレクトリ領域の大きさ(普通は224エントリにする) DW 2880 ; このドライブの大きさ(2880セクタにしなければいけない) DB 0xf0 ; メディアのタイプ(0xf0にしなければいけない) DW 9 ; FAT領域の長さ(9セクタにしなければいけない) DW 18 ; 1トラックにいくつのセクタがあるか(18にしなければいけない) DW 2 ; ヘッドの数(2にしなければいけない) DD 0 ; パーティションを使ってないのでここは必ず0 DD 2880 ; このドライブ大きさをもう一度書く DB 0,0,0x29 ; よくわからないけどこの値にしておくといいらしい DD 0xffffffff ; たぶんボリュームシリアル番号 DB "HELLO-OS " ; ディスクの名前(11バイト) DB "FAT12 " ; フォーマットの名前(8バイト) RESB 18 ; とりあえず18バイトあけておく ; プログラム本体 entry: MOV AX,0 ; レジスタ初期化 MOV SS,AX MOV SP,0x7c00 MOV DS,AX MOV ES,AX MOV SI,msg putloop: MOV AL,[SI] ADD SI,1 ; SIに1を足す CMP AL,0 JE fin MOV AH,0x0e ; 一文字表示ファンクション MOV BX,15 ; カラーコード INT 0x10 ; ビデオBIOS呼び出し JMP putloop fin: HLT ; 何かあるまでCPUを停止させる JMP fin ; 無限ループ msg: DB 0x0a, 0x0a ; 改行を2つ DB "hello, world" DB 0x0a ; 改行 DB 0 RESB 0x7dfe-($-$$)-0x7c00 ; 0x7dfeまでを0x00で埋める命令 DB 0x55, 0xaa ; 以下はブートセクタ以外の部分の記述 DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00 RESB 4600 DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00 RESB 1469432
注意するところは、RESB 0x7dfe-($-$$)-0x7c00
の部分です。今回は、OSをメモリに展開する先頭を0x7x00に設定してあるため、その分を引いてあげる必要があるみたいです。
本によるとこれからOSを作っていく時に、ブートセクタの部分は変えたりするみたいなのですが、残りの部分は編集する必要がないため、分割してコンパイルする形式を取るみたいです。
例によって、著者の方の自作ツールはmacで動作しないので自分で書きます。やる事としては、ブートセクタのバイナリファイルに「; 以下はブートセクタ以外の部分の記述」以降を引っ付けてあげれば良いみたいなので
// boot_sector.asm ; hello-os ORG 0x7c00 ; このプログラムがどこに読み込まれるのか ; FAT12ディスクのための記述 JMP entry DB 0x90 DB "HELLOIPL" ; ブートセクタの名前を自由に書いてよい(8バイト) DW 512 ; 1セクタの大きさ(512にしなければいけない) DB 1 ; クラスタの大きさ(1セクタにしなければいけない) DW 1 ; FATがどこから始まるか(普通は1セクタ目からにする) DB 2 ; FATの個数(2にしなければいけない) DW 224 ; ルートディレクトリ領域の大きさ(普通は224エントリにする) DW 2880 ; このドライブの大きさ(2880セクタにしなければいけない) DB 0xf0 ; メディアのタイプ(0xf0にしなければいけない) DW 9 ; FAT領域の長さ(9セクタにしなければいけない) DW 18 ; 1トラックにいくつのセクタがあるか(18にしなければいけない) DW 2 ; ヘッドの数(2にしなければいけない) DD 0 ; パーティションを使ってないのでここは必ず0 DD 2880 ; このドライブ大きさをもう一度書く DB 0,0,0x29 ; よくわからないけどこの値にしておくといいらしい DD 0xffffffff ; たぶんボリュームシリアル番号 DB "HELLO-OS " ; ディスクの名前(11バイト) DB "FAT12 " ; フォーマットの名前(8バイト) RESB 18 ; とりあえず18バイトあけておく ; プログラム本体 entry: MOV AX,0 ; レジスタ初期化 MOV SS,AX MOV SP,0x7c00 MOV DS,AX MOV ES,AX MOV SI,msg putloop: MOV AL,[SI] ADD SI,1 ; SIに1を足す CMP AL,0 JE fin MOV AH,0x0e ; 一文字表示ファンクション MOV BX,15 ; カラーコード INT 0x10 ; ビデオBIOS呼び出し JMP putloop fin: HLT ; 何かあるまでCPUを停止させる JMP fin ; 無限ループ msg: DB 0x0a, 0x0a ; 改行を2つ DB "hello, world" DB 0x0a ; 改行 DB 0 RESB 0x7dfe-($-$$)-0x7c00 ; 0x7dfeまでを0x00で埋める命令 DB 0x55, 0xaa
// other_disk.asm ; ブートセクタ以外の部分の記述 DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00 RESB 4600 DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00 RESB 1469432
上のようにhelloos.asmとipl.asmに分離します。そして、それぞれをコンパイルしてバイナリファイルにします。
nasm -f bin -o boot_sector.img boot_sector.asm nasm -f bin -o other_disk.img other_disk.asm
それぞれコンパイルできたら結合します。
cat other_disk.img >> boot_sector.img
起動することを確認します。起動できたらOK!
これで、今後はブートセクタのみを編集するだけで良くなりました。ちょっと楽になりますね。
バイナリファイルの分割・結合については以下の記事を参考にしました。
もっとコンパイルを楽にするために、Makefileの作成を行います。本の内容に従って僕の環境に合わせて書いたものが以下になります。
other_disk.img : other_disk.asm Makefile nasm -f bin -o other_disk.img other_disk.asm helloos.img : other_disk.img boot_sector.asm Makefile nasm -f bin -o helloos.img boot_sector.asm cat other_disk.img >> helloos.img img : make -r helloos.img
そして、「make img」を実行した結果がこれです。
user chapter2$ make img make -r helloos.img nasm -f bin -o other_disk.img other_disk.asm other_disk.asm:4: warning: uninitialized space declared in .text section: zeroing [-w+other] other_disk.asm:6: warning: uninitialized space declared in .text section: zeroing [-w+other] nasm -f bin -o helloos.img boot_sector.asm boot_sector.asm:27: warning: uninitialized space declared in .text section: zeroing [-w+other] boot_sector.asm:58: warning: uninitialized space declared in .text section: zeroing [-w+other] cat other_disk.img >> helloos.img user chapter2$ ls Makefile helloos.img boot_sector.asm other_disk.img other_disk.asm
ちゃんとhelloos.imgが生成されてますね!これでコンパイルがずっと楽になりました。生成されたhelloos.imgで正常に「hello, world」が表示されることも確認しました。
まとめ
今回は手を動かすよりも読んで理解するということが多かったです。ここまででのアセンブリ言語の命令についてはだいたい理解することが出来ました。レジスタについても種類は覚えきれていないですが、どのようなものがあるかや管理のされ方など理解することが出来ました。
明日は Chapter3です!タイトルは「32ビットモード突入とC言語導入」ということで読むだけでもワクワクします!明日も頑張りたいと思います。