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

日々勉強中。。。

0から作るOS開発

環境準備

環境設定

ブートローダ

カーネルローダ

GRUB

カーネル

ドライバーその他

ブートローダその7 メモリアクセス

前回までの内容

これまでで、 ということがわかりました。それでは前回の続きでセグメントにアクセスする方法について説明していきます

がその前にセグメントレジスタの種類について

セグメントレジスタの種類

セグメントレジスタにはCS、DS、SS、ES、FS、GSがありました

このレジスタはそれぞれに違う役割を持っています

大きくわけると、

となります。各セグメントレジスタとレジスタが指定するセグメントについては下記図のようになります

各セグメントレジスタと対応するセグメントの図

セグメントレジスタとセグメントとアセンブラ言語

これまでセグメントレジスタとセグメントの関係を見てきました

ではアセンブラ言語とセグメントの関係はどうなっているのでしょうか

この前作りました最初のブートローダをもう一度見てみましょう


;/********************************************************************************
; File:boot.asm
; Description:Bootloader
;
;********************************************************************************/
[BITS 16]

ORG	0x7C00

;==================================================================================
; 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

;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
;
; BOOT
;
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
BOOT:
	CLI
	HLT
	
TIMES 510 - ($ - $$) DB 0

DW 0xAA55

この前のアセンブラソースコードを載せてみました

起動直後はCS、DS、SS、ES、FS、GSの値はすべて0x0000として考えてみましょう

まず最初に

ORG	0x7C00

が出てきました。これはこのプログラムが0x7C00であることを意味していました


JMP	BOOT		;BS_jmpBoot

次にジャンプ命令がありました。このJMP命令はどこに置かれるのでしょうか?

ブートローダは0x7C00にコピーされるのでこのプログラムも0x7C00をベースアドレスとして作りました

そのプログラムの第0番目がこのJMP命令となります

ベースアドレスが0x7C00ですのでこのJMP命令は0x7C00+0番目のバイト=0x7C00に

置かれることを想定しています。違うアドレスに置いても命令自体は実行されますが、

BOOTのアドレスは0x7C00をベースアドレスにしたアドレスになっています

(この理由については少し下で説明いたします)



さてここでコードセグメントCSの値は0x0000と考えてみました。

するとコードセグメントの開始アドレスは0x00000となります

コードセグメントの大きさは0xFFFFですので0x7C00はコードセグメント内に入っています

ではどうやってコードセグメント内の0x7C00にアクセスしているのでしょうか?

コードセグメント内のアドレスアクセス

今まで説明してきたレジスタ以外に更に特殊なレジスタがあります

IP(Instruction Pointer)という命令があるアドレスを指すレジスタで命令ポインタといいます

0x00000から始まるコードセグメント内の0x7C00にあるこのJMP命令にアクセスするために

IPの値は0x7C00になっています。これで0x7C00のJMP命令を実行することができます

(BIOSからJMP命令か何かでこのアドレスに飛んでくるのでEIPの値が0x7C00になっています)

EIPの値はソフトウェアが故意に書き換えることはできません

その行の命令が終わればハードウェアが自動的に

次の命令のアドレスに書き換えてくれますので、特に意識する必要はありません

が、プログラムがどのセグメントにあるかをコードセグメントで指定する必要がありますので

CSの値には注意が必要です

0x00000から始まるコードセグメントの先頭から0x7C00をEIPが指している

データセグメント内のアドレスアクセス

コードセグメントにあるデータへのアクセスについてはDSレジスタを使います(もちろんほかのデータセグメントを表す

セグメントレジスタでもできます。DSレジスタ以外はアセンブラ言語で明示的に指定する必要がありますが)

ここでの例ではDSレジスタの値は0x0000で0x0000から始まるセグメントでした

ではそのセグメント内のデータにアクセスするにはどうすればよいのでしょうか

これまで出てきたように0x7C00がベースアドレスとなります

例えばワードサイズ(ワードは16ビットのサイズでした)の”BPB_BytsPerSec” にアクセスするときは

	MOV	AX, WORD [BPB_BytsPerSec]

と記述できます。この例では”BPB_BytsPerSec”の値をAXレジスタに格納しています

この命令を上記の”CLI”命令の前に書いてアセンブルしてみてください

アセンブルされたバイナリをバイナリエディタで開いてみてください

すると下記図のように表示されます

(PCはリトルエンディアンなので最小位のデータが先に配置されますので注意してください)

BPB_BytsPerSecは0x7C0Bに配置される

バイナリで見ると”BPB_BytsPerSec”は0x000Bにあります

0x000Bにありますが実際には0x7C00に配置されますので実行するときは0x7C0Bに配置されます

MOV命令の引数を見てみますと”BPB_BytsPerSec”のアドレスとして0x7C0Bが指定されています

(リトルエンディアンなので最小位のバイトが先にきて0x0B7Cに見えます)

0x7C00がベースアドレスなので0x7C00に0x000Bを足して0x7C0Bが”BPB_BytsPerSec”の

DSセグメント内のアドレスになります

0x0000から始まるデータセグメントの先頭から0x7C0Bの場所をアセンブラで指定される

セグメントとセグメント内のアドレス

セグメントとそのセグメント内のアドレスを記述するときには次のように記述します

(セグメント内のアドレスはオフセットといいます)


	セグメントレジスタの値:オフセット


ですので、このBPB_BytsPerSecのアドレスを表したいときには


	DSセグメントレジスタの値:オフセット(BPB_BytsPerSecの場所)=0x0000:0x7C0B


と記述します。また同じ場所を示すのにセグメントの開始アドレスを変えて


	0x07C0:0x000B


と表記しても同様に0x7C0Bのアドレスを意味します

(このとき左側の値はセグメントレジスタの値で0x10を掛ける前の値です)

アドレスの表記の方法がわかりました。DSレジスタで指定するセグメント以外のセグメントには

どうやってアクセスするのでしょうか

ES、FS、GSレジスタのセグメントへのアクセス

DSレジスタで指定するセグメントは一番使用するセグメントですので、

特にDSレジスタを指定するすることはありません

その他のデータセグメントレジスタであるES、FS、GSのセグメントはアクセスするには

明示的にレジスタを指定しなければなりません。 例えばESレジスタが指定するセグメントにアクセスするときは


	MOV	[ES:BX], AX



と書きます。ここでアクセスするアドレスは

	[セグメントレジスタの値:オフセット]



と記述します。これでDSレジスタで指定するセグメント以外のセグメントにもアクセスできます

また、オフセットとして汎用レジスタを指定することができます

そして、汎用レジスタは命令によって役割が当てられると説明したかと思います。

その役割がこのデータセグメントのオフセットとしての役割をはたします

特にSI、DI、BPレジスタは

論理アドレスとリニアアドレス

[セグメントレジスタの値:オフセット]の組み合わせのことを論理アドレス(Logical Address)といいます

対して論理アドレスから、セグメントの開始アドレス+オフセット=アクセスしたいアドレス

を計算できました。この計算したアドレスをリニアアドレス(Linear Address)といいます

スタックセグメント内のアドレスアクセス

スタックセグメント内のアドレスアクセスもコードセグメント、データセグメントと同じようにやります

単純にセグメントレジスタSSに値を入れてオフセット(スタックセグメント内の場所)を指定してやればいいだけです

スタックセグメントのオフセットは汎用レジスタのSPレジスタで指定します

SPレジスタはスタックポインタといい関数コールしたときの呼び出し元アドレスを保存したり、

PUSH命令、POP命令などのスタックを操作するときに使用する極めて重要なレジスタです

(SPレジスタは汎用レジスタですが主にスタックの操作をする専用レジスタと考えたほうがいいと思います)

0x00000から始まるスタックセグメントの先頭から0xFFFCをSPが指している

スタックは大きいアドレス(ここではSPレジスタの値が0xFFFCとしました)から使っていきます

(関数コール、PUSH命令のたびに0xFFFCから値を引いていき使いすぎると最終的には0x0000

まで行ってしまいます)

ですので使用できるメモリのなるべく大きなところを今回使用しています

さてここまでで、セグメントを操作することができるようになりました

それではセグメントを操作するブートローダを作ってみましょう

起動直後のセグメントレジスタの初期化

ではセグメントレジスタを初期化する処理を追加してみます


;/********************************************************************************
; File:boot.asm
; Description:Bootloader
;
;********************************************************************************/
[BITS 16]

ORG	0x7C00

;==================================================================================
;
; 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

;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
;
; BOOT
;
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
BOOT:
	CLI
	
; Initialize Data Segment
	XOR	AX, AX
	MOV	DS, AX
	MOV	ES, AX
	MOV	FS, AX
	MOV	GS, AX

	XOR	BX, BX
	XOR	CX, CX
	XOR	DX, DX

; Initialize Stack Segment and Stack Poiniter
	MOV	SS, AX
	MOV	SP, 0xFFFC
	
	HLT
	
TIMES 510 - ($ - $$) DB 0

DW 0xAA55



これでセグメントレジスタの初期化ができました

(コードセグメントは明示的に初期化してません。ちょっと容量を減らすために。。。)

Vritual Boxで起動できましたでしょうか

画面的には前回まででと何らかわりません。。。。


	XOR	AX, AX

この命令はAXレジスタどうしで排他的論理和をとっています

排他的論理和は同じ値のものを0、違う値のものを1にする性質があります

ですので、AXレジスタどうしは同じ値なので結局AXレジスタの値は0にクリアされることになります

これはほんのりちょっとしたテクニックで、MOV命令などでAXレジスタに0x0000を入れてもいいのですが

MOV命令では0x0000という値をメモリからとってきます

XORはメモリにアクセスせずにAXレジスタにアクセスするだけで0クリアできるので、少し動作が速くなります



次の行から0クリアしたAXレジスタの値をDS、ES、FS、GSに入れてセグメントレジスタに入れておきます

(Tips:この場合はレジスタどうしのデータ移動なのでメモリアクセスはしません)

そしてついでにBX、CS、DX汎用レジスタも後々使うので変数の初期化と同様に初期化しておきます

次の命令はSSレジスタとSPレジスタを初期化しています



次回はスタックについてもうちょっとだけ説明いたします

inserted by FC2 system