0から作るソフトウェア開発

日々勉強中。。。

0から作るOS開発

環境準備

環境設定

ブートローダ

カーネルローダ

GRUB


ドライバーその他

ブートローダその11 FAT12ファイルシステムを読み込む

前回までの内容

これまでで、 ということがわかりました。それではブートローダの開発を進めていきましょう!

まずはFAT12ファイルシステムの構造について見ていきましょう

FAT12ファイルシステム

ブートローダその4 FAT12 でFAT12のさわりの部分で紹介しました。今回はFAT12の全体像

(といってもブートローダで必要なところだけです。すみません。。。)を

見ていきたと思います。このブートローダではフロッピーディスクと使用していますので、

フロッピーディスク上でのFAT12を見ていきます

FAT12の構造

FAT12に大まかに分けて次のような領域に分かれています

FAT12として上記の4つの領域があります。

では実際にディスク上にどのように配置されている(どのような構造)のでしょうか?

フロッピーディスクは全部で2880セクタありました(LBAで2880論理セクタです。

今回の説明でも全てセクタは論理セクタとしています)

この2880セクタはFAT12では下記図のような構造になっています

(少なくとも今回使うFAT12では。)

ブートセクタセクタは0セクタ目、FATは1-9セクタ目、FAT(予備)は10-18セクタ目、ルートディレクトリは19-32セクタ目、33セクタ目以降2879セクタ目までファイルが置かれる

現在までのところ、FAT12のブートセクタがプログラムだということが分かっています

ブートセクタの最初の部分は ブートローダその4 FAT に出てきました

このパラメータはFAT12のディスク情報となりますので、

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以降の領域について見ていきます

FAT領域

FATはFile Allocation Tableの省略でした。英語を日本語にしてみると、

”ファイル割り当てテーブル”といったところでしょうか

ただのテーブルです。FATにはファイルがどのセクタに記録されている

(割り当てられている)のかが書かれています。

この”どのセクタに記録されているのか”という情報が書かれたものをクラスタといいます

クラスタ

クラスタとは日本語でいうところの自然に集まったグループといえばいいのでしょうか

ファイルは当然512バイト以上あるものもあります

そのような場合、ディスク上では何セクタかにまたがって記録されます

(連続したセクタでない場合もあります)

このようにそのファイルが記録されたセクタをファイル単位でグループ化したものがセクタになります



なにやらよくわかりませんが、とりあえずファイルがどのセクタに

記録されているのかがクラスタに記録されています

ではクラスタがFAT領域にどのように記録されているのか見てみます

FAT領域にはクラスタが記録されている。クラスタは12ビットのサイズで0番目から配置されている

クラスタは図のように12ビットで1つのクラスタになります。FAT領域の最初にあるクラスタを0番目として数えます

さきほどクラスタにはどのセクタに記録されているかが記録されていると書いておりましたが、正確には

”クラスタ番号”が書かれています。このクラスタ番号から記録されているセクタ番号を計算します

クラスタの1つが1つのセクタに対応していて、例えばクラスタ番号2は33番目のセクタにあたります

この計算の仕方は後述しますので、先にクラスタ番号の役割について見ていきます

クラスタ番号の役割

クラスタに書かれているクラスタ番号には次のクラスタの番号を入れます

こうすることでそのファイルを記録されるのに使われている分の、クラスタ(セクタ)番号を知ることができます

例えばクラスタ番号2からクラスタ番号8までの7つのクラスタ分のファイルは次のようにFATに記録されます

クラスタ番号2から8を使ったファイルではクラスタ2に0x003、クラスタ3に0x004と次のクラスタ番号を入れていく

このファイルではクラスタ番号2から始まるのでクラスタ2の値を見てみると0x003と書かれています

この値が次に続くクラスタ番号です。そして0x003と書かれていたので

次にクラスタ3を見てみると0x004と書かれています。ですので、次にクラスタ4の値を見ていきます

このように次々と読み込んで行くと最後にクラスタ8の0xFFFにぶち当たります

この0xFFFという値がこのファイルの終わりを意味しています

これでクラスタ番号2から始まるファイルはクラスタ2、3、4、5、6、7、8に記録されていることがわかります

(ファイルサイズは7クラスタ×512バイト=3584バイトになります)

このようにクラスタにはファイルを構成するクラスタ番号をチェインしていきますが、中には特殊なクラスタもあります

特殊なクラスタ

先ほどの例ででてきました特殊なクラスタとして0xFFFの値を持ち、終端を意味するクラスタがありました

終端を意味するクラスタ番号としてFATの仕様書では0xFF8〜0xFFFを終端番号としています

その他にもFATの一番最初の2つも特殊なクラスタになります

0番目のクラスタ0はFAT12では”FAT12”であることを意味する0xFF0が書かれます

厳密にはBPB_Mediaの値+0xF00が入ります。今回はブートローダを作るときに、

BPB_Mediaの値を0xF0としました。ですので、0xFF0がクラスタ0にはいります

(仕様書での例を見ますと0xFF8が例として挙げられていますが、ここでは0xFF0としました

BPB_Mediaが0xF8だとリムーバルじゃないメディアの意味となります)

また1番目のクラスタ1は終端番号0xFFFが入ります

またその他に、0xFF7という値が入っていればそのクラスタ(セクタ)は不良セクタという意味になります

そしてファイルに割り当てがないクラスタは0x000が入ります。ファイルを削除した場合に、OSは

ファイル領域のデータは消さずに、FAT領域のクラスタ情報を0x000にします

(ファイルを参照できなくなっただけで、ファイル自体のデータは残っています)

FATで記録できるファイル

FAT領域は全部で9セクタあります(もちろんフロッピーディスクでの話です)

そのサイズは9セクタ×512バイト=4608バイトになります

最初に2つのクラスタは上記で見た通り特殊なクラスタですので、その分を引くと

4608バイト-3バイト(クラスタ2つ分)=4605バイトとなります

そして4605バイトに記録できるクラスタの数は

4605バイト÷1.5バイト(クラスタ1つは12ビット)=3070個のクラスタをFATの中に

入れることができますので、FAT12で作れるファイルは最大3070個となります

また、FAT12のファイルの最大サイズは

3070クラスタ×512バイト=1571840(約1.5M)バイトとなります

ここまで見てきましたように、FATはファイルの位置やサイズを記録している

重要な領域ですので、予備としてもう一つあります

FATの予備

FAT領域には2つのFATがあります。2つありますが、両方同じFATデータが入っています

FATではFAT領域が一番重要な領域ですので、2つ持たせることで片方のセクタに不良が

あっても、もう一方のFATを読みこめばファイルは正常に読み込みできるようにするためです

これでFATに記録されているクラスタの内容が分かってきました

ではFAT領域をディスクを読み込むため、FAT領域の開始論理セクタを取得してみましょう

FAT領域の開始セクタとサイズ

FAT領域の開始セクタ

FAT領域の開始セクタを知るにはブートセクタのパラメータを利用します


	FAT領域の開始セクタ	= ブートセクタの開始セクタ + 予約領域のサイズ(ブートセクタのサイズ)
				= 0 + BPB_RsvdSecCnt



計算するまでもないですが、FAT領域はブートセクタの次にありますので、

予約領域のサイズ(予約領域のセクタ数)を足せばFAT領域の開始セクタが得られます

では次にFAT領域を読み込むセクタ数(FAT領域のサイズ)を計算します

FAT領域のサイズ

FAT領域のサイズもパラメータから得られます(計算の必要はありません。。。)


	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		; ドライブ番号




このソースではFAT領域を0x7E00に読み込む処理をしています

もう少し具体的には

セクタの読み込みに1セクタずつやっているので効率が悪い例です。。。

もっと効率よい処理に直してみることをおすすめします

ReadSector関数は前回の内容からパワーアップさせてます

セクタ読み込みでエラーがあったかどうか判断して、エラーがあれば

ドライブ初期化しています。エラー処理は5回リトライします

これでFAT領域を好きなアドレスに読み込むことができるようになりました

次はクラスタ番号の読み方について見ていきます

FAT12のクラスタ番号の読み方

FAT12のクラスタ番号は1つのクラスタにつき12ビット(1.5バイト)と中途半端なため

読み込むのが結構難しいです

クラスタの読み方

上図は赤い領域が偶数番号のクラスタ、青が奇数番号のクラスタを表しています

2番目(赤)のクラスタの値は太字の1,2,3の順番で0x003となります

3番目(青)のクラスタの値は太字の1,2,3の順番で0x004となります

以下同様に読み込んでいきます

これを少し数式っぽくしてみます

まずはクラスタ番号をNとしてその位置(オフセット)は


	クラスタ番号Nのオフセット = N + ( N / 2 )



そして 偶数番号のクラスタの値を取得するには


	偶数番号のクラスタの値 = クラスタ番号Nのオフセットからワードサイズ(2バイト)読み取る & 0x0FFF



図の例でいいますと、クラスタ番号2をワードサイズで読み取ると値は0x4003となります

(リトルエンディアンなので後ろのアドレスの値0x40が前にきます)

読み取った0x4003から一番上位の0x4を除去するため0x0FFFでANDします

すると0x4003 & 0x0FFF = 0x0003と読み取れます

次に奇数番号のクラスタの値を取得するには


	奇数番号のクラスタの値 = クラスタ番号Nのオフセットからワードサイズ(2バイト)読み取る >> 4



となります。図の例でいいますと、クラスタ番号3をワードサイズで読み取ると値は0x0040となります

ここで最後の0x0が余分に入ってきて0x004が左にずれてしまっているので、右に4ビットシフトします

すると0x0040 >> 4 = 0x0004と読み取れます

これでクラスタの値が読み取れるようになりました

クラスタ番号を読取るアセンブラコードを見てみましょう

次のソースコードは少し後の説明にでてくるファイルの読み込み
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番目のクラスタ)は

特殊なクラスタでしたので、この2つ分を取り除いているからです

そして1クラスタごとのセクタ数はブートセクタのBPB_SecPerClusの値となります

ただし、注意していただきたい点があり、このファイルのセクタ番号は

ファイル領域先頭を0番目としたオフセットとなります

(一番最初のブートセクタからのセクタ番号ではないことに注意です)

アセンブラも一応見ておきましょう

次のソースコードも少し後の説明にでてくるファイルの読み込み
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レジスタに格納されます



上記でAXレジスタにクラスタ番号を変換したファイルのセクタ番号が格納されます

FAT領域まとめ

これまでで、

ができるようになりました

では次にルートディレクトリ領域を見ていきます

ルートディレクトリ領域

ルートディレクトリにはファイルの情報が書かれています

中でも重要なのがファイル名とそのファイルの開始クラスタです

ブートローダがファイルをロードする場合にはまずこのルートディレクトリにある

カーネルの”ファイル名”を探します。ファイルが見つかった場合、

そのファイルの開始クラスタを見ます。開始クラスタが分かったので、

FAT領域にあるクラスタを見に行きます。FAT領域のクラスタを見るとファイルに使われている

クラスタがわかりますので、後はそのクラスタ番号をセクタ番号(LBA)に直せば

これまでやってきたように、ファイルのデータが読み出せることができます

ルートディレクトリに記録される情報はファイル単位で、

1つのファイル情報につき32バイトあります

この情報のことをルートディレクトリの”エントリ”といいます

それではルートディレクトリのエントリを見ていきましょう

ルートディレクトリのエントリ
名前 オフセット
(バイト)
サイズ
(バイト)
説明
DIR_Name 0 11 ファイル名(またはディレクトリ名)を入れます
原則的には8文字+拡張子3文字で記録されます
例えば”a.txt”というファイル名ではドット”.”は記録されずに
”A TXT”と記録されます。8文字に満たない場合は
スペース(0x20)が8文字になるまで入ります
拡張子が無く11文字に満たないファイル名の場合は
後ろにスペース(0x20)を11文字になるまで入れます
その他仕様書に記載されている例を挙げます

”abc.txt”	→ ”ABC     TXT”
”ABC.TXT”	→ ”ABC     TXT”
”Abc.Txt”	→ ”ABC     TXT”
”abc”		→ ”ABC        ”
”abc.”	→ ”ABC        ”
”abcdef.a”	→ ”ABCDEF  A  ”
”.txt”	→ 無効な名前となります

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バイトではなく、実際のファイルサイズです


ルートディレクトリ領域は14セクタで構成されていて14セクタ×512バイト=7168バイトとなります

エントリは32バイトなのでルートディレクトリに格納できるファイルは7168バイト÷32バイト=224ファイルとなります

ブートローダは目的のファイルをルートディレクトリの先頭から各エントリのDIR_Nameにあるファイル名を順番に探し

該当ファイルが見つかった場合DIR_FstClusLOからクラスタの開始番号を取得することで目的のファイルのセクタを求めます

ルートディレクトリ領域の開始セクタとサイズを求める

目的のファイルを探すために、ルートディレクトリを読み込む必要があります

ルートディレクトリを読み込むためには開始セクタとそのセクタ数を知る必要がありました

ルートディレクトリの開始セクタ番号

ルートディレクトリ領域の開始セクタ番号は


	ルートディレクトリの開始セクタ	= ブートセクタのセクタ数 + ( FATの数 × FATのセクタ数 )
					= BPB_ResvdSecCnt + (BPB_NumFATs × BPB_FATSz16)



と計算できます。アセンブラで計算もできますが、FAT領域を(予備も含め)上のほうで読み込んでいました

FAT12ではFAT領域の次にがルートディレクトリ領域なので、FATの読み込みが終わったところが

ルートディレクトリの開始領域としてもいいと思います

ルートディレクトリのセクタ数

次にルートディレクトリのセクタ数です


	ルートディレクトリのセクタ数	= ((エントリの数 * エントリのサイズ) + (1セクタのバイト数 - 1)) / 1セクタのバイト数
					= ((BPB_RootEntCnt * 32) + (BPB_BytsPerSec - 1)) / BPB_BytsPerSec

となります。 ここで(1セクタのバイト数 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=ルートディレクトリのセクタ数



これでAXにルートディレクトリの開始セクタ番号が、CXにルートディレクトリのセクタ数が

入ることになりますので、後はBIOSの割り込み処理を使ってルートディレクトリをメモリに

読み込みます

読み込んだ次にルートディレクトリに格納されているファイルを探してみましょう

ルートディレクトリから読み込みたいファイル名を探す

ルートディレクトリには最大BPB_RootEntCntのエントリがありますので、

とりあえず、エントリの数分探します

文字を表示するときに文字列を扱いましたが、今回は探すには文字列の比較を行います

ファイル名はエントリの先頭から11バイトありますので、これと読み込みたいファイル名と比較します

次のエントリにはエントリのサイズ32バイトを足したアドレスを見に行きます

ではアセンブラ言語のソースを見てきます


;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
;
; 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を元にもどす)



この処理により目的のファイル名を持つエントリのアドレスがBXに入った状態となります

目的のファイル名を持つエントリからファイルの開始クラスタ番号を取り出す

ファイルの開始クラスタ番号は、エントリの先頭から0x001A(26)バイトにありますので、

そこから取得します。BROWSE_ROOTでルートディレクト探索が終わった結果に0x001A

を足します。(POP命令はBROWSE_ROOTの後処理となります)。


BROWSE_FINISHED:
    POP CX
	MOV	AX, WORD [BX+0x001A]					



BX+0x001Aのアドレスが指す値をAXに格納します

ここでBXは先程見つかったエントリの先頭アドレスです

これでAXにファイルの最初のクラスタ番号が入りました

このAXに格納してあるクラスタ番号をFATから読み込んでファイルが格納されている

セクタ番号を取得してファイルを読み込むことができます

ファイル領域

ファイル領域はルートディレクトリの次の領域ですので、ルートディレクトリを読み込むときに

ファイル領域の開始セクタ番号を求めて憶えておきます

ファイル領域の開始セクタを下記ソースではdatasectorに格納しています

先ほどのルートディレクトリ読み込みのソースに追加してみます

					
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
;
; 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にはルートディレクトリのサイズ(セクタ数)が入っていますので
						; 足します



これまでで、ファイルのセクタ番号(ファイル領域先頭からのオフセット)と

ファイルサイズ(FATのクラスタを順に読み込む)がわかっていました

そして上記処理でファイル領域の開始セクタ番号がわかりましたので

BIOSの処理でフロッピーディスクから目的のファイルが読み込める準備ができました

ではファイルを読み込んでいきましょう

ファイルの読み込み

これまでご紹介した内容でフロッピーディスクディスクに格納されているファイルのセクタ番号が

わかりましたので、実際にファイルを読み込んでみます

次の関数ではルートディレクトリを解析して、読み込みたいファイルの開始クラスタ番号が分かった時点で

呼出すことを想定しています

また、開始クラスタ番号をLBAに変換する関数ClusterLBAで

ファイルの開始セクタ番号を計算しています

次のソースコードは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



これでES:BXで指定したアドレスにファイルを読み込むことができました

テキストなどのファイルを読み込ませ、DisplayMessage関数で表示させたりして

読み込みできているか確認してみましょう

inserted by FC2 system