自作OSに挑戦する日記 2日目

 「30日でできる!OS自作入門」を読んで分かったことや、とりあえず書いておきたいことなどを書いていきます。
 この本はChapterが1から30まであるので、各チャプター毎に1記事書いていきます。

Chapter 2 「アセンブラ学習とMakefile入門」

今回やった内容

環境

作業記録

 前半は作業記録というより、学習ノートみたいなものです。後半から作業記録っぽくなります。

 まず、アセンブリ言語について勉強します。

アセンブリ言語の命令

  • ORG (origin)

    • OSがPCメモリのどこに配置されるかを指定する命令。
  • JMP (jump)

    • 指定したラベルまでジャンプする命令。C言語のgotoらしい。
    • chapter2をずっと読んでいって分かったことなのですが、どうやらアセンブリ言語にはループがないのでラベルを使ってループを表現するみたいです。
  • 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人で「すげぇ…最高…」って言ってました。

 …ただ、まだ理屈を知っただけなので、実際にやってみます。実際にやってみるのが大事。

f:id:guguru0014:20190116224011p:plain
(今回は都合により仮想環境上での実行です)

やった!!!!ちゃんと動きました!!!

 あ、コードも貼っておきます。そういえば、昨日は全く分からなかったコードが読めるようになっています。自分すごいぞ。

; 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!
 これで、今後はブートセクタのみを編集するだけで良くなりました。ちょっと楽になりますね。

バイナリファイルの分割・結合については以下の記事を参考にしました。

qiita.com


 もっとコンパイルを楽にするために、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言語導入」ということで読むだけでもワクワクします!明日も頑張りたいと思います。