IPLを読んでみた。

はじめに

Advent Calender

この記事は以下のAdvent Calendarの記事です。 SPC同好会 Advent Calendar 2017の24日目。

adventar.org

自作OS Advent Calendar 2017 の24日目。

adventar.org

実は…

色々と予定がありまして(言い訳)OSが完成しませんでした( ^ω^)…

その代わりと言っては何ですが、以下のサイトにかかれているipl.asmについて解説したいと思います。

RustでOSを書いてみる(環境構築編)

qiita.com

ipl.asm

github.com

ipl.asm

line 7

このプログラムをMBRに置くことで読み込ませる。

ソース

   ORG 0x7c00

解説

MBRのアドレスの位置はhttp://wiki.osdev.org/MBR_(x86) によって、以下のように書かれている。

An MBR is loaded by the BIOS at physical address 0x7c00, with DL set to the "drive number" that the MBR was loaded from. The BIOS then jumps to the very beginning of the loaded MBR (0x7c00), because that part of the MBR contains the "bootstrap" executable code.

つまりは0x07c0からMBRとして読み込んでもらえるということである。

line 8, 9

シリンダ数を定義

ソース

   ;; #define
    cyls equ 0x0a     ; 何シリンダ目まで読み込むかの設定

解説

cylsという定数をequ命令で、0x0aと定義している。

line 12, 13

INITというラベル(後に解説する)にジャンプしている。

ソース

START:
    jmp INIT   

解説

STARTというラベルを定義する。 その次の行でINITというラベルにジャンプしている。

line 16~37

FAT12というファイルシステムのヘッダを作成。

ソース

FAT12:
    db "ANO_MYOS"       ; OS名
    dw 512            ; 1セクタあたりのバイト数
    db 1          ; 1クラスあたりのセクタ数
    dw 1          ; 予約セクタ
    db 2          ; FATのテーブルの数
    dw 224            ; ルートディレクトリの長さ
    dw 2880           ; 全セクタ数
    db 0xF0           ; メディアの種類
    dw 9          ; FAT1つの長さ(セクタ単位)
    dw 18         ; 1トラックあたりのセクタの数
    dw 2          ; ドライブのヘッドの数
    dd 0          ; 他のパーティションのブートセクタの位置(0ならなし)
    dd 2880           ; 全セクタ数(2880か0を指定)
    db 0          ; ドライブ番号
    db 0          ; 予約(WindowsNTで使用)
    db 0x29           ; 以下の3項目があることを示す

    dd 0xffffffff     ; ボリュームシリアル番号
    db 'ANO_OS     '  ; ボリュームラベル
    db 'FAT12   '     ; ファイルシステム名
    times 18 db 0      ; 18バイト空けておく

解説

FAT12のヘッダとして必要なデータをおいている。 ここで説明すると大変なので、気になった方は以下のリンク先の説明を読んでほしい。 FATファイルシステムのしくみと操作法

line 40~49

セグメントレジスタとスタックポインタの初期化。

ソース

INIT:
    ;; セグメントレジスタを初期化(全て0x0000 = csに設定 )
    mov ax,cs
    mov ds,ax     ; データセグメント初期化
    mov es,ax     ; エクストラセグメン初期化
    mov ss,ax     ; スタックセグメント初期化

    ;; スタックポインタ初期化(0x7c00に設定)
    ;; SPは0x7c00にしないと正常に動かない(原因は調査中)
    mov sp,0x7c00

解説

0x0000を持っているcsレジスタの値を、ds(データセグメント)とes(エクストラセグメント)とss(スタックセグメント)に代入する。
スタックポインタは0x7c00にしなければ動かない(原因は調査中らしい)。

line 51~62

ディスク読み込みの初期化。

ソース

   ;; ディスク読み込みの初期化
DISK_INI:
    mov si,loadimg
    call PRINT

    mov ax,0x0820        ; 読み込んだデータをセグメントアドレス0x0820に展開
    mov es,ax
    mov ch,0     ; シリンダ0
    mov dh,0     ; ヘッド0
    mov cl,2     ; セクタ2
    mov dl,0x00      ; Aドライブ
    mov bx,0x0000        ; オフセットアドレスを0に設定

解説

loadimgという名前が貼られたアドレス(中身は'Loading image...',0x0d,0x0a,0x00)をsiレジスタに格納した後、PRINT(後に解説)というラベル先を呼ぶ(後に戻ってくる)。
axを通して0x0820をesに0x0820(読み込んだデータを展開するセグメントアドレス)に代入する。

その後、chレジスタに0を格納することでシリンダ0を、dhレジスタに0を格納することでヘッド0を、clレジスタに0を格納することでセクタ2を、dlレジスタに0x00を格納することでAドライブを選択する。

最後にbxレジスタに0x0000を代入することで、オフセットアドレスを0に設定する。

line 64, 65

失敗回数を数えるsiレジスタを初期化。

ソース

READLOOP:
    mov si,0     ; 失敗回数を数えるレジスタsiを初期化

解説

ここでREADLOOPというラベルを宣言後、失敗回数を数えるレジスタsiを初期化する。 (ここで作成したREADLOOPラベルは次のデータを読みこむ度、ジャンプされる。)

line 67~81

1セクタ読もうとする。

ソース

RETRY:
    mov ah,0x02
    mov al,1     ; 1セクタ読み込み
    int 0x13     ; ディスク関連ファンクション呼出    
    jnc NEXT       ; cf=0(エラー未発生)ならNEXTへ

    ;; 失敗した場合
    add si,1
    cmp si,5
    jae ERROR      ;  5回失敗したら、エラー

    ;; ドライブのリセット
    mov ah,0x00
    int 0x13
    jmp RETRY

解説

まずRETRYというラベルを宣言する。 ahレジスタに0x02(割り込みの際にセクタ読み込みを意味する)、alレジスタに1を設定し(これには1セクタ読み込むという意味がある)、0x13で割り込みをかける。 cf=0かどうか(0ならエラー未発生)で分岐する。

失敗した場合はsiレジスタに1加算しsiと5を比べ、もし5回失敗したとき(siレジスタが5だったら)はERRORラベルにジャンプする。 その後ahレジスタに0x00を格納し(ディスクコントローラをリセット)、0x13(ディスクサービス)で割り込みをかけた後に、またRETRYにジャンプする。

line 83~108

セグメントもしくはヘッドもしくはシリンダを進める。

ソース

NEXT:
    ;; セグメントアドレスを0x200(512バイト)進める
    mov ax,es
    add ax,0x0020  
    mov es,ax

    add cl,1     ; セクタを1進める

    ;; 18セクタ読み込んだか
    cmp cl,18
    jbe READLOOP

    add dh,1     ; ヘッド1へ
    mov cl,1     ; セクタ1から読み込み
    
    ;;裏表読み込んだか
    cmp dh,2
    jb READLOOP

    
    add ch,1     ; シリンダを1進める
    mov dh,0     ; ヘッド0から読み込み

    ;; 10シリンダ目まで読み込んだか
    cmp ch,cyls
    jb READLOOP

解説

まずNEXTというラベルを作成する。 その後、esレジスタの値をaxレジスタに格納した後、0x200(512バイト)加算してesレジスタに戻す。 clレジスタに1を足して、セクタを1進める。

18セクタ読み込んだかを確認して(clレジスタと18を比較して)、読み込まれてなかったら(clの中身>18)READLOOPにジャンプする。

読み込まれていれば、dhレジスタに1加算することでヘッドを一個進めて、clレジスタに1を格納することでセクタ1から読み込ませる。

その後、表裏のレジスタが読まれたかを確認し(dhレジスタの中身と2を比較する)、読み込まれていたら(dhレジスタの中身<2)ならREADLOOPにジャンプする。

シリンダに1加算することで1つ進めて、dhレジスタに0を格納し、ヘッドに0を設定する。

その後、最大シリンダ数(cyls)とchレジスタを比べて、chレジスタが最大シリンダ数以下ならREADLOOPにジャンプする。

line 110~121

ソース

READ_SETUP:
    mov ax,0x0000
    mov es,ax     ; もう一度esを初期化(もとの状態にもどす)
    
    mov si,complete
    call PRINT
    
    mov [0x0ff0],ch      ; 何シリンダ目まで読み込んだかメモ

    jmp 0xc200
    
    jmp FIN            ; ジャンプに失敗したとき用

解説

axレジスタを通して、esレジスタに0を格納して初期化。 その後complete先の文字列をsiに確認しPRINTをコールして表示する。 0x0ff0にchレジスタの値を格納して、0xc200にジャンプする。(失敗したらFINにジャンプする。)

line 123~125

ERROR用メッセージを表示する。

ソース

ERROR:
    mov si,error_mes
    call PRINT

解説

ERRORというラベルを設定する。 siレジスタにerror_mes(中身はerror_mes db 'Loading error.',0x0d,0x0a,0x00)を格納して、PRINTへジャンプ。

line 127~129

最終処理。

ソース

FIN:
    HLT
    jmp FIN

解説

FINを定義して、hlt状態を回す。

line 131~141

文字列の表示。

ソース

   ;; 文字出力ルーチン
PRINT:
    lodsb         ; siで指定されたアドレスから1バイト取り出しalに格納
    cmp al,0
    je PRINTED     ; 文末まで来たらPRINTEDへ

    mov ah,0x0e      ; 1文字書き込みモード
    mov bx,0x0007
    int 0x10

    jmp PRINT

解説

PRINTラベルを宣言して、siレジスタ指定されたアドレスから1バイトから取り出して、alレジスタに格納する。 文末まで来てることを確認(alレジスタと0を比較して同じかを確認)したら、PRINTEDにジャンプする。 ahレジスタに0x0eを格納して文字コード書き込みを設定し、ベースレジスタを0x0007に格納した後、0x10で割り込みをかける。 その後PRINTラベルにジャンプしてループする。

line 143, 144

リターンする。

ソース

PRINTED:
    ret           ; 呼出し元へ戻る

解説

PRINTEDというラベルを宣言し、retでリターンする(呼び出し元に戻る)。

line 147~151

各文字列を宣言する。

ソース

STRING_DATA:    
    loadimg  db 'Loading image...',0x0d,0x0a,0x00 ; 最後は、CR+LF+NULL
    complete db 'Loading complete!',0x0d,0x0a,0x00
    error_mes db 'Loading error.',0x0d,0x0a,0x00

解説

各文字列を宣言する。

line 152~153

バイナリ的に必要な処理をする。

ソース

   times 512 - 2 - ($-$$) db 0 ; 残りのバイトを0で埋める

    dw 0xAA55     ;  ブートシグニチャ

## 解説
iplの残りのバイトをすべて0で埋めて、ブートシグニチャを置く。

最後に

こんなざっとした解説になりましたが許してください(実際ソースのままの方が読みやすいのかもしれない)。
質問・間違い等あればコメントください。よろしくお願いします。

あとがき

Rustあんま関係なくね?と思ったので、Rust要素消しました。