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

日々勉強中。。。

0から作るOS開発

環境準備

環境設定

ブートローダ

カーネルローダ

GRUB

カーネル

ドライバーその他

ブートローダその10 フロッピーからデータを読み込む

前回までの内容

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

フロッピーからデータを読み込む

今回はフロッピーからデータを読み込む方法についてです

フロッピーで扱う最小単位はセクタで512バイトでした。ですので、読み込むときもセクタ単位(512バイト単位)で行います

今回もやはりBIOSの割り込み処理を使ってセクタを読み込みます

使用する割り込み処理はINT0x13命令を実行することで呼び出せます

読み込みで使用するINT0x13命令には2つのモードがあります

1つ目はフロッピードライブの初期化モード、2つ目が当然セクタ読み込みモードです

INT0x13命令のフロッピードライブ初期化モード

フロッピードライブの初期化を行うことで読み取る機械のヘッドを最初のセクタ(セクタ番号1)の位置に戻すことができます

では初期化モードの引数をみてみましょう

また割り込み処理を行った後の結果(返り値)が汎用レジスタに格納されます

では簡単な関数を作ってみます


ResetFloppyDrive:
	MOV	AH, 0x00
	MOV	DL, 0x00
	INT	0x13
	JC FAILURE
	HLT
FAILURE:
	HLT

この例ではJNC命令が新しくでてきました。JC(Jump if Carry)命令はEFLAGSのCF(Carry Flag)が

0の場合FAILUREラベルにジャンプします。ついでに、逆の命令としてJNC(Jump if Not Carry)命令が

あります。JNC命令は逆にCFが1の場合にジャンプするという命令となります。FAILUREラベル以降ではエラー

処理を行うようにします

(この例の場合成功、失敗でもHLT命令が実行されるだけですが。。。)

これまでやってきたこととほとんど同じで、簡単にできたかと思います

これでフロッピードライブの初期化ができました。次にセクタ読み込みを見ていきます

INT0x13命令のセクタ読み込みモード

指定したセクタを読み込むには同様にINT0x13命令を使います。ただし指定する引数が違います

そしてこの割り込み処理も返り値が汎用レジスタに格納されます

これでフロッピーディスクから好きなセクタを好きなアドレスに読み込むことができます

ここで指定する引数のセクタ、シリンダ(トラック)、ヘッドのおさらいです

ではフロッピーディスクの総セクタはいくつになるでしょうか。


	フロッピーディスクの総セクタ数	=ヘッド数×ヘッダあたりのシリンダ数×シリンダあたりのセクタ数
					=2×80×18
					=2880セクタ(0X0E04セクタ)

あります。FAT12のブートセクタのに書き込んだ総セクタ数に一致しています。

(他のディスク情報についても確認してみてください)



それではフロッピーディスクからセクタを読み込んでみましょう。


ReadSectors:
	MOV AH, 0x02		;セクタ読み込みモード
	MOV AL, 0x01		;1つのセクタだけ読み込み
	MOV CH, 0x01		;2つ目のシリンダ(トラック)上にあるセクタを読み込みます
	MOV CL, 0x02		;指定したシリンダ上の2つ目のセクタを読み込みます
	MOV DH, 0x00		;1番目のヘッドから読み込みます(フロッピーには2つのヘッドがある)
	MOV DL, 0x00		;1番目のドライブから読み込みます(フロッピーのAドライブは1番目)
	MOV BX, 0x1000
	MOV ES, BX			;アドレス0x10000から開始するセグメントに読み込みます
	MOV BX, 0x0000		;セグメントの最初に読み込みます。オフセットに0x0000を指定。
	INT 0x13		;セクタを読み込みます

このBIOSの割り込み処理でフロッピーディスクの最初のセクタ(ブートセクタ)をアドレス0x10000に読み込みます

一般的なOSは0x10000にカーネルが読み込まれますので、今回はそれを真似てみました

このINT0x13の処理ではヘッド、シリンダ、セクタを指定して読み込みましたが、

どこのヘッドでどこのシリンダで。。。と考えていくとちょっとどのセクタまで読み込んだのかソフトウェア上の管理も

やりにくかったりします。そこでATAの仕様ではLBA(Logical Block Addressing)が導入されていますので

LBAを使ってディスクにアクセスしようと思います

(HDDやCDとかも同じ考え方で通常LBAを扱います)

LBA(Logical Block Addressing)

LBAはその名が示しているように論理アドレス的な役割で、メモリのアドレスは1バイト目、

2バイト目〜4Gバイト目と1バイトずつ数えていましたが、LBAでは1セクタ目、

2セクタ目〜最大セクタ数目とセクタ単位で数えていきます(すべてのセクタに0から番号をつけていきます)

ソフトウェア上では、ヘッドやシリンダというこを気にすることなく論理的なセクタを指定することで

ディスクにアクセスできます

例えば、0番目のヘッド、1番目のシリンダ、1番目のセクタにアクセスしたいときにLBAは、


	LBA = ヘッド番号 × ディスク上のシリンダ数 × シリンダ毎のセクタ数 + シリンダ番号 × シリンダ毎のセクタ数 + セクタ番号
	    = 0 × 80 × 18 + 2× 18 + 1
	    = 37(セクタ目)



となります。これは例で、こんなややこしい計算はしなくていいと思います

さきほどヘッド数とシリンダ数とシリンダ毎のセクター数から

フロッピーディスクの総セクタ数は2880セクタであることがわかりましたので、

実際は直感的に(直感では困りますが。。。)

LBA 0-2879セクタのアドレス範囲でアクセスすることになります

これでヘッド、シリンダを指定しなくてよくなったので、ずっと楽になったかと思います

(直線的な一本のアドレスになりますので、

Linear Addressing:ライナーアドレッシングといいます。メモリもそうですね。)



しかし、LBAでアクセスできるからと言いましても、実際にはINT0x13命令でBIOSの割り込み処理を使いますので

最終的には元のヘッド、シリンダの単位に直さないといけません

ここで注意していただきたいのですが、物理セクタの番号は1から始まりでした(ヘッド、シリンダは0から始まる)

しかし、LBAで指定する倫理セクタは0から始まりますので、この点だけ気をつけてください

LBAから物理ヘッド番号に変換する

物理ヘッド番号は次のように計算します。(時間のある方は式の意味も考えてみるとより深く理解できます)


	物理ヘッド番号 = ( LBA / シリンダ毎のセクタ数 ) mod ヘッド数 



となります。フロッピーディスクの場合シリンダ毎のセクタ数は18、ヘッド数は2です。

この式のmodは割り算した結果の余りです

LBAから物理シリンダ(トラック)番号に変換する

物理シリンダ番号は次のように計算します


	物理シリンダー番号 = LBA / ( シリンダ毎のセクタ数 × ヘッド数 )



LBAから物理セクタ番号に変換する

物理セクタ番号は次のように計算します


	物理セクタ番号 = ( LBA mod シリンダ毎のセクタ数 ) + 1



+1していることに注意してください。物理セクタ番号は1始まり、LBAである論理セクタ番号は0始まりでした

ではアセンブラで変換してみましょう

アセンブラでLBAを物理ヘッド番号、シリンダ(セクタ)番号、セクタ番号に変換してみる

それではまず変換関数を見てみます

				
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
; LBA2CHS
; input AX:sector number(LBA)
; output AX:quotient, DX:Remainder
; convert physical address to logical address
; physical sector = (logical sector MOD sectors per track) + 1
; physical head   = (logical sector / sectors per track) MOD number of heads
; physical track  = logical sector / (sectors per track * number of heads)
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
LBA2CHS:
	XOR	DX, DX				; initialize DX
	DIV	WORD [BPB_SecPerTrk]		; calculate 
	INC	DL				; +1
	MOV	BYTE [physicalSector], DL	
	XOR	DX, DX				; initialize DX
	DIV	WORD [BPB_NumHeads]		; calculate
	MOV	BYTE [physicalHead], DL	
	MOV	BYTE [physicalTrack], AL	
	RET
	
physicalSector	DB 0x00
physicalHead	DB 0x00
physicalTrack	DB 0x00



物理セクタ番号の計算

ここでDIV命令が出てきました。DIV命令は割り算(DIVide)です(符号無しで割り算します)

ただし、注意点があります。DIV命令では変数WORD [BPB_SecPerTrk]しかありません

割る数、割られる数の関係はどうなっているのでしょうか

DIV命令は16ビットの変数を指定するとそのAXレジスタの値割る変数を計算します

計算した結果、AXレジスタに商を、DXレジスタに余りを格納します

この例ではAXレジスタの値割るシリンダ(セクタ)毎のトラック数を計算しています

余りを格納しているDXに1を足せば物理セクタ番号が取得できます

次の命令のINC DLで、DLレジスタ(余りが格納されている)に1を足します

これでDLレジスタ(フロッピーでは最大2880セクタですので、

結果はDLレジスタの1バイト(最大値255)に収まります)に

物理セクタ番号が格納されますので、absoluteSector変数に

MOV命令でDLレジスタの値を格納します


	DIV	WORD [BPB_SecPerTrk]		; AXレジスタの値(LBA)÷シリンダ(セクタ)毎のセクタ数
	INC	DL				; (LBA mod シリンダ毎のセクタ数)余りに1を足す
	MOV	BYTE [physicalSector], DL	;計算した物理セクタ番号を変数に格納



物理ヘッド番号の計算

先ほどの計算結果で、AXには最初にAXに格納されていました

LBA÷シリンダー(トラック)毎のセクター数の商が格納されいます

ですので、AXの値は( LBA / シリンダ毎のセクタ数 )となっていますので、

これをヘッド数BPB_NumHeadsで割ってやります

その余りがDXに格納されますので(同じく1バイトに収まります)、

DLの値を変数physicalHeadに格納します


	DIV	WORD [BPB_NumHeads]		; calculate
	MOV	BYTE [physicalHead], DL	



物理シリンダ(トラック)番号の計算

ここまでの計算でAXの値には( LBA / シリンダ毎のセクタ数 / ヘッド数 )が格納されいますので

これを変数physicalTrackに格納します


	MOV	BYTE [physicalTrack], AL	



これで物理ヘッド番号、物理シリンダ(トラック)番号、物理セクタ番号が計算できました

この関数をINT0x13命令の前に呼んでやれば自動的にLBAから物理アドレスに変換することができます

LBA論理セクタ2000を読み込んでみる

では2000論理セクタを読み込んでみましょう(何も記録されていないので意味はありませんが。。。軽く流してください)


	MOV AX, 2000				; 10進数
	CALL ReadSectors
		
		
ReadSectors:
	CALL	LBA2CHS
	MOV	AH, 0x02			; 読み込みモード
	MOV	AL, 0x01			; 1セクタだけ読み込み
	MOV	CH, BYTE [physicalTrack]	; トラック
	MOV	CL, BYTE [physicalSector]	; セクタ
	MOV	DH, BYTE [physicalHead]		; ヘッド
	MOV	DL, BYTE [BS_DrvNum]		; ドライブ(BS_DrvNumにはx00をいれておきます)
	MOV	BX, 0x1000
	MOV	ES, BX				; アドレス0x10000から開始するセグメントに読み込みます
	MOV	BX, 0x0000			; セグメントの最初に読み込みます。オフセットに0x0000を指定。
	INT	0x13				; ディスク読み込み
	RET



これで、AXに読みたい論理セクタを指定してReadSectors関数を呼び出せばアドレス0x10000に

読み込みたいセクタが読み込まれます(この例では1セクタのみ)

inserted by FC2 system