「手探りでCUI OS作成に挑む」連載
この記事は「手探りでCUI OS作成に挑む」連載の一部です。
全体の目次は「手探りでCUI OS作成に挑む」連載目次を御覧下さい。
目的
これまで使用していたDISK読み込み関数は読みこむセクタが数十を超えると失敗したり、読みこむLBAと読み込み先のアドレス指定が複雑だったりと、問題を抱えていました。
そこで早い内にこの関数を改良しておくことにしました。
今までの関数はdriver/disk.asmの中に入っています。
https://github.com/ooe1220/sourouOS/tree/20250628/boot
改良版
;---------------------------------------------------
; 任意LBAの任意セクタ数を任意メモリへ読み込む
; 入力:
; DX = 保存先セグメント (ESに設定される)
; BX = 保存先オフセット (DIに設定される)
; CX:SI = LBAアドレス (32bit)
; AL = 読み込むセクタ数 (最大128)
;---------------------------------------------------
read_sectors:
; 引数をメモリへ退避(レジスタを上書きする前に保存)
mov [tmp_sectors], al ; セクタ数
mov [tmp_seg], dx ; 保存先セグメント
mov [tmp_ofs], bx ; 保存先オフセット
mov [tmp_lba_low], si ; LBA下位16bit
mov [tmp_lba_high], cx ; LBA上位16bit
; 必要レジスタ退避(呼び出し規約に従って保存)
push ax
push bx
push cx
push dx
push si
push di
push es
mov dx, [tmp_seg] ; ESセグメント設定用
mov bx, [tmp_ofs] ; DIオフセット設定用
; メモリ転送用設定
mov es, dx ; 保存先セグメントをESに設定
mov di, bx ; 保存先オフセットをDIに設定
;-----------------------------------------
; IDEコントローラへのLBA設定
;-----------------------------------------
; 0x1F6: ドライブ/ヘッドレジスタ
; bit7-4: 0xE0 (LBAモード + マスタードライブ)
; bit3-0: LBAアドレスのbit24-27
mov dx, 0x1F6
mov cx, [tmp_lba_high] ; LBA上位
mov ah, ch ; LBA bit24-27 (CXの上位8bit)
and ah, 0x0F ; 下位4bitのみ有効
mov al, 0xE0 ; LBAモード + マスタードライブ
or al, ah ; LBAアドレス上位と結合
out dx, al
; 0x1F2: セクタカウントレジスタ
mov dx, 0x1F2
mov al, [tmp_sectors] ; 読み込むセクタ数
out dx, al
; 0x1F3: セクタ番号レジスタ (LBA bit0-7)
mov dx, 0x1F3
mov si, [tmp_lba_low] ; LBA下位
mov ax, si ; LBA下位16bit (SI)
out dx, al ; bit0-7を出力
; 0x1F4: シリンダ低位レジスタ (LBA bit8-15)
mov dx, 0x1F4
shr ax, 8 ; bit8-15をALに
out dx, al
; 0x1F5: シリンダ高位レジスタ (LBA bit16-23)
mov dx, 0x1F5
mov cx, [tmp_lba_high] ; LBA上位
mov al, cl ; LBA bit16-23 (CXの下位8bit)
out dx, al
; 0x1F7: コマンドレジスタ
mov dx, 0x1F7
mov al, 0x20 ; 読み込みコマンド (READ SECTORS)
out dx, al
;-----------------------------------------
; データ読み込み処理
;-----------------------------------------
mov byte [already_read_sectors], 0 ;既に読みこんだセクタ数を数える
.next_sector:
; データ準備完了(DRQ)を待つ
.wait_drq:
mov dx, 0x1F7
in al, dx
test al, 8 ; bit3 (DRQ)が立っているか
jz .wait_drq ; 準備できてなければ待機
; 1セクタ(512バイト=256ワード)を転送
mov cx, 256 ; ループカウンタ
mov dx, 0x1F0 ; データポート
.read_word:
in ax, dx ; データポートから1ワード(2バイト)読み込み
mov [es:di], ax ; メモリに保存
add di, 2 ; 次のワード位置へ
loop .read_word
; 次のセクタへ
inc byte [already_read_sectors] ; 読み込んだセクタ数を増やす
mov al, [already_read_sectors]
cmp al, [tmp_sectors] ; 総セクタ数と比較
jb .next_sector ; 未完なら継続
;-----------------------------------------
; 終了処理
;-----------------------------------------
; レジスタ復旧(退避した逆順で)
pop es
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
;-----------------------------------------
; データ領域
;-----------------------------------------
section .data
tmp_sectors db 0 ; 読み込むセクタ数
tmp_seg dw 0 ; 保存先セグメント
tmp_ofs dw 0 ; 保存先オフセット
tmp_lba_low dw 0 ; LBAアドレス下位
tmp_lba_high dw 0 ; LBAアドレス上位
already_read_sectors db 0 ; 読み込み済みセクタカウンタ(0初期化)
呼び出し部分
[bits 16]
[org 0x7E00]
start:
; レジスタ初期化
xor ax, ax
mov ds, ax
mov es, ax
; スタック設定
mov ss, ax
mov sp, 0x7C00
mov dx, 0x900 ; 保存先セグメント (ES = 0x200)
mov bx, 0x100 ; 保存先オフセット (DI = 0x100)
mov cx, 0x0000 ; LBA上位16bit (200 < 2^16なので0)
mov si, 201 ; LBA下位16bit (200 = 0x00C8)
mov al, 100 ; 読み込むセクタ数 (1セクタ)
call read_sectors ; ディスク読み込み実行
;;;;
mov si,testmsg
call print_string
;;;;
jc .read_error ; エラー処理
.read_error:
cli
hlt
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
%include "disk.asm"
testmsg db "aaaaaaa"
times 510-($-$$) db 0
dw 0xAA55
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, 1 ; 読み込むセクタ数 = 4
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
100セクタ(128kb)分1埋めしたデータを作る。
times 51200 db 1
動作確認
# 仮想HDD生成128MB
dd if=/dev/zero of=os.img bs=1M count=128
nasm boot.asm -o boot.bin
nasm testcode.asm -o testcode.bin
dd if=boot.bin of=os.img bs=512 seek=0 conv=notrunc
dd if=testcode.bin of=os.img bs=512 seek=1 conv=notrunc
# 検証用データをLBA201の位置へ複製
dd if=testdata.bin of=os.img bs=512 seek=201 conv=notrunc
qemu-system-i386 -hda os.img -monitor stdio
実行中にQEMU上から
メモリ0x0900:0x100から100セクタ分のダンプを見ると
無事読み込まれている。
(qemu) xp /51200bx 0x9100
0000000000009100: 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01
0000000000009108: 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01
...省略...
00000000000158f0: 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01
00000000000158f8: 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01
原理
0x1F6:
ビット | 意味 |
---|---|
7 | 1=LBAモード, 0=CHSモード |
6 | 1=マスタードライブ, 0=スレーブドライブ |
5 | 常に1 |
4 | 0=ドライブ選択 |
3-0 | LBAアドレスのbit24-27 (CHSモード時はヘッド番号) |
よって上位4ビットは1110(=0xE)を設定
下位4ビットはLBA bit24-27を設定
0x1F2:読み込むセクタ数を設定
0x1F3:LBA bit0-7を設定
0x1F4:LBA bit8-15を設定
0x1F5:LBA bit16-23を設定
0x1F7:0x20(データ転送開始の指令)を設定
0x1F7番口へ0x20を送信したあと
0x1F7番口から取得したデータのbit3(4ビット目)が1かどうかを確認して
0x1F0番口からデータを1セクタずつ取得して[es:di]へ格納していく。
0x1F7番口のbit3が1で無ければまだデータの転送準備が整っていないのでループして待つ。
毎セクタbit3を確認しなければならない。