MBR・VBR自作でFAT16として認識させる

## 「手探りでCUI OS作成に挑む」連載
この記事は「手探りでCUI OS作成に挑む」連載の一部です。
全体の目次は[「手探りでCUI OS作成に挑む」連載目次](https://qiita.com/earthen94/items/b5d77f98f0cf85a13346)を御覧下さい。

## MBR VBRとは
通常、HDDなどの記憶媒体では先頭の512バイトに**MBR（Master Boot Record）が存在します。FATファイルシステムで初期化されたパーティションの先頭にはVBR（Volume Boot Record）**が置かれます。
MBR中でパーティションが4つまで登録でき、それにより一つのHDDをCドライブ、Dドライブと分割できます。
そしてそれぞれのパーティションの先頭の512バイトにはVBRと呼ばれるデータが置かれ、ここにはFATに関する情報が記録されます。
OSが起動可能なHDDの場合は初めにMBRを実行し、その後VBRを実行し、その後カーネルと順を追って実行されます。
ファイルシステムを実装していないOS自作本ではいきなりMBRからOSのコードを立ち上げていることもありますが、FAT16でファイルシステムを実装する場合は以下の用に実装できます。

## MBRのコード
```
[BITS 16]
[ORG 0x7C00]

start:
    ; 1. MBRを安全な領域（0x0600）へ退避
    xor ax, ax
    mov ds, ax
    mov si, 0x7C00
    mov di, 0x0600
    mov cx, 512
    rep movsb

    ; 2. セグメントレジスタの初期化
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x7C00
    
    ; 0x0600にジャンプして処理を続行
    jmp 0x0000:(continue - start + 0x0600)

continue:

    mov si, msg_loading
    call print_string

    ; 4. VBRを0x7C00に読み込む（このコードを上書き）
    mov ah, 0x02
    mov al, 1       ; 読み込むセクタ数
    mov ch, 0       ; シリンダ番号
    mov cl, 1       ; セクタ番号（1〜63）
    mov dh, 1       ; ヘッド番号
    mov dl, 0x80    ; 一台目のHDDを読み込む
    mov bx, 0x7C00
    int 0x13
    jc disk_error

    ; 5. 0x7C00に読み込んだVBRへ跳ぶ
    jmp 0x0000:0x7C00

print_string:
    lodsb
    or al, al
    jz .done
    mov ah, 0x0E
    int 0x10
    jmp print_string
.done:
    ret

disk_error:
    mov si, msg_disk_error
    call print_string
    jmp $

msg_loading db "Loading VBR...", 0
msg_disk_error db "Disk read error!", 0

; MBRの残りを埋める
times 446-($-$$) db 0

; パーティションテーブル（FAT16のパーティション）
db 0x80               ; アクティブパーティション
db 0x01, 0x01, 0x00   ; CHS開始位置（0,1,1）
db 0x04               ; パーティションタイプ（FAT16）
db 0xFF, 0xFF, 0xFF   ; CHS終了位置（最大値）
dd 63                 ; 開始LBA（63セクタ目）
dd 4096               ; セクタ数（約2MB）

; 残りの3つの空パーティションを埋める
times 16 * 3 db 0

dw 0xAA55
```

## VBRのコード
```
[BITS 16]
[ORG 0x7C00]  ; VBRはアドレス0x7C00に読み込まれる

; FAT16のBPB
jmp start
nop
db "MSWIN4.1"     ; OEM名
dw 512           ; セクタあたりのバイト数
db 1             ; クラスタあたりのセクタ数
dw 1             ; 予約セクタ数
db 2             ; FATテーブルの数
dw 512           ; ルートディレクトリ項目数
dw 4096          ; 総セクタ数（16ビット）
db 0xF8          ; 媒体種別（HDD等）
dw 8             ; 各FATのセクタ数
dw 63            ; トラックあたりのセクタ数
dw 255           ; ヘッド数
dd 63            ; 隠しセクタ数
dd 0             ; 総セクタ数（32ビット、16ビットが0の場合に使用）
db 0x80          ; BIOSドライブ番号（0x80＝HDD）
db 0             ; 予約（使用されない）
db 0x29          ; 拡張ブート用
dd 0x12345678    ; ボリューム番号
db "FAT16DISK "  ; ボリュームラベル
db "FAT16   "    ; ファイルシステムの種類

start:

    mov si, msg_loaded
    call print_string

    ; FAT16ファイルシステムのマウント（今後の拡張用）
    jmp $       ; 無限ループ（実際にはファイルシステムを読み込む）

print_string:
    lodsb
    or al, al
    jz .done
    mov ah, 0x0E
    int 0x10
    jmp print_string
.done:
    ret

msg_loaded db "FAT16 VBR Loaded!", 0

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

dw 0xAA55
```

## 起動
OSはまだ実装していませんがMBRからVBRを起動することに成功しました。
```
# コンパイル
nasm -f bin mbr.asm -o mbr.bin
nasm -f bin vbr.asm -o vbr.bin

dd if=/dev/zero of=virtual_disk.img bs=1M count=1024  # 1GBの仮想HDD生成

# MBRを先頭512バイトへ書き込み
dd if=mbr.bin of=virtual_disk.img bs=512 count=1 conv=notrunc

# VBRを63セクタ目へ書き込み「第1パーティションの先頭512バイト」
dd if=vbr.bin of=virtual_disk.img bs=512 seek=63 conv=notrunc

# 起動する
qemu-system-i386 -hda virtual_disk.img
```
![图片.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/01f8fff5-5978-4ce6-ab65-54a17682f318.png)

## 仮想HDDが本当にFATとして識別されるかをXPで検証
先ほど作った仮想HDDをWindowsXPでEドライブとして読み込ませるとFATとして認識されました。
```
  qemu-system-i386 \
  -m 1024 \
  -hda xp.img \
  -hdb virtual_disk.img \
  -boot c \
  -cpu host \
  -smp 2 \
  -net nic -net user \
  -vga std \
  -rtc base=localtime \
  -enable-kvm \
  -usbdevice tablet
```
![图片.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/48b40261-8e17-4fe3-a0a2-9d9ea97a9c01.png)

## 次回
次はNASMを用いて実際に簡単なファイルを書き込み、XP上で読み込まれるかを確認します。
又、今回はOSを起動するHDDのMBRとVBRを自分で研究してコードを書きました。
OSの入っていないただのHDDの場合にもMBRとVBRは生成されるようなので、実際にXPで初期化をしてどんなデータが生成されるのかを見てみます。
恐らく実行できる命令はないのではないかと思います。