「手探りでCUI OS作成に挑む」連載
この記事は「手探りでCUI OS作成に挑む」連載の一部です。
全体の目次は「手探りでCUI OS作成に挑む」連載目次を御覧下さい。
やりたいこと
現在CUIのOSを開発中です。以下は開発中のソースです。
https://github.com/ooe1220/sourouOS/tree/20250628
COMファイルを実行する雛形を作ります。
まだFAT表を解析してファイルを読み込む部分を実装していないので一旦4セクタ目に固定でCOMファイルを設置して、実行します。
実装するCOMファイル
先にこのCOMファイルがMS-DOS上で動くことを確認します。
hello.asm
org 0x100 ; COMは必ず0x100から開始(セグメントは自由)
mov ah, 0009h
mov dx, msg
int 21h
mov ax, 4C00h
int 21h
msg db 'Hello, World!$'
times 512 - ($ - $$) db 0 ; 1セクタ分埋める
# コンパイル
nasm -f bin hello.asm -o hello.com
# 空のフロッピーイメージ作成 FAT12で初期化
dd if=/dev/zero of=floppy.img bs=512 count=2880
mkfs.fat -F 12 floppy.img
# フロッピーにhello.comを入れる
sudo mkdir /mnt/floppy
sudo mount -o loop floppy.img /mnt/floppy
sudo cp hello.com /mnt/floppy
sudo umount /mnt/floppy
## QEMUで起動
qemu-system-i386 -fda floppy.img -hda msdos_hdd.img -boot c
実装
ブートローダ(今回の重点ではない)
boot.asm
boot.asm
; 第2セクタを読み込み、実行するブートローダー
[bits 16]
org 0x7C00
start:
; セグメント初期化
xor ax, ax
mov ds, ax
mov es, ax
; スタック設定
mov ss, ax
mov sp, 0x7C00
mov si, msg1
call print_string
; ディスクから第2セクタを 0x7E00 に読み込む
mov ah, 0x02 ; INT 13h サービス 2: 読み込み
mov al, 3 ; 読み込むセクタ数 = 3
mov ch, 0 ; シリンダ = 0
mov cl, 2 ; セクタ番号 = 2(1始まり)
mov dh, 0 ; ヘッド = 0
mov dl, 0x80 ; ドライブ番号
mov bx, 0x7E00 ; 読み込み先アドレス
int 0x13
jc disk_error ; エラー時に無限ループ
; 読み込んだコードへ跳ぶ(第2セクタ)
jmp 0x0000:0x7E00
disk_error:
mov si, msg2
call print_string
jmp $
print_string:
pusha
mov ah, 0x0E ;1文字出力
mov bh, 0
.print_loop:
lodsb ;SIの示すアドレスから1バイトをALに読み込み、SIを+1(次の文字へ)
test al, al ;alが0かどうかを確認する
jz .done ;文字が 0 か(=終端記号か)を確認。※'hello',0 文字列の最後に目印の0を入れている
int 0x10 ;BIOS割り込みで AL の文字を表示
jmp .print_loop
.done:
popa
ret
msg1 db 'boot loader', 0x0D, 0x0A,0
msg2 db 'disk_error', 0x0D, 0x0A,0
times 510-($-$$) db 0
dw 0xAA55
検証用のコード
INT21Hを割り込み表(IVT)へ登録
COMファイルを0x0200:0x0100へ読み込む
0x0200:0x0100へ跳ぶ
testcode.asm
testcode.asm
[bits 16]
[org 0x7E00]
start:
; レジスタ初期化
xor ax, ax
mov ds, ax
mov es, ax
; スタック設定
mov ss, ax
mov sp, 0x7C00
; INT21Hを割り込み表へ登録
mov word [0x21 * 4], int21_handler
mov word [0x21 * 4 + 2], cs
; COMファイルは4セクタ目に保存されている
mov bl, 0x03 ; LBA 0-7 4セクタ目〜
mov bh, 0x00 ; LBA 8-15
mov cl, 0x00 ; LBA 16-23
mov si, 0x01 ; 読み込みセクタ数 1
mov ax, 0x0200
mov es, ax
mov di, 0x0100 ;転送先 0x0200:0x0100
call read_multi_sector
jmp 0x0200:0x0100
cli
hlt
%include "int21.asm"
%include "driver/disk.asm"
%include "driver/vga.asm"
times 1024-($-$$) db 0
MS-DOSシステムコールの再現
今回の重点は9番の文字列表示機能
int21.asm
int21.asm
; int 21h ハンドラ
int21_handler:
pusha
; DSを呼び出し元のCSに合わせる
push ax
mov ax, 0x0200
mov ds, ax
pop ax
cmp ah, 1
je get_key
cmp ah, 2
je put_char
cmp ah, 9
je printstring
cmp ah, 4Ch
je int21_exit
jmp unknown_function
; AH=01h キーボード入力 (結果ALに)
get_key:
mov ah, 0
int 16h
popa
iret
; AH=02h 文字表示 (DLに文字)
put_char:
mov ah, 0x0E
mov al, dl
int 10h
popa
iret
; AH=09h $終端文字列表示
printstring:
push ds ; DSを退避
push si ; SIを退避
mov si, dx ; DXが文字列のオフセットと想定(DSセグメント)
print_loop:
lodsb ; AL = [DS:SI], SI++
cmp al, '$' ; '$' で終わり?
je print_done
mov ah, 0x0E
mov bh, 0x00
mov bl, 0x07 ; 文字属性(標準)
int 0x10 ; 文字表示
jmp print_loop
print_done:
pop si
pop ds
popa
iret
; AH=4Ch 終了処理(カーネルに戻る等)
int21_exit:
popa
; ここで終了処理。例: 無限ループで停止
cli
hlt
unknown_function:
popa
iret
動作確認
# コンパイル
nasm boot.asm -o boot.bin # 1セクタ
nasm testcode.asm -o testcode.bin # 2セクタ
nasm -f bin hello.asm -o hello.com # 1セクタ
# 結合
cat boot.bin testcode.bin hello.com > os.img
qemu-system-i386 -hda os.img -monitor stdio
今後
今回はCOMファイルを固定の場所に設置して読み込みましたが、今後はFAT表及びルートディレクトリにファイルとして書き込み、そこから起動できるようにします。
具体的には以下の用に起動できるようにするつもりです。
C:\>DIR
hello.com
C:\>hello.com
Hello,World!
又現在はint10hを用いて文字列を表示しているが、現在開発中のOSではVRAMへ直書きして文字を表示しているため、この部分も書き直す。