「手探りで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
[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
[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
; 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
# 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表の中身がうまく設定出来ていないのかもしれない。
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
今後の予定
各ファイルの開始クラスタ、ファイルの大きさなどを再確認してどういう時は読み込みに失敗してどういう時に成功するかを調べて原因を探っていく。