Tsurugidake's diary

プログラミング学習の記録,備忘録

Linuxで書くOS自作入門 3日目(前半)

「30日でできる!OS自作入門」の3日目
環境はUbuntu, アセンブラはNASMで開発中
長くなりそうなのでharib00d, 10シリンダ読み込むプログラムまでをここに書きます.

書き換えた部分の概要

BPB(BIOS Parameter Block)の後にディスクをメモリに読み込むプログラムを書く.ディスク読み込みはINT命令によって実行される.

  • INT命令は割り込み(Interrupt)処理の呼び出しの命令であり,BIOSの機能を呼び出す.
  • そもそもBIOSBasic Input/Output Systemの略で,ハードウェアとの最も低レベルな入出力を行うプログラム. コンピュータのモデルごとに設計され,マザーボード上のメモリに埋め込まれている.

セクタごとにディスクを読み込んで行く.メモリのオフセットにはヘッド,シリンダ,セクタのサイズを考える必要があるためメモ.
(下記はフロッピーディスクの場合)

  • 1ディスク = 2ヘッド = 160シリンダ = 2,880セクタ = 1,474,560バイト
  • 1ヘッド = 80シリンダ = 1,440セクタ = 737,280バイト
  • 1シリンダ = 18セクタ = 9,216バイト
  • 1セクタ = 512バイト

読み込みループ部分

    CYLS  EQU      10             ; 読み込むシリンダ数

EQUは定数を定義できる.アセンブラが後で数字に書き換えてくれる.

BPB部分は前回と同じなので省略.

entry:
        MOV      AX,0        ; レジスタ初期化
        MOV      SS,AX
        MOV      SP,0x7c00   ; ブートセクタは0x00007c00に読み込まれる
        MOV      DS,AX

; ディスクを読む

        MOV      AX,0x0820
        MOV      ES,AX         ; セグメントレジスタ(ES:BX)なのでメモリ上では0x8200
        MOV      CH,0        ; シリンダ番号
        MOV      DH,0        ; ヘッド番号
        MOV      CL,2            ; セクタ番号
readloop:
        MOV      SI,0        ; エラー回数リセット
retry:
        MOV      AH,0x02         ; AH=0x02 : ディスク読み込み
        MOV      AL,1            ; 処理セクタ数
        MOV      BX,0        ; バッファアドレス
        MOV      DL,0x00         ; ドライブ番号 0->Aドライブ
        INT      0x13           ; ディスクBIOS呼び出し
        JNC      next         ; エラーがおきなければnextへ
        ADD      SI,1            ; SI(エラー回数)に1を足す
        CMP      SI,5            ; SIと5を比較
        JAE      error            ; エラー回数5回以上 だったらerrorへ
        MOV      AH,0x00
        MOV      DL,0x00         ; ドライブ番号 0->Aドライブ
        INT      0x13           ; ドライブのリセット
        JMP      retry
next:
        MOV      AX,ES         ; アドレスを0x200進める 0x200は1セクタ分
        ADD      AX,0x0020       ; セグメントレジスタ(ES:BX)なので0x200->0x20
        MOV      ES,AX         ; ADD ES,0x020 という命令がないのでこうしている
        ADD      CL,1            ; CLに1を足す
        CMP      CL,18           ; CLと18を比較
        JBE      readloop     ; CL(セクタ番号) <= 18 だったらreadloopへ
        MOV      CL,1            ; CLを1に戻す
        ADD      DH,1
        CMP      DH,2
        JB       readloop     ; DH(ヘッド番号) < 2 だったらreadloopへ
        MOV      DH,0
        ADD      CH,1
        CMP      CH,CYLS
        JB       readloop     ; CH(シリンダ番号) < CYLS だったらreadloopへ
        JMP      success

INT命令を呼び出す前に,各レジスタの値を設定する必要がある.(AT)BIOS - os-wiki
ヘッド,シリンダ,セクタの値を指定することでディスクの読み込み部分は確定する. メモリ上への読み込み部分はセグメントレジスタ[ES:BX]を指定する.実際のメモリアドレスはES * 16 + BXとなるので,ESに代入するときは16進数だと下一桁の0が無くなるので注意する.
読み込みのエラーが起こる場合があるので,エラー回数をレジスタSIでカウントし,5回までならやり直すように設定した.(QEMUでエミュレートするだけなら特に意味はない) 後は,セクタ,シリンダ,ヘッドの順に,比較命令とジャンプ命令の組み合わせによって繰り返しをプログラムする.

終了処理

fin:
        HLT                      ; 何かあるまでCPUを停止させる
        JMP      fin              ; 無限ループ

終了時のプログラムは前回と同じ.

本誌では読み込み成功時には何も表示されないプログラムとなっているが,本当に読み込めているか不安になってしまうため,オリジナルで成功時のメッセージを表示するように書き換えた.

success:
        MOV      SI,msg2
        JMP      putloop

error:
        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

msg:
        DB       0x0a, 0x0a        ; 改行を2つ
        DB       "load error"
        DB       0x0a           ; 改行
        DB       0

msg2:
        DB       0x0a, 0x0a        ; 改行を2つ
        DB       "load success"
        DB       0x0a           ; 改行
        DB       0

        TIMES    0x7dfe-0x7c00-($-$$) DB 0     ; 0x7dfeまでを0x00で埋める命令

        DB       0x55, 0xaa    ;ブートセクタが有効であることを示す2バイト

実行

従来のMakefileで実行すると失敗した.

f:id:tsurugidake:20170826001953p:plain:w400

QEMUのオプションでフロッピーディスクのブートを明示してないことが原因だった.原因の特定にかなりの時間を消費した.

    qemu-system-i386 -fda haribote.img    # -fdaオプションをつける

f:id:tsurugidake:20170826002531p:plain:w400

今度はちゃんと成功表示が出た.