自作OSに挑戦する日記 3日目
「30日でできる!OS自作入門」を読んで分かったことや、とりあえず書いておきたいことなどを書いていきます。
この本はChapterが1から30まであるので、各チャプター毎に1記事書いていきます。
Chapter 3 「32ビットモード突入とC言語導入」
今回やった内容
環境
- メインPC
- MacBook Pro (13-inch, 2017, Four Thunderbolt 3 Ports)
- macOS Mojave 10.14
- 自作OSを動作させるPC
- Core 2 Duo
- 4GB RAM
- (ハードオフのジャンクコーナにいた子)
作業記録
今回で、IPL(初期プログラムローダ)を完成させます。 今まで作っていたやつは本物じゃないらしい…。他のプログラムをロードしてこそIPL。
まずはディスクの仕組みについて勉強。本の説明はフロッピーディスクについてです(ちょっと内容が古い)。ディスクの仕組みのほか、バッファアドレスやメモリの番地指定の方法についても学びました。
勉強が終わったところで、実際にディスクの中身を読み込むようなアセンブリを書きます。
何も表示されない、ディスク(USB)読み込み成功! pic.twitter.com/XnJp2vuKd7
— ゆん (@yn0014) 2019年1月17日
エラーの時はエラーメッセージが出るのですが、成功時に何も出ないのは悲しいということで、自分でちょっと改造してみました。
読み込み成功した時に何も出ないのは寂しいからちょっといじってみた pic.twitter.com/rZ0SkuxMlJ
— ゆん (@yn0014) 2019年1月17日
自分で考えて書いたコードがちゃんと動くことに感動…。アセンブリについてちゃんと理解できている証拠です、えらい。
さっき書いたディスクの中身を読み込むようなアセンブリは、1セクタしか読み込まないようになっているので、10シリンダ・2ヘッダ・18セクタ分読めるようにしていきます。やっている内容は、ループしてレジスタの値を変えながらディスク関数を呼んでいるだけです。ループが入ると急にプログラムっぽくなりますね、楽しい。
完成コードがこれです。10 x 2 x 18 x 512(1セクタ=512バイト) = 184320バイト=180KB分、ディスクから読み込めるようになりました。
// ipl.asm ; haribote-ipl ; TAB=4 CYLS EQU 10 ; 何シリンダディスクからOSを読み込むかを設定する定数 ORG 0x7c00 ; このプログラムがどこに読み込まれるのか ; 以下は標準的なFAT12ディスクのための記述 JMP entry DB 0x90 DB "HARIBOTE" ; ブートセクタの名前を自由に書いてよい(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 "HARIBOTEOS " ; ディスクの名前(11バイト) DB "FAT12 " ; フォーマットの名前(8バイト) RESB 18 ; とりあえず18バイトあけておく ; プログラム本体 entry: MOV AX,0 ; レジスタ初期化 MOV SS,AX MOV SP,0x7c00 MOV DS,AX ; ディスクを読む MOV AX,0x0820 MOV ES,AX MOV CH,0 ; シリンダ0 MOV DH,0 ; ヘッド0 MOV CL,2 ; セクタ2 readloop: MOV SI, 0 ; 読み込み失敗回数カウンタ初期化 retry: MOV AH, 0x02 MOV AL, 1 ; 1セクタだけ読み込む MOV BX, 0 MOV DL, 0x00 INT 0x13 ; ディスクBIOS呼び出し JNC next ; エラーがなければnextへ ADD SI, 1 ; 失敗カウンタ+1 CMP SI, 5 ; 失敗回数が5なら…エラー表示 JAE error MOV AH, 0x00 MOV DL, 0x00 INT 0x13 ; システムリセット JMP retry ; もう一度読み込みに挑戦する next: MOV AX, ES ADD AX, 0x0020 ; アドレスを0x200進める MOV ES, AX ; ADD ES, 0x200という命令がないのでAXレジスタを経由している ADD CL, 1 ; 1セクタ進める CMP CL, 18 JBE readloop ; 読み込みセクタが18以下ならreadloopへ MOV CL, 1 ADD DH, 1 CMP DH, 2 JB readloop ; 読み込みヘッダが<2ならreadloopへ MOV DH, 0 ADD CH, 1 CMP CH, CYLS JB readloop ; 読み込みシリンダが<CYLS(定数)ならreadloopへ JMP success fin: HLT ; 何かあるまでCPUを停止させる JMP fin ; 無限ループ ; 読み込み成功メッセージがあるメモリのアドレスを記録 success: MOV SI, successmsg JMP putloop ; エラーメッセージがあるメモリのアドレスを記録 error: MOV SI, errormsg ; 出力ループ 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 ; 読み込み成功メッセージ successmsg: DB 0x0a, 0x0a ; 改行2つ DB "load some sector success" DB 0x0a ; 改行 DB 0 ; 読み込み失敗メッセージ errormsg: DB 0x0a, 0x0a ; 改行を2つ DB "load error" DB 0x0a ; 改行 DB 0 RESB 0x7dfe-($-$$)-0x7c00 ; 0x7dfeまでを0x00で埋める命令 DB 0x55, 0xaa
ブートセクタ以外を読み込めるようになったので、OS本体をブートセクタ以外のディスク上に配置して、それを実行するということに挑戦します。とりあえず本に従って、HLTするだけのOSコードを用意しコンパイルします。そしてディスクイメージに保存しておきます。
hariboteos.imgをバイナリエディタで開いて、OS本体が配置されているアドレスを調べます。
ディスクイメージ内のアドレス0x5600にOS本体が配置されているみたいです。現在のプログラムだとブートセクタの先頭アドレスが0x8000となるなので、この2つを足し合わせることで実際にPCのメモリ上で展開されたときのアドレスを求めることができます。
[追記 : 2019/1/18]
ディスクイメージに保存するやり方が間違っていたらしく、0x5600に配置されたというのは間違いでした。正しくは0x4200に配置されます。なので、実際にPCのメモリ上で展開された時のアドレスは0xC200になります。
以下のコードは、求めたアドレスを使って、実際にOS本体を実行するようにしたものです。
// ipl.asm ; haribote-ipl ; TAB=4 CYLS EQU 10 ; 何シリンダディスクからOSを読み込むかを設定する定数 ORG 0x7c00 ; このプログラムがどこに読み込まれるのか ; 以下は標準的なFAT12ディスクのための記述 JMP entry DB 0x90 DB "HARIBOTE" ; ブートセクタの名前を自由に書いてよい(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 "HARIBOTEOS " ; ディスクの名前(11バイト) DB "FAT12 " ; フォーマットの名前(8バイト) RESB 18 ; とりあえず18バイトあけておく ; プログラム本体 entry: MOV AX,0 ; レジスタ初期化 MOV SS,AX MOV SP,0x7c00 MOV DS,AX ; ディスクを読む MOV AX,0x0820 MOV ES,AX MOV CH,0 ; シリンダ0 MOV DH,0 ; ヘッド0 MOV CL,2 ; セクタ2 readloop: MOV SI, 0 ; 読み込み失敗回数カウンタ初期化 retry: MOV AH, 0x02 MOV AL, 1 ; 1セクタだけ読み込む MOV BX, 0 MOV DL, 0x00 ; USBはHDDとして認識される INT 0x13 ; ディスクBIOS呼び出し JNC next ; エラーがなければnextへ ADD SI, 1 ; 失敗カウンタ+1 CMP SI, 5 ; 失敗回数が5なら…エラー表示 JAE error MOV AH, 0x00 MOV DL, 0x00 INT 0x13 ; システムリセット JMP retry ; もう一度読み込みに挑戦する next: MOV AX, ES ADD AX, 0x0020 ; アドレスを0x200進める MOV ES, AX ; ADD ES, 0x200という命令がないのでAXレジスタを経由している ADD CL, 1 ; 1セクタ進める CMP CL, 18 JBE readloop ; 読み込みセクタが18以下ならreadloopへ MOV CL, 1 ADD DH, 1 CMP DH, 2 JB readloop ; 読み込みヘッダが<2ならreadloopへ MOV DH, 0 ADD CH, 1 CMP CH, CYLS JB readloop ; 読み込みシリンダが<CYLS(定数)ならreadloopへ MOV [0x0ff0], CH JMP 0xC200 ; OS本体のアドレスまでジャンプ ; 終了、CPU待機 fin: HLT ; 何かあるまでCPUを停止させる JMP fin ; 無限ループ ; 読み込み成功メッセージがあるメモリのアドレスを記録 success: MOV SI, successmsg JMP putloop ; エラーメッセージがあるメモリのアドレスを記録 error: MOV SI, errormsg ; 出力ループ 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 ; 読み込み成功メッセージ successmsg: DB 0x0a, 0x0a ; 改行2つ DB "load some sector success" DB 0x0a ; 改行 DB 0 ; 読み込み失敗メッセージ errormsg: DB 0x0a, 0x0a ; 改行を2つ DB "load error" DB 0x0a ; 改行 DB 0 RESB 0x7dfe-($-$$)-0x7c00 ; 0x7dfeまでを0x00で埋める命令 DB 0x55, 0xaa
これでディスク上に配置したOS本体を実行できるようになったのですが、HLTを実行し続けるような処理だと画面上に変化がなく面白くないので、画面を真っ暗にするようなコードを付け加えておきます。
// hariboteos.asm ; haribote-os-body ORG 0xC200 MOV AL, 0x13 MOV AH, 0x00 INT 0x10 fin: HLT JMP fin
できた2つのコードをコンパイルして、hariboteos.imgにharibote_hlt.imgを保存・実行すると…
ちゃんと画面が真っ暗になることが確認できました!!OS本体をちゃんと実行できているみたいです!
3日目の目玉であるC言語の導入を行います。とりあえず、何もせずループするだけのCを書きます。
// bootpack.c void HariMain(void){ fin: goto fin; }
3日目はここからが一番大変です。先ほど書いたアセンブリとCのファイルをそれぞれオブジェクトファイルへコンパイルしてリンクさせる、という作業を行うのですが、この部分は全て筆者の方の自作ツールを使うように書かれているのでwindows環境以外だと自分でやり方を探すしかありません。
まず、アセンブリとCのコンパイル・リンクを行います。gccを使うとコマンド1つで終わるという記事があったのでありがたく使わせてもらいます。また、リンクを行う際にリンカファイルが必要なので、これも記事に書かれていたものを使わせてもらいます。
syusui.tumblr.com記事に書かれているようにコンパイルするコマンドを実行しようとしますが、macのgccだと「-T」オプションが存在しないので怒られてしまいます。
なので、macでも「-T」オプションが使えるようにクロスコンパイラのgccをビルドします。
ビルドしたgccでコンパイルをしてbootpack.hrbを生成します。そして、hariboteos.imgと結合すると完成です。
i386-elf-gcc -march=i486 -m32 -nostdlib -T har.ld -o bootpack.hrb bootpack.c cat bootpack.hrb >> hariboteos.img
実行すると先ほどと同じく真っ暗な画面になりました。ちゃんと出来ているみたいです。
HLT命令はC言語から呼ぶ事が出来ないので、アセンブリでHLTを実行する関数を書き、それをCから呼ぶようにます。本に書かれているコードでは動かないので少し修正しました。
// naskfunc.asm ; nasmfunc [BITS 32] ; 32ビットモード用に機械語を生成する ; オブジェクトファイルのための情報 GLOBAL io_hlt ; このプログラムに含まれる関数名 ; 関数 [SECTION .text] io_hlt: HLT RET
// bootpack.c void io_hlt(void); void HariMain(void){ fin: io_hlt(); goto fin; }
nasmfunc.asmのオブジェクトファイルを生成して、bootpack.cとリンクしてコンパイルします。これで、nasmfunc.asmに書いたio_hlt関数がCからでも呼び出せるようになりました。
nasm -f elf -o nasmfunc.o nasmfunc.g i386-elf-gcc -march=i486 -m32 -nostdlib -T har.ld -o bootpack.hrb -g bootpack.c nasmfunc.o
最後は、いつも通りにhariboteos.imgを生成します。実行すると真っ暗な画面が出てきました、成功です!(たぶん…)
まとめ
IPL完成とC言語実行まで出来ました。内容の理解はすぐ出来たのですが、コンパイルやビルドに手間を取られ、終えるのに3日もかかってしまいました。もっと他のところで時間使いたいな〜。
おまけ
3日目終了時点でのMakefileを貼っておきます。
other_disk.img : other_disk.asm Makefile nasm -f bin -o other_disk.img other_disk.asm hariboteos.img : other_disk.img ipl10.asm Makefile nasm -f bin -o hariboteos.img ipl10.asm cat other_disk.img >> hariboteos.img nasmfunc.o : nasmfunc.asm Makefile nasm -f elf -o nasmfunc.o nasmfunc.asm hariboteos.sys : asmhead.asm bootpack.c har.ld Makefile nasm -f bin -o hariboteos.sys asmhead.asm i386-elf-gcc -march=i486 -m32 -nostdlib -T har.ld -o bootpack.hrb -g bootpack.c nasmfunc.o cat bootpack.hrb >> hariboteos.sys img : make -r hariboteos.img make -r nasmfunc.o make -r hariboteos.sys hdid hariboteos.img cp ./hariboteos.sys /Volumes/HARIBOTEOS diskutil umount /dev/disk3 diskutil eject /dev/disk3 make clean write : diskutil unMountDisk /dev/disk3 sudo dd if=./hariboteos.img of=/dev/disk3 clean : rm *.sys rm *.o rm *.hrb rm other_disk.img