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?

MBR→VBR→カーネルの順に起動する雛形(自作OS)

Last updated at Posted at 2025-06-18

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

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

前回まで

前回までにMBR及びVBRを実装し、FAT16の要であるルートディレクトリ及びFAT表を実装しました。

詳細は以下の記事をご参照下さい。
MBR・VBR自作でFAT16として認識させる
FAT16ファイルシステムを手書きで作る

今回の目的

今回はkernel.binの中に処理を書き、VBRから起動するところを実装します。
MBR及びVBRはファイルとして認識されませんが、MSDOSのようにカーネル部分はファイルとして認識されるように設計します。

MBR:パーティションの設定及びVBRの読み込み
VBR:FAT16が使う設定値をまとめたBPBの登録及びkernel.binの起動
kernel.bin:OSの中核機能は主にこの中で実装していく

実装部分

mbr.asm(変更無し)
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
    
    ; 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

前回までは"FAT16 VBR Loaded!"と文字列を表示するだけで何もしませんでしたが、
112セクタ目から64KBバイト分のkernel.binをメモリ0x8000番地へ読み込み、起動するコードを追加しました。

vbr.asm
[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

    ; >>>>>>>>今回追加>>>>>>>>
    ; kernelは128セクタ分(65536バイト=64KB),LBA=112
    ; KERNEL.BIN は LBA 112 セクタ目から始まる
    mov ah, 0x02
    mov al, 128 ; 128セクタ読み込む
    mov ch, 0 ; 
    mov cl, 50 ; セクタ = LBA 112 -> CHS
    mov dh, 1 ; ヘッド
    mov dl, 0x80 ; HDD
    mov bx, 0x8000 ; メモリ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 "FAT16 VBR Loaded!", 0x0D, 0x0A, 0
; >>>>>>>>今回追加>>>>>>>>
error_msg db "Failed to load KERNEL.BIN", 0x0D, 0x0A, 0
; <<<<<<<<今回追加<<<<<<<<

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

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

bits 16

; ------------------------------
; 設定値。VBRと矛盾しないよう注意
; ------------------------------
sectors_per_fat    equ 8      ; 一つのFAT表は8セクタを占める
bytes_per_sector   equ 512    ; 1セクタは512バイト
root_dir_entries   equ 512    ; ルートディレクトリに登録可能なファイル数
kernel_start_cluster equ 2    ; KERNEL.BINが始まるクラスタ番号
kernel_size        equ 65536  ; 文件大小(字节)
%define cluster_count (kernel_size / bytes_per_sector) ; KERNEL.BINが何セクタを占めるか

; ------------------------------
; FAT1表LBA 64〜始まる (DDでこの場所に置く)
; ------------------------------
fat1:
    ; 初めの2クラスタ分は実質固定
    dw 0xFFF8 ; F8はHDD、FF固定
    dw 0xFFFF ; ファイルの最後のクラスタの目印

    ; KERNEL.BINクラスタの連なり(2 → 3 → ... → EOF)
    %assign i kernel_start_cluster
        %rep cluster_count
        %if i == kernel_start_cluster + cluster_count - 1 ;最後のクラスタには目印0xFFFFを置く
            dw 0xFFFF
        %else
            dw i + 1
        %endif
        %assign i i + 1
    %endrep

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

; ------------------------------
; FAT2表(FAT表1の予備 FAT1の直後)簡略化の為に0埋め
; ------------------------------
fat2:
    ; 表1個8セクタ×512バイト/表
    times sectors_per_fat * bytes_per_sector 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 kernel_start_cluster   ; 開始クラスタ
    dd kernel_size            ; ファイルの大きさ(バイト)

    ; ルートディレクトリが32セクタとなるように0で埋める。
    times root_dir_entries * 32 - ($ - root_dir) db 0

今回はMBR→VBR→カーネルの順に起動する処理に重点を置いて説明をしたかった為、
c:\>と表示し何か押されたら改行して又c:\>と表示するだけのOSの雛形を書きました。

kernel.asm
[BITS 16]
[ORG 0x8000]

start:
    ; レジスタ初期化
    xor ax, ax
    mov ds, ax
    mov es, ax
    
    ; スタック設定
    mov ss, ax
    mov sp, 0x7C00
    
    mov si, message
    call print_string
    
command_loop:
    ; c:\>を表示
    mov si, prompt
    call print_string
    
    ; キーボードから入力されるのを待つ(入力が終わるまで返ってこない)
    mov ah, 0x00
    int 0x16 
    
    ; command_loopへ戻りc:\>を表示して次の命令を待つ
    jmp command_loop

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

message db "Hello from 0x8000 kernel", 0x0D, 0x0A, 0
prompt db  0x0D, 0x0A,'C:\> ', 0

; ここで64KBまで0埋めする
times 65536-($-$$) db 0

動作確認

# コンパイル
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

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

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

# >>>>>>>>今回追加>>>>>>>>
# kernel.binのコンパイル
nasm -f bin kernel.asm -o kernel.bin

# 112セクタ目〜に書き込み
dd if=kernel.bin of=virtual_disk.img bs=512 seek=112 conv=notrunc
# <<<<<<<<今回追加<<<<<<<<

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

QEMUで実行したところMBR→VBR→カーネルの順に起動し、最後にエンターを押した分だけc:\>と表示されていることが確認出来ます。
图片.png

課題

XP上でこの仮想HDDをEドライブとして読み込みバイナリエディタでkernel.binを開こうとすると読み込みに失敗します。原因があるとすればVBR中のBPB、FAT表、ルートディレクトリに誤りがありkernel.binを書き込んだ場所と矛盾が生じているのかもしれません。
このままでも起動は出来ますがカーネルを1つのファイルとして実装する以上、開けないと意味が無いので今後修正する予定です。
图片.png
图片.png

今後の展望

今後は32ビットモード(プロテクトモード)へ切り替える予定ですが、その場合BIOS呼び出しが直接使えなくなるため、基本的な入出力処理を自力で実装していきます。具体的にはキーボード入力、画面表示、ディスク読み込みなどのドライバ機能を作成し、BIOSに依存しない環境を整えます。

また、COMファイル(EXEファイルの原型で、単純な実行形式)の実行機能をOSに実装する予定です。これにより、自作OS上で簡単なアプリの動作が可能になります。

COMファイルの実行環境が整った後は、アセンブリ言語を駆使して三角関数を用いた3Dモデル描写アプリの作成を計画しています。
(最近ゲーム実況をよく見ているのですが、立体の描写の仕組みが気になりすぎて、内容が全然頭に入ってこないので、原理から研究して自分で実装することにしました……)

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?