0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CUIOS開発 FAT16の構造1

Last updated at Posted at 2025-07-01

「手探りでCUI OS作成に挑む」連載

この記事は「手探りでCUI OS作成に挑む」連載の一部です。
全体の目次は「手探りでCUI OS作成に挑む」連載目次を御覧下さい。

ソース

https://github.com/ooe1220/sourouOS/tree/20250628/boot
今回の分はGITに上げていません。

目的

現在自作しているCUIOSでファイルシステムを実装するにあたり、WindowsやMS-DOSからも読み込みが可能な形式にしたいと思っております。
現在CUIOSを書き込んでいる仮想HDDをWindowsXP上で読み込むと、ファイル名は確認できるのですが開けません。
そこでFAT16に関わる部分を全てみなおしました。
以下は昨日Ubuntu上で仮想HDDをFAT16形式で初期化しVBRを分析した記事です。
https://qiita.com/earthen94/items/d084c7596ef8191abc47

方法

128MBの仮想HDDを作成する。
実際に使用するのはそのうちの120MB。
1クラスタ=4セクタに設定する。
FAT表及びルートディレクトリはfat16_init.asmで生成する。
OSの中核となるKERNEL.BIN,TEXT1.TXT,TEXT2.TXTをFAT表及びルートディレクトリに書き込む。
WindowsXP上で確認する

変更した部分

boot/mbr.asm
boot/mbr.asm
[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
    
    mov si, msg_loading
    call print_string
    
    ; 0x0600にジャンプして処理を続行
    jmp 0x0000:(continue - start + 0x0600)

continue:

    mov si, msg_relocated
    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 "[MBR] Execution started at 0x0000:0x7C00", 0x0D, 0x0A, 0
msg_relocated db "[MBR] Relocated to 0x0000:0x0600", 0x0D, 0x0A, 0
msg_disk_error db "Disk read error!", 0x0D, 0x0A, 0

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

; パーティションテーブル(FAT16のパーティション)
db 0x80               ; アクティブパーティション
db 0x01, 0x01, 0x00   ; CHS開始位置(0,1,1)
db 0x04               ; パーティションタイプ(FAT16 < 512MB → 0x04、512MB超 → 0x06)
db 0xFF, 0xFF, 0xFF   ; CHS終了(最大値、LBA使用前提なら適当でも可)
dd 63                 ; 開始LBA(63セクタ目)
dd 245760 - 63       ; パーティションのセクタ数=総セクタ数 - 開始位置

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

dw 0xAA55
boot/vbr.asm
boot/vbr.asm
[BITS 16]
[ORG 0x7C00]  ; VBRはアドレス0x7C00に読み込まれる

; FAT16のBPB
jmp start
nop
db "SOUROUOS"     ; OEM名(8bitに満たない場合は空白埋め)
dw 512              ; バイト/セクタ(0x0200)
db 4                ; セクタ/クラスタ(2KBクラスタ)
dw 1                ; 予約セクタ数(通常は1)
db 2                ; FAT数
dw 512              ; ルートディレクトリエントリ数(512×32=16KB)
dw 0                ; 総セクタ数(16bit領域、0で代わりにTotSec32を使用)
db 0xF8             ; メディアタイプ(固定ディスク)
dw 250              ; FATサイズ(セクタ数、後述計算)
dw 63               ; セクタ/トラック(LBA互換)
dw 255              ; ヘッド数(LBA互換)
dd 0                ; 隠しセクタ数
dd 245760           ; 総セクタ数(120MB ÷ 512)
; 拡張BPB (FAT16用)
db 0x80          ; BIOSドライブ番号(0x80=HDD)
db 0                ; 予約
db 0x29             ; 拡張ブートシグネチャ
dd 0x12345678       ; ボリュームID(任意)
db 'NO NAME    '    ; ボリュームラベル
db 'FAT16   '       ; ファイルシステム名

start:

    mov si, msg_loaded
    call print_string

    ; kernelは128セクタ分(65536バイト=64KB),LBA=596
    ; KERNEL.BIN は LBA 596 セクタ目から始まる
    mov ah, 0x02
    mov al, 128         ; 128セクタ読み込む
    mov ch, 0           ; Cylinder 0
    mov cl, 30          ; Sector 30
    mov dh, 9           ; Head 9
    mov dl, 0x80        ; HDD
    mov bx, 0x8000      ; 読み込み先アドレス
    int 0x13
    jc load_error

    jmp 0x0000:0x8000 ; kernelの開始アドレスへ跳ぶ

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

msg_loaded db "[VBR] Execution started at 0x0000:0x7C00", 0x0D, 0x0A, 0
error_msg db "Failed to load KERNEL.BIN", 0x0D, 0x0A, 0

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

dw 0xAA55
​boot/fat16_init.asm
​boot/fat16_init.asm
; FAT表及びルートディレクトリを初期化する。
; DDで64セクタ目からの位置に書き込む。
; 固定でKERNEL.BINを一つ登録しておく。
; 前提:
;   - MBRは0セクタ目に設置
;   - VBRは63セクタ目に設置(パーティションはMBR中で63セクタ目から始まるように設定してある)
;   - FATの情報は64から始まる(VBRの直後に設置)

bits 16

; ------------------------------
; FAT1表LBA 64〜始まる (DDでこの場所に置く) 
; ------------------------------
fat1:
    dw 0xFFF8          ; クラスタ0:予約
    dw 0xFFFF          ; クラスタ1:予約

    dw 3               ; クラスタ2 → 次は3
    dw 4               ; クラスタ3 → 次は4
    dw 5               ; クラスタ4 → 次は5
    dw 6               ; クラスタ5 → 次は6
    dw 7               ; クラスタ6 → 次は7
    dw 8               ; クラスタ7 → 次は8
    dw 9               ; クラスタ8 → 次は9
    dw 10              ; クラスタ9 → 次は10
    dw 11              ; クラスタ10 → 次は11
    dw 12              ; クラスタ11 → 次は12
    dw 13              ; クラスタ12 → 次は13
    dw 14              ; クラスタ13 → 次は14
    dw 15              ; クラスタ14 → 次は15
    dw 16              ; クラスタ15 → 次は16
    dw 17              ; クラスタ16 → 次は17
    dw 18              ; クラスタ17 → 次は18
    dw 19              ; クラスタ18 → 次は19
    dw 20              ; クラスタ19 → 次は20
    dw 21              ; クラスタ20 → 次は21
    dw 22              ; クラスタ21 → 次は22
    dw 23              ; クラスタ22 → 次は23
    dw 24              ; クラスタ23 → 次は24
    dw 25              ; クラスタ24 → 次は25
    dw 26              ; クラスタ25 → 次は26
    dw 27              ; クラスタ26 → 次は27
    dw 28              ; クラスタ27 → 次は28
    dw 29              ; クラスタ28 → 次は29
    dw 30              ; クラスタ29 → 次は30
    dw 31              ; クラスタ30 → 次は31
    dw 32              ; クラスタ31 → 次は32
    dw 0xFFFF          ; クラスタ32 → EOF (終端マーク)

    ; TEXT1.TXTクラスタチェーン(1クラスタ分)
    dw 0xFFFF  ; TEXT1.TXT は1クラスタで終端
    
    ; TEXT2.TXTクラスタチェーン
    dw 0xFFFF

    ; FAT表の剰余分を0で埋める (250セクタ×512バイト-既に書き込んだ分のバイト数)
    times 128000 - ($ - fat1) db 0

; ------------------------------
; FAT2表(FAT表1の予備 FAT1の直後)簡略化の為に0埋め
; ------------------------------
fat2:
    dw 0xFFF8          ; クラスタ0:予約
    dw 0xFFFF          ; クラスタ1:予約

    dw 3               ; クラスタ2 → 次は3
    dw 4               ; クラスタ3 → 次は4
    dw 5               ; クラスタ4 → 次は5
    dw 6               ; クラスタ5 → 次は6
    dw 7               ; クラスタ6 → 次は7
    dw 8               ; クラスタ7 → 次は8
    dw 9               ; クラスタ8 → 次は9
    dw 10              ; クラスタ9 → 次は10
    dw 11              ; クラスタ10 → 次は11
    dw 12              ; クラスタ11 → 次は12
    dw 13              ; クラスタ12 → 次は13
    dw 14              ; クラスタ13 → 次は14
    dw 15              ; クラスタ14 → 次は15
    dw 16              ; クラスタ15 → 次は16
    dw 17              ; クラスタ16 → 次は17
    dw 18              ; クラスタ17 → 次は18
    dw 19              ; クラスタ18 → 次は19
    dw 20              ; クラスタ19 → 次は20
    dw 21              ; クラスタ20 → 次は21
    dw 22              ; クラスタ21 → 次は22
    dw 23              ; クラスタ22 → 次は23
    dw 24              ; クラスタ23 → 次は24
    dw 25              ; クラスタ24 → 次は25
    dw 26              ; クラスタ25 → 次は26
    dw 27              ; クラスタ26 → 次は27
    dw 28              ; クラスタ27 → 次は28
    dw 29              ; クラスタ28 → 次は29
    dw 30              ; クラスタ29 → 次は30
    dw 31              ; クラスタ30 → 次は31
    dw 32              ; クラスタ31 → 次は32
    dw 0xFFFF          ; クラスタ32 → EOF (終端マーク)

    ; TEXT1.TXTクラスタチェーン(1クラスタ分)
    dw 0xFFFF  ; TEXT1.TXT は1クラスタで終端
    
    ; TEXT2.TXTクラスタチェーン
    dw 0xFFFF

    ; FAT表の剰余分を0で埋める (250セクタ×512バイト-既に書き込んだ分のバイト数)
    times 128000 - ($ - fat2) db 0

; ------------------------------
; ルートディレクトリ(FAT2の直後,LBA 80)
; ------------------------------
root_dir:
    ; KERNEL.BINの登録(32字节)
    db 'KERNEL  BIN'          ; ファイル名(8.3形式)
    db 0x20                   ; 属性(0X20は通常のファイルを意味する)
    db 0                      ; 保留
    db 0                      ; 作成時間(ミリ秒)
    dw 0x0000                 ; 作成時間(16:00:00)
    dw 0x2100                 ; 作成日時(2023-01-01)
    dw 0x2100                 ; 最終変更日時
    dw 0                      ; EA索引
    dw 0x0000                 ; 最終変更時間
    dw 0x2100                 ; 最終変更日時
    dw 2   ; 開始クラスタ
    dd 65536                  ; ファイルの大きさ(バイト)
    
    ; TEST1.TXTの登録
    db 'TEST1   TXT'          ; ファイル名(8.3形式)
    db 0x20                   ; 属性
    db 0
    db 0
    dw 0x0000
    dw 0x2100
    dw 0x2100
    dw 0
    dw 0x0000
    dw 0x2100
    dw 34    ; 開始クラスタ番号
    dd 512   ; ファイルサイズ(バイト)

    ; TEST2.TXTの登録
    db 'TEST2   TXT'
    db 0x20
    db 0
    db 0
    dw 0x0000
    dw 0x2100
    dw 0x2100
    dw 0
    dw 0x0000
    dw 0x2100
    dw 35    ; 開始クラスタ番号
    dd 1024   ; ファイルサイズ

    ; ルートディレクトリが32セクタとなるように0で埋める。
    times 512 * 32 - ($ - root_dir) db 0
exec.sh
exec.sh
# bash exec.shで権限無視して実行可能 

#!/bin/bash
set -e  # エラーが出たら即終了

clear
# コンパイル
cd boot # nasm -f bin ​boot/fat16_init.asm -o fat16_init.binがなぜか実行できない為苦肉の策
nasm -f bin mbr.asm -o mbr.bin
nasm -f bin vbr.asm -o vbr.bin
nasm -f bin ​fat16_init.asm -o fat16_init.bin
cd ..
nasm -f bin kernel.asm -o kernel.bin

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

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

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

# ルートディレクトリ及びFAT表を64バイト目〜「第1パーティションの2セクタ目」に書き込み
dd if=boot/fat16_init.bin of=virtual_disk.img bs=512 seek=64 conv=notrunc

# カーネル部分
dd if=kernel.bin of=virtual_disk.img bs=512 seek=596 conv=notrunc

# 検証用ファイル
dd if=testfile/TEST1.TXT of=virtual_disk.img bs=512 seek=724 count=1 conv=notrunc
dd if=testfile/TEST2.TXT of=virtual_disk.img bs=512 seek=728 count=2 conv=notrunc

# 一時ファイル削除
rm -f boot/mbr.bin
rm -f boot/vbr.bin
rm -f boot/fat16_init.bin
rm -f kernel.bin

# 起動する
qemu-system-i386 -hda virtual_disk.img -monitor stdio

Windows上で確認

TEXT1.TXTは開けたが、TEXT2.TXTは開けない。
KERNEL.BINをHxDで開こうとしたがエラーが出た。
FAT表の中身がうまく設定出来ていないのかもしれない。
图片.png
图片.png

  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

今後の予定

各ファイルの開始クラスタ、ファイルの大きさなどを再確認してどういう時は読み込みに失敗してどういう時に成功するかを調べて原因を探っていく。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?