OS自作入門 + アルファ part1

OS自作入門 + アルファ part1

やっとディスクの読み込みができたのでブログに書こうと思います。
0から作るOS開発」というサイトを手本に作ってみたのですが、このサイト、あちこち説明やプログラムが間違っているので、あらためて作り方をまとめたいと思います。
まず、フロッピー ディスク イメージのマウント用にImDiskをインストールします。
てきとーに1440キロバイト (1.44メガバイトと表記されるが、1.44*1024*1024でなく1.44*1000*1024バイトである) のファイルを作成し、右クリックメニューからマウント。
Windows ではAドライブとBドライブがフロッピーディスクに割り当てられた気がするので、とりあえずAドライブにしました。
マウントしたら、「コンピュータ」からAドライブを右クリックメニューからFAT12でフォーマットします。
nasmでアセンブラでプログラムを作成し、Cygwinでブ-トセクタに書き込むことにします。
Cygwinインストールフォルダ直下のbinフォルダにコマンドが入っているので、そのパスを環境変数PATHに登録しておけば、コマンドプロンプト上でもCygwin上と同じ操作ができます。これにより、バッチファイルで一括で操作したりできます。
QEMUなどのエミュレータで動作確認をするわけですが、VMWare Playerで実行する場合は、「後で OS をインストール」、「ゲストOS その他、バージョン その他」、「ハードウェアのカスタマイズ」から「追加」で「フロッピー ドライブ」、「フロッピーイメージを使用する」、「起動時に接続」にチェック、でフロッピーイメージからOS起動ができる。
これでブートセクタを作る準備ができました。実際にブートセクタに書き込む方法は「ブートローダ その3」を参照。(Cygwinを起動してやれと書いてあっても、先ほど書いたように環境変数PATHにbinフォルダのパスを登録しておけば、コマンドプロンプト上から操作できます)

ブートセクタ (テスト)

マウントしてできたAドライブに以下のプログラムをコンパイルしてブートセクタに書き込み、
また、テキトーに文章を書いて(全角文字は使わないでください)「TEXT.TXT」という名前でAドライブに保存しておきます。
なぜかOS名は「EatIce (氷食べろ)」にしました。

%define   FAT_ADDR      0x7E00
%define  ROOT_ADDR      0xA200
%define  BOOT_FILE_ADDR 0x0500

[BITS 16]
ORG  0x7C00


; IPLへジャンプ

JMP entry
DB 0x90    ; Windows 8 では必須


; OEM name string (ブートセクタの名前)

BS_OEMName  DB "EatIce  "


; BIOS Parameter Block

BPB_BytsPerSec  DW 512   ; セクターあたりのバイト数
BPB_SecPerClus  DB 1     ; クラスターあたりのセクター数
BPB_RsvdSecCnt  DW 1     ; FAT場所 (起動プログラムのセクター数)
BPB_NumFATs     DB 2     ; FAT個数
BPB_RootEntCnt  DW 224   ; ルートディレクトリの最大ファイル数 (エントリー数)
BPB_TotSec16    DW 2880  ; ドライブのセクター数 (12bit/16bit用)
BPB_Media       DB 0xf0  ; メディアのタイプ
BPB_FATSz16     DW 9     ; FATあたりのセクター数
BPB_SecPerTrk   DW 18    ; トラックあたりのセクター数
BPB_NumHeads    DW 2     ; ヘッド数
BPB_HiddSec     DD 0     ; パーティション前の隠しパーティションのセクター数
BPB_TotSec32    DD 0     ; ドライブのセクター数 (32bit用)


; 拡張 BIOS Parameter Block

BS_DrvNum      DB 0x00           ; 物理ドライブ番号
BS_Reserved1   DB 0x00           ; 予約
BS_BootSig     DB 0x29           ; シグネチャ
BS_VolID       DD 0x20131023     ; シリアル番号
BS_VolLab      DB "EatIceOS   "  ; ボリューム名
BS_FilSysType  DB "FAT12   "     ; FATの種類


; Initial Program Loader

entry:
  ; レジスタ初期化
  XOR AX, AX
  XOR BX, BX
  XOR CX, CX
  XOR DX, DX
  ; セグメント初期化
  MOV DS, AX
  MOV ES, AX
  MOV FS, AX
  MOV GS, AX
  ; スタック関連初期化
  MOV SS, AX
  MOV SP, 0xFFFC

  ; ディスクリセット
  CALL DiskReset

  ; FAT読み込み
  CALL FATLoad

  ; ルートディレクトリ読み込み
  CALL RootLoad

  ; ファイル読み込み
  MOV SI, BootFileName
  MOV BX, BOOT_FILE_ADDR
  CALL FileRead

  ; ファイル読み込み確認テスト (テキトウ)
  MOV SI, BOOT_FILE_ADDR
  CALL MsgDraw

  ; 成功
  MOV SI, MSG_OK
  CALL MsgDraw

fin:
  HLT
  JMP fin

; 失敗
error:
  MOV SI, MSG_ERR
  CALL MsgDraw
  JMP fin

; -------------------------------- ;
; 高度なサブルーチン
; -------------------------------- ;


; FAT読み込み

FATLoad:
  XOR AX, AX
  MOV AL, BYTE [BPB_NumFATs]     ; FAT数
  MUL WORD [BPB_FATSz16]         ; ×FATあたりサイズ
  MOV CX, AX                     ; FATサイズ (ループ回数)
  MOV AX, WORD [BPB_RsvdSecCnt]  ; FAT場所
  ; 
  ADD WORD [CurSecter], AX
  ADD WORD [CurSecter], CX
  ; FAT読み込みメモリアドレス
  MOV BX, FAT_ADDR
  ; 読み込み
  CALL DiskRead
  RET


; ルートディレクトリ読み込み

RootLoad:
  MOV AX, 32                     ; エントリサイズ
  MUL WORD [BPB_RootEntCnt]      ; ×エントリ数
  ; 四捨五入割り算
  ADD AX, WORD [BPB_BytsPerSec]
  DEC AX
  DIV WORD [BPB_BytsPerSec]
  MOV CX, AX                     ; ルートサイズ (ループ回数)
  ; XOR AX, AX
  ; MOV AL, BYTE [BPB_NumFATs]     ; FAT数
  ; MUL WORD [BPB_FATSz16]         ; ×FATあたりサイズ
  ; ADD AX, WORD [BPB_RsvdSecCnt]  ; +FAT場所 =ルート場所
  MOV AX, WORD [CurSecter]        ; ルート場所
  ; 
  ADD WORD [CurSecter], CX
  ; ルート読み込みメモリアドレス
  MOV BX, ROOT_ADDR
  ; 読み込み
  CALL DiskRead
  RET


; ファイル読み込み

; DS:SI == 文字列メモリアドレス, ES:BX == 読み込み先メモリアドレス

FileRead:
  PUSH BX
  CALL GetFstClusByFileName
  POP BX
  CALL FileReadByFstClus
  RET

; -------------------------------- ;
; 中位なサブルーチン
; -------------------------------- ;


; ファイル名から開始クラスタ取得

; DS:SI == 文字列メモリアドレス

; AX = 開始クラスタ

GetFstClusByFileName:
  MOV BX, ROOT_ADDR
  MOV CX, WORD [BPB_RootEntCnt]  ; エントリ数 (ループ回数)
GetFstClusByFileNameLp:
  MOV DI, BX
  PUSH CX
  ; 異なる文字数カウント
  MOV CX, 11
  PUSH DI
  PUSH SI
  REPE CMPSB
  POP SI
  POP DI
  JCXZ GetFstClusByFileNameFin
  ; 次のエントリ
  POP CX
  ADD BX, 32
  LOOP GetFstClusByFileNameLp
  ; 1件もヒットしなかった
  JMP error
GetFstClusByFileNameFin:
  POP CX
  MOV AX, WORD [BX+26]
  RET


; 開始クラスタからファイル読み込み

; AX == 開始クラスタ, ES:BX == 読み込み先メモリアドレス

FileReadByFstClus:
FileReadByFstClusNext:
  PUSH AX
  SUB AX, 2
  XOR CX, CX
  MOV CL, BYTE [BPB_SecPerClus]
  MUL CX
  ADD AX, WORD [CurSecter]       ; データエリア場所
  MOV CX, 1
  CALL DiskRead
  POP AX
  PUSH BX
  ; クラスタの場所
  MOV BX, AX
  SHR BX, 1
  ADD BX, AX
  ; クラスタのメモリアドレス
  ADD BX, FAT_ADDR
  MOV DX, WORD [BX]
  ; 偶数番目 (奇数番号) なら右へ半byteシフト
  TEST AX, 1
  JZ FileReadByFstClusElse
  SHR DX, 0x0004
FileReadByFstClusElse:
  AND DX, 0x0FFF
  ; 次のクラスタ
  MOV AX, DX
  CMP DX, 0x0FF0
  ; 
  POP BX
  JB FileReadByFstClusNext
  ; 読み込み終了
  
  RET

; -------------------------------- ;
; 簡単なサブルーチン
; -------------------------------- ;


; DiskReset

DiskReset:
  MOV AH, 0x00              ; ドライブ初期化
  MOV DL, BYTE [BS_DrvNum]  ; ドライブ番号
  INT 0x13
  JC error                  ; 失敗時
  RET


; DiskRead

; AX == LBA, ES:BX == 読み込み先メモリアドレス, CX == セクター数

DiskRead:
DiskReadNext:
  MOV DI, 5                 ; エラーカウンタ
DiskReadRetry:
  PUSH AX
  PUSH BX
  PUSH CX
  ; LBAチェック
  CMP AX, WORD [BPB_TotSec16]
  JAE error
  ; LBA2CHS
  XOR DX, DX
  DIV WORD [BPB_SecPerTrk]
  INC DL
  MOV CL, DL                ; セクター
  XOR DX, DX
  DIV WORD [BPB_NumHeads]
  MOV DH, DL                ; ヘッド
  MOV CH, AL                ; シリンダー
  ; 読み込み実行
  MOV AH, 0x02              ; ドライブ読み込み
  MOV AL, 0x01              ; セクター数
  MOV DL, BYTE [BS_DrvNum]  ; ドライブ番号
  INT 0x13
  JNC DiskReadFin           ; 成功時
  ; 失敗時
  CALL DiskReset
  POP CX
  POP BX
  POP AX
  DEC DI
  JNZ DiskReadRetry         ; やり直す
  JMP error
DiskReadFin:
  POP CX
  POP BX
  POP AX
  ; 次のセクター読み込み準備
  ADD BX, WORD [BPB_BytsPerSec]
  INC AX
  LOOP DiskReadNext
  RET


; 文字列表示

; DS:SI == 文字列メモリアドレス

MsgDraw:
  PUSH SI
MsgDrawLp:
  LODSB
  OR AL, AL
  JZ MsgDrawFin
  MOV AH, 0x0E   ; 一文字表示
  MOV BH, 0x00   ; 0
  MOV BL, 0x07   ; カラーコード
  INT 0x10
  JMP MsgDrawLp
MsgDrawFin:
  POP SI
  RET

; -------------------------------- ;
; その他
; -------------------------------- ;


; 変数, データ

MSG_OK   DB 0x0A, "OK", 0x0A, 0x00
MSG_ERR  DB 0x0A, "ERROR", 0x0A, 0x00
BootFileName  DB "TEST    TXT", 0x00
CurSecter  DW 0


; 残りを 0 で埋める

TIMES 510 - ($ - $$)  DB 0x00


; Boot Signature

DB 0x55, 0xaa

(アセンブラ初心者なので、アドバイスとかあればいただきたいです)
※2013/11/17追加 … 他人のを間違っていると言っておきながら、自分のプログラムも一部間違っているようです。現在修正途中です、
サブルーチンのコメントに「高度な」とか「簡単な」とかつけてますが、テキトーに分類してるだけなのであまり気にしなくていいです。
これでテキストファイルの内容を読み込んで、文字列が表示されるはずです。
↑QEMUでの動作
これを作るだけで1週間近くかかってしまった…。
次回は「0から作るOS開発」の「カーネルローダ」に相当する内容を書いていきたいと思います。
ではでは。またいつか。
↓ブログランキング投票 (クリック) お願いします


「OS自作入門 + アルファ part1」への2件のフィードバック

  1. おお、これは見事なアセンブリ・・・
    本格的になってきましたね。

  2. ありがとうございます!!
    アセンブリ初心者だったのですが、なんとかここまでできました…

コメントは受け付けていません。