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開発 DIRコマンドの実装

Last updated at Posted at 2025-06-28

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

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

ソース

やりたいこと

MSDOSの用なDIRコマンドを実装します。
いきなりサブディレクトリを実装するのは難しいため、一旦ルートディレクトリのみの実装とします。
よってcdコマンドを使ってフォルダ内のファイルを表示するといったことはまだ出来ません。

動作

DIRを押すHDDに保存されているファイル一覧を表示する。
图片.png

今回追加したソース

ファイル 関数名 機能
driver/disk.asm read_multi_sector 指定LBAから複数セクタ分をメモリへ読み込む
filesystem/fat16.asm read_fat FAT表およびルートディレクトリをメモリ上へ配置
command/dir.asm dir ルートディレクトリを参照しファイル名を表示する

今回ファイルの中身は参照しないためFAT表はメモリ上へ読み込むのみで使用しません。

検証用データ

今までkernel.binのみ仮想HDDに登録していましたが、TEST1.TXT及びTEST2.TXTをルートディレクトリに追加しました。

boot/​fat16_init.asm
    ...省略...
    ; 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 100    ; 開始クラスタ番号(例:適当に100番とする)
    dd 1234   ; ファイルサイズ(バイト)

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

ソース

何セクタ目から何セクタ分をメモリの何番地へ読み込むかを指定して、HDD上のデータを読み込みます。

driver/disk.asm
driver/disk.asm
; read_multi_sector:
;  複数セクタの読み込み
;  BX = LBA下位16bit (LBA 0-15)
;  CX = LBA上位8bit  (LBA 16-23)
;  SI = 読み込みセクタ数
;  ES:DI = 転送先アドレス
;  現状CXの値には対応しておらず65536セクタを越えると0に戻る
;  今後対応する
;  初めはread_sectorを呼び出さず直接複数セクタを呼び出す関数を作る
;  予定であったがどうしても1セクタ以上読み込まれずこの形になった。

read_multi_sector:
    pusha              ; 全レジスタ退避

.next_sector:
    ; 現在のLBAで1セクタ読み込み
    call read_sector
    jc .error          ; エラーあれば終了

    ; 次のセクタへLBAインクリメント
    inc bl             ; LBA下位8bit++
    cmp bl, 0
    jne .skip_bh_inc
    inc bh             ; 下位16bitの上位8bit(bh)++
.skip_bh_inc:
    ; 転送先アドレスを512バイト進める
    add di, 512

    dec si
    jnz .next_sector

    popa
    clc                ; 成功フラグクリア
    ret

.error:
    mov si, disktest
    call print_string
    popa
    stc                ; エラーフラグセット
    ret



; =========================================================
; read_sector - IDE HDDから1セクタ(512バイト)読み込み
;
; 機能:
;   指定したLBAアドレスから1セクタ(512バイト)を
;   メモリ(ES:DI)へ転送する
;
; 呼び出し前の設定:
;   BX = LBAアドレス 下位16ビット(LBA 0-15)
;   CX = LBAアドレス 上位8ビット(LBA 16-23)
;   ES:DI = 転送先メモリアドレス(512バイト以上の領域)
;
; 戻り値:
;   CF = 0 正常終了
;   CF = 1 エラー発生
;
; 補足:
;   常に512バイト固定の読み込み
; =========================================================

read_sector:
    pusha
    push ds
    push es
    
    ; 引数を保存
    mov [lba_low16_bx], bx  ; BXレジスタをスタックに保存(bl、bhを含む)
    mov [lba_high8_cx], cx  ; CXレジスタをスタックに保存(clを含む)
    
    ; ===== 1. HDDが使用可能か =====
    mov dx, 0x1F7       ; 状態レジスタポート
    mov cx, 0xFFFF      ; 最大試行回数
.wait_not_busy:
    in al, dx
    test al, 0x80       ; 最高位ビット=1かどうか(0x80 = 1000 0000)
    jz .ready           ; 1でなければ使用可能 →readyへ遷移
    dec cx              ; 試行回数を減算
    jnz .wait_not_busy  ; 使用不可の場合は待ち続ける
    jmp .error          ; 時間切れ

.ready:

    ; ===== 2. どのドライブを選択するか​​と​​LBAアドレスの上位4ビット​​を設定 =====
    mov dx, 0x1F6       ; 「ドライブ/ヘッドレジスタ」
    mov al, 0xE0        ; 本文を参照
    or al, bh           ; 
    and al, 0xEF        ; bit4=0(固定)
    out dx, al
    
    ; ===== 3. 読み込みセクタ数の設定 =====
    mov dx, 0x1F2       ; セクタ数レジスタ​​:転送する​​セクタ数​​を指定するために使用
    mov al, 1           ; 1セクタ読み込み
    out dx, al

    ; ===== 4. LBAアドレスの指定 =====
    
    ; 引数を取り出す
    mov bx, [lba_low16_bx]
    mov cx, [lba_high8_cx]
    
    mov dx, 0x1F3       ; LBA 0-7
    mov al, bl          ; bl = LBA 0-7
    out dx, al

    mov dx, 0x1F4       ; LBA 8-15
    mov al, bh          ; bh = LBA 8-15
    out dx, al

    mov dx, 0x1F5       ; LBA 16-23
    mov al, cl   
    out dx, al
    
    ; ===== 5. 読み込み命令送信 =====
    mov dx, 0x1F7
    mov al, 0x20        ; 0x20​​: セクタ読み込み (参考 0x30​​: セクタ書き込み)
    out dx, al          ; 一般的な書き方 outb(0x1F7, 0x20) → 読み込み開始

    ; ===== 6. データ転送が可能となるのを待つ =====
    mov cx, 0xFFFF      ; 最大試行回数
.wait_data_ready:
    in al, dx
    test al, 0x80       ; 読み込み可能か
    jnz .wait_data_ready
    test al, 0x08
    jnz .data_ready
    dec cx
    jnz .wait_data_ready
    jmp .error

.data_ready:
    ; ===== 7. エラー確認 =====
    test al, 0x01       ; 状態レジスタ 0bit目=1はエラー
    jnz .error

    ; ===== 8. データ読み込み =====
    mov cx, 256         ; 512バイト(WORD単位で÷2) IDEデータレジスタ(0x1F0)は​​16ビット(2バイト)単位​​でデータを扱う
    mov dx, 0x1F0       ; データレジスタ(読み書きに使用される)
    cld                 ; 念のため明示的にDF=0に設定し、insw実行時に​​メモリアドレス(DI)を自動増加​​(+2)させる。std(DF=1)だと、アドレスが減少(-2)
    rep insw            ; ES:DIへ読み込む cxの回数だけinswを繰り返し、

    pop es
    pop ds
    popa
    clc                 ; 成功
    ret

.error:

    pop es
    pop ds
    popa
    stc                 ; 失败
    ret

disktest db  'disktest', 0x0D, 0x0A, 0
lba_low16_bx dw 0     ; LBA 下位16bit バッファ
lba_high8_cx dw 0     ; LBA 上位8bit バッファ(chは実質無意味)

FAT表及びルートディレクトリをメモリ上へ読み込む。

filesystem/fat16.asm
ilesystem/fat16.asm
; FAT表及びルートディレクトリを0x0000:9000へ読み込む
; 8 (FAT1) + 8 (FAT2) + 32 (ルートディレクトリ) = 48セクタ
; 64セクタ目から48セクタを読み込む(パーティション第63セクタ目〜、63セクタ目にはVBRを設置)

; メモリ上の分布
; FAT1は64セクタ目から8セクタ分、メモリの 0x0000:0x9000 に読み込む
; FAT2はその次の8セクタ分、つまり72〜79セクタ目、メモリの 0x0000:0xA000 に読み込む
; ルートディレクトリは80セクタ目から32セクタ分、メモリの 0x0000:0xB000 に読み込む

; 番地計算方法
; セクタサイズは512バイト(0x200)
; FAT1のサイズ:8セクタ × 512 = 4096 = 0x1000バイト
; FAT2の開始アドレスは FAT1の終了アドレス + 0x1000 → 0x9000 + 0x1000 = 0xA000
; ルートディレクトリ開始アドレスは FAT2の終了アドレス + 0x1000 → 0xA000 + 0x1000 = 0xB000

read_fat:
    pusha
    push es
    
    mov bl, 0x40           ; LBA 0-7 64セクタ目〜
    mov bh, 0x00           ; LBA 8-15
    mov cl, 0x00           ; LBA 16-23
    mov si, 48              ; 読み込みセクタ数 48
    
    xor ax, ax
    mov es, ax
    mov di, 0x9000         ; 転送先 0x0000:9000
    
    call read_multi_sector
    
    mov si, msg_fat_loaded
    call print_string
    
    pop es
    popa
    
    ret
    
msg_fat_loaded db '[Kernel] FAT info loaded at 0x0000:0x9000', 0x0D, 0x0A, 0

ルートディレクトリを参照し、ファイルの一覧を表示します。

command/dir.asm
command/dir.asm
; いづれは外部コマンド(DIR.COM)にする予定
; ファイル一覧を表示する
; 
dir:

    ; ルートディレクトリの先頭番地
    mov si, 0xB000

    ; 全512エントリを順番に見る
    mov cx, 512

.dir_loop:
    push cx           ; ループカウンタ退避
    push si           ; SI退避

    ; 最初の1バイトを確認(0なら未使用、0xE5は削除済み)
    mov al, [si]
    cmp al, 0
    je .skip_entry    ; 0ならスキップ
    cmp al, 0xE5
    je .skip_entry    ; 削除済みエントリはスキップ

    ; ファイル名(8+3)を表示
    mov bx, si
    call print_filename_83   ; ファイル名表示サブルーチン(後述)

    ; 改行
    mov si, newline
    call print_string

.skip_entry:
    pop si
    add si, 32         ; 次のエントリへ
    pop cx
    loop .dir_loop

ret


; 入力: BX = エントリ先頭番地
print_filename_83:
    pusha

    mov si, bx          ; SI = エントリ先頭

    ; 8文字のファイル名表示
    mov cx, 8
.print_name:
    mov al, [si]
    cmp al, ' '         ; スペースなら終端
    je .skip_spaces
    call putchar_direct
    
.skip_spaces:
    inc si
    loop .print_name

    ; ドットを表示
    mov al, '.'
    call putchar_direct

    ; 拡張子(3文字)
    mov cx, 3
.print_ext:
    mov al, [si]
    cmp al, ' '
    je .skip_ext_spaces
    call putchar_direct
    
.skip_ext_spaces:
    inc si
    loop .print_ext

    popa
    ret

XPにて検証

ファイルとして認識されている
图片.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
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?