0から作るソフトウェア開発
日々勉強中。。。 |
Follow @Nina_Petipa |
ブートローダその11 FAT12ファイルシステムを読み込む |
;================================================================================== ; BIOS parameter blocks(FAT12) ;==================================================================================
JMP BOOT ;BS_jmpBoot BS_OEMName DB "MyOS " BPB_BytsPerSec DW 0x0200 ;BytesPerSector BPB_SecPerClus DB 0x01 ;SectorPerCluster BPB_RsvdSecCnt DW 0x0001 ;ReservedSectors BPB_NumFATs DB 0x02 ;TotalFATs BPB_RootEntCnt DW 0x00E0 ;MaxRootEntries BPB_TotSec16 DW 0x0B40 ;TotalSectors BPB_Media DB 0xF0 ;MediaDescriptor BPB_FATSz16 DW 0x0009 ;SectorsPerFAT BPB_SecPerTrk DW 0x0012 ;SectorsPerTrack BPB_NumHeads DW 0x0002 ;NumHeads BPB_HiddSec DD 0x00000000 ;HiddenSector BPB_TotSec32 DD 0x00000000 ;TotalSectors BS_DrvNum DB 0x00 ;DriveNumber BS_Reserved1 DB 0x00 ;Reserved BS_BootSig DB 0x29 ;BootSignature BS_VolID DD 0x20120627 ;VolumeSerialNumber 日付を入れました BS_VolLab DB "MyOS " ;VolumeLabel BS_FilSysType DB "FAT12 " ;FileSystemType
FAT領域の開始セクタ = ブートセクタの開始セクタ + 予約領域のサイズ(ブートセクタのサイズ)
= 0 + BPB_RsvdSecCnt
FAT領域のサイズ = BPB_FATSz16
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ ; ; Load FAT From Floppy ; ;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
LOAD_FAT: ; FATをアドレス0x7E00に読み込む MOV BX, WORD [BX_FAT_ADDR] ; FATを読み込むアドレス0x7E00を引数BXに入れる ADD AX, WORD [BPB_RsvdSecCnt] ; FATの開始セクタを取得 XCHG AX, CX ; FATの開始セクタを一旦CXレジスタに退避 MOV AX, WORD [BPB_FATSz16] ; FATのサイズを計算(FATのセクタ数を取得) MUL WORD[BPB_NumFATs] ; FATの予備領域も念のため読み込む ; AXの値×BPB_NumHeads→AXに格納 XCHG AX, CX ; CXにFATのサイズを、AXにFATの開始セクタを入れる READ_FAT: call ReadSector ; FATを1セクタずつ読み込む ADD BX, WORD [BPB_BytsPerSec] ; 1セクタ読み込んだので格納アドレスに512バイトを足す INC AX ; 次のセクタを読み込むのでAXに1をたす DEC CX ; FATのサイズ分読み込むので1セクタ読み込むごとに1減らす JCXZ FAT_LOADED ; DEC CXでZFが0になれば読み込み終わり JMP READ_FAT ; 次の処理へ FAT_LOADED: HLT
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ ; ReadSector ; Read 1 Sector ; Input: BX:読み込んだセクタを格納するアドレスを入れておく ; : AX:読み込みたいLBAのセクタ番号 ;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
ReadSector: MOV DI, 0x0005 ; エラー発生時5回までリトライする SECTORLOOP: PUSH AX ; AX、BX、CXをスタックに退避 PUSH BX PUSH CX CALL LBA2CHS ; AXのLBAを物理番号に変換 MOV AH, 0x02 ; セクタ読み込みモード MOV AL, 0x01 ; 1セクタ読み込み MOV CH, BYTE [physicalTrack] ; LBA2CHSで計算したトラック番号 MOV CL, BYTE [physicalSector] ; LBA2CHSで計算したセクタ番号 MOV DH, BYTE [physicalHead] ; LBA2CHSで計算したヘッド番号 MOV DL, BYTE [BS_DrvNum] ; ドライブ番号(Aドライブ) INT 0x13 ; BIOS処理呼び出し JNC SUCCESS ; CFを見て成功か失敗かを判断 XOR AX, AX ; ここからエラー発生時の処理。ドライブ初期化モード INT 0x13 ; エラーが発生した時はヘッドを元に戻す DEC DI ; エラーカウンタを減らす POP CX ; AX、BX、CXの値が変更されたので POP BX ; 退避したいたデータをスタックから元に戻す POP AX JNZ SECTORLOOP ; DEC DIの計算結果が0でなければ、セクタ読み込みをリトライ INT 0x18 ; 昔のソースなので何がしたかったのか???Set Media Type SUCCESS: POP CX ; 成功時の処理レジスタの値を元に戻す POP BX POP AX RET ;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ ; ; Variable ; ;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ BX_FAT_ADDR DW 0x7E00 ; データセグメントの0x7E00にFATを読み込む BS_DrvNum DB 0x00 ; ドライブ番号
クラスタ番号Nのオフセット = N + ( N / 2 )
偶数番号のクラスタの値 = クラスタ番号Nのオフセットからワードサイズ(2バイト)読み取る & 0x0FFF
奇数番号のクラスタの値 = クラスタ番号Nのオフセットからワードサイズ(2バイト)読み取る >> 4
次のソースコードは少し後の説明にでてくるファイルの読み込みの
LOAD_IMAGEラベルで使用するソースコードの一部です(ファイルの読み込み
ではNEW_CLUSTERラベルからLOAD_IMAGEラベルに変更しています)。
最初のMOV命令ではファイルの先頭のクラスター番号をclusterに格納
しています。NEW_CLUSTERではクラスターのチェインをファイルの終端に
達するまでたどっていきます。
MOV WORD [cluster], 0x0002 ; clusterが指すアドレスにはクラスタ読みたいクラスタ番号を格納します
NEW_CLUSTER:
MOV AX, WORD [cluster] ; 読みたいクラスタ番号をAX、CX、DXに入れます
MOV CX, AX
MOV DX, AX
SHR DX, 0x0001 ; ここで割る2をしてDXに入れます(DX=クラスタ番号/2)
ADD CX, DX ; CXにクラスタのオフセットを入れます
; (CX = クラスタ番号N+クラスタ番号/2)
MOV BX, WORD[BX_FAT_ADDR] ; BXに読み込んだFAT領域の先頭アドレスを入れます
ADD BX, CX ; BXに読みたいクラスタのアドレスを入れます
; (BX = FAT先頭アドレス+クラスタのオフセット)
MOV DX, WORD [BX] ; DXにBXが指すアドレスの値を入れます
; (クラスタの値をワードで読み取っている)
TEST AX, 0x0001 ; 奇数か、偶数か判定。TEST命令は0x0001とAXの値をANDします
; (結果は格納しませんが、ZFは更新されます)
JNZ ODD_CLUSTER ; ZFが0でない(AXが奇数)ならODD_CLUSTERにジャンプ
EVEN_CLUSTER: ; 偶数のときはここの処理へ
AND DX, 0x0FFF ; 12ビットだけとりだす
JMP LOCAL_DONE ; 読み取り完了したので、LOCAL_DONEへジャンプ
ODD_CLUSTER: ; 奇数の時はここの処理へ
SHR DX, 0x0004 ; 右に4ビットシフトする
LOCAL_DONE: ; クラスタの値を読み取れた後はここへ
MOV WORD [cluster], DX ; 読み取ったクラスタの値をclusterへ格納。次クラスタ用に保存。
CMP DX, 0x0FF0 ; 終端クラスタかどうかCMP命令でDXの値と0x0FF0を比較
; 比較はDXの値(クラスタから読み取った値)引く0x0FF0を行う
; 引いた結果0以上ならEFLAGSレジスタのCFフラグは0となる
; (すなわちDXが0x0FF0以上のときに0x0FF0を引いた結果は0以上)
; 引いた結果0以外ならCFフラグは1
; (通常のクラスタは0x0FF0以下なので必ずキャリーが発生する)
JB NEW_CLUSTER ; JB命令はEFLAGSレジスタのCFが1の場合NEW_CLUSTERへジャンプする
ファイルのセクタ番号 = ( クラスタ番号 - 2 ) × 1クラスタごとのセクタ数
= ( クラスタ番号 - 2 ) × BPB_SecPerClus
クラスタ番号から2を引いていますが、FAT領域の最初の2つ(0番目と1番目のクラスタ)は
次のソースコードも少し後の説明にでてくるファイルの読み込みの
LOAD_IMAGEラベルで使用するソースコードの一部です(先ほどのNEW_CLUSTER
ラベルの処理の前段階の処理となります)。この処理では、ファイルの最初のクラスター
番号がclusterに格納されているものとして、処理を行います。cluserに格納されて
いるクラスター番号から最終的にファイルのセクター番号を計算してその結果をAX
レジスターに格納する処理となります。
MOV AX, WORD[cluster] ; AXに読み込んだクラスタ番号を入れます
SUB AX, 0x0002 ; SUB命令でAXの値から2を引きます
XOR CX, CX ; CXレジスタを初期化します
MOV CL, BYTE [BPB_SecPerClus] ; CLレジスタ(CXの下位1バイト)に1クラスタごとのセクタ数をいれます
MUL CX ; MUL命令でAXの値とCXの値をかけます。結果はAXレジスタに格納されます
ルートディレクトリのエントリ | |||
---|---|---|---|
名前 |
オフセット (バイト) |
サイズ (バイト) |
説明 |
DIR_Name | 0 | 11 |
ファイル名(またはディレクトリ名)を入れます 原則的には8文字+拡張子3文字で記録されます 例えば”a.txt”というファイル名ではドット”.”は記録されずに ”A TXT”と記録されます。8文字に満たない場合は スペース(0x20)が8文字になるまで入ります 拡張子が無く11文字に満たないファイル名の場合は 後ろにスペース(0x20)を11文字になるまで入れます その他仕様書に記載されている例を挙げます
|
DIR_Attr | 11 | 1 |
ファイルの属性が入ります Windows7でファイルを作ると0x20が入っていました 0x01:読み取り専用のファイルです 0x02:隠しファイルとなります 0x04:OSが使うファイルとなります 0x08:ボリュームの名前となります 0x10:このファイルはディレクトリです。 0x20:このファイルは変更、削除、上書きされたときにバックアップされます (バックアップに対応したドライバが必要です) 0x0F:ロングネーム(11文字以上の名前)用のファイルです |
DIR_NTRes | 12 | 1 | Windows NT用の予約領域です |
DIR_CrtTimeTenth | 13 | 1 |
タイムスタンプが記録されます (ただしミリ秒で1/10秒単位です。例えば0x64は10秒を意味します) |
DIR_CrtTime | 14 | 2 |
作成時間が記録されます ビット0-4:秒 ビット5-10:分 ビット11-15:時間(23時間) |
DIR_CrtDate | 14 | 2 |
作成時間が記録されます ビット0-4:日 ビット5-8:月 ビット9-15:年(1980年から数えて何年目か) |
DIR_LstAccDate | 18 | 2 |
最終アクセス日が記録されます フォーマットはDIR_CrtDateと同じです |
DIR_FstClusHI | 20 | 2 |
ファイルが記録されている最初のクラスタ番号の上位ワードが記録されます FAT12とFAT16では0x0000が記録されます |
DIR_WrtTime | 22 | 2 |
書き込み時間が記録されます フォーマットはDIR_CrtTimeと同じです |
DIR_WrtDate | 24 | 2 |
書き込み日が記録されます フォーマットはDIR_CrtDateと同じです |
DIR_FstClusLO | 26 | 2 |
このフィールドがFAT12で重要なフィールドです ファイルが記録されている最初のクラスタ番号の下位ワードが記録されます ブートローダを作成するときはこのフィールドを見てください |
DIR_FileSize | 28 | 4 |
ファイルサイズがバイト数で記録されます 4バイトであることに注意してください。またこのファイルサイズは クラスタ数×512バイトではなく、実際のファイルサイズです |
ルートディレクトリの開始セクタ = ブートセクタのセクタ数 + ( FATの数 × FATのセクタ数 )
= BPB_ResvdSecCnt + (BPB_NumFATs × BPB_FATSz16)
ルートディレクトリのセクタ数 = ((エントリの数 * エントリのサイズ) + (1セクタのバイト数 - 1)) / 1セクタのバイト数
= ((BPB_RootEntCnt * 32) + (BPB_BytsPerSec - 1)) / BPB_BytsPerSec
となります。
ここで(1セクタのバイト数 1)を足していますが、四捨五入をするためです
;***************************************************************************
; FATを読み込んだ直後でAXにルートディレクトリ開始セクタ番号が入っています
;***************************************************************************
LOAD_ROOT:
MOV BX, WORD [BX_RTDIR_ADDR] ; BX_RTDIR_ADDRのアドレスに格納します
XOR CX, CX ; CXレジスタを初期化
XCHG AX, CX ; ルートディレクトリ開始セクタ番号を退避CXレジスタに(AXは0になる)
MOV AX, 0x0020 ; エントリのサイズは32バイト(0x0020バイト)
MUL WORD [BPB_RootEntCnt] ; エントリの数×エントリのサイズをAXに格納
ADD AX, WORD[ BPB_BytsPerSec ] ; AXに1セクタのバイト数を足す
DEC AX ; AXから1を引く
DIV WORD [BPB_BytsPerSec] ; AXの値÷1セクタのバイト数
XCHG AX, CX ; AXとCXを入れ替える
; AX=ルートディレクトリの開始セクタ番号
; CX=ルートディレクトリのセクタ数
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
;
; Browse Root directory
;
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
MOV BX, WORD [BX_RTDIR_ADDR] ; 読み込んだルートディレクトリのアドレスを取得します
MOV CX, WORD [BPB_RootEntCnt] ; エントリの数を取得します
MOV SI, ImageName ; 読み込みたいファイル名のアドレスを取得します(11文字)
BROWSE_ROOT: ; ルートディレクトリ探索開始
MOV DI, BX ; ルートディレクトリのエントリのアドレスをDIに格納
PUSH CX ; CX(エントリ数)を退避
MOV CX, 0x000B ; CXに0x00B(11文字)を格納
PUSH DI ; DIを退避
PUSH SI ; SIを退避
REPE CMPSB ; 文字列CMPSB命令を繰り返す
; REPEはCXに格納されている値(11文字)分CMPSB命令を繰り返します
; CMPSB命令はDS:SIに格納されている1バイトと
; ES:DIに格納されている1バイトを比較します
; 比較結果は一致しなかったバイト数がCXに格納されます
POP SI ; SIを元の値に戻します
POP DI ; DIを元の値に戻します
JCXZ BROWSE_FINISHED ; CXが0であればFinishへ(Jamp if CX is Zero)
ADD BX, 0x0020 ; 次のエントリを見に行くため32バイト足します
POP CX ; CXを元の値に戻します
LOOP BROWSE_ROOT ; 次のエントリを見に行きます。BROWSE_ROOTにジャンプ
; LOOP命令はCXの値(エントリの数)分ループします
JMP FAILURE ; エントリを全部見終わってファイルが無ければ失敗
BROWSE_FINISHED: ; ファイル発見
POP CX ; CXの値を元に戻します(PUSHしたままなのでSPを元にもどす)
BROWSE_FINISHED:
POP CX
MOV AX, WORD [BX+0x001A]
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
;
; Load Root From Floppy
;
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
LOAD_ROOT:
MOV BX, WORD [BX_RTDIR_ADDR]
XOR CX, CX
MOV WORD [datasector], AX ; FATを読み込んだ直後なので、
; AXにはルートディレクトリの開始が入っています
XCHG AX, CX
MOV AX, 0x0020
MUL WORD [BPB_RootEntCnt]
ADD AX, WORD[ BPB_BytsPerSec ]
DEC AX
DIV WORD [BPB_BytsPerSec]
XCHG AX, CX
ADD WORD [datasector], CX ; CXにはルートディレクトリのサイズ(セクタ数)が入っていますので
; 足します
次のソースコードはBROWSE_ROOTしてルートディレクトリのエントリーが見つかった
後の処理となります。エントリーからファイルの開始クラスター番号を取得してファイル
のセクター番号をClusterLBAで計算してファイルイメージをロードしていきます。
MOV AX, WORD [BX+0x001A] ; ルートディレクトリのエントリから ; ファイルの開始クラスタ番号を取得 MOV BX, WORD[ES_IMAGE_ADDR] ; ファイルを格納する先のデータセグメントを ; BXに入れる MOV ES, BX ; ESにBXの値を格納する(ESセグメントに格納する) XOR BX, BX ; BXを初期化(ESセグメントのオフセット) PUSH BX ; ファイルを格納する先のオフセットをスタックに退避 MOV WORD [cluster], AX ; ファイルの開始クラスタ番号をclusterに入れる
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ ; ; Load Image ; ;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
LOAD_IMAGE: MOV AX, WORD [cluster] ; clusterに入っているクラスタ番号をAXに入れる POP BX ; ファイルを格納する先のオフセットを元に戻す CALL ClusterLBA ; ファイルの先頭セクタ番号を計算する XOR CX, CX MOV CL, BYTE [BPB_SecPerClus] ; 1クラスタあたりのセクタ数をCLに入れる CALL ReadSector ; ファイルクの一部(1セクタ)をES:BXに読み込む ADD BX, 0x0200 ; 1セクタ読み込んだので1セクタ(0x200バイト)分 ; オフセットを進める PUSH BX ; BXの内容が変更されるので退避 ; ここから次のクラスタを調べる MOV AX, WORD [cluster] ; AXの値は変更されてしまったので、再度clusterを入れる MOV CX, AX ; CXに調べるクラスタ番号を入れる MOV DX, AX ; DXに調べるクラスタ番号を入れる SHR DX, 0x0001 ; クラスタ番号N / 2 ADD CX, DX ; クラスタのオフセットを計算 MOV BX, WORD[BX_FAT_ADDR] ; 読み込んだFAT領域のアドレスをBXに入れます ADD BX, CX ; 調べたいクラスタのアドレスを計算 MOV DX, WORD [BX] ; DXにクラスタNの値を入れる TEST AX, 0x0001 ; 奇数か、偶数かを調べる JNZ ODD_CLUSTER ; ZFが0でない場合ODD_CLUSTERへ EVEN_CLUSTER: ; 偶数クラスタの処理 AND DX, 0x0FFF ; 次クラスタの値を取得(DX) JMP LOCAL_DONE ; 次クラスタ読み込み完了 ODD_CLUSTER: ; 奇数クラスタの処理 SHR DX, 0x0004 ; 次クラスタの値を取得(DX) LOCAL_DONE: ; 次クラスタ読み込み完了処理 MOV WORD [cluster], DX ; 次クラスタ番号をclusterへ CMP DX, 0x0FF0 ; 終端クラスタか調べる JB LOAD_IMAGE ; 終端クラスタでない場合はLOAD_IMAGEへ戻る ALL_DONE: ; ファイル読み込み完了 POP BX ; スタックポインタを戻すため、意味は無いがBXの値を元に戻す MOV SI, msgIMAGEOK ; 成功メッセージを表示 call DisplayMessage HLT ; 停止
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ ; PROCEDURE ClusterLBA ; convert FAT cluster into LBA addressing scheme ; LAB = (cluster - 2 ) * sectors per cluster ; INPUT : AX : cluster number ; OUTPUT : AX : base image sector ;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
ClusterLBA: SUB AX, 0x0002 XOR CX, CX MOV CL, BYTE [BPB_SecPerClus] MUL CX ADD AX, WORD [datasector] ; ここでdatasectorはファイル領域の開始セクタ番号 ; そこにクラスタ番号から計算したオフセットを足せば ; 読み込みたいファイルのセクタ番号を取得できる RET