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

日々勉強中。。。

0から作るOS開発

環境準備

環境設定

ブートローダ

カーネルローダ

GRUB

カーネル

ドライバーその他

ブートローダその9 画面に文字を表示する

前回までの内容

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

画面に文字を表示する

画面に文字を表示することができればデバッグがかなり楽になります

アセンブラ言語ではC言語のprintf関数のようなものがありませんので

自分で表示する関数を準備する必要があります

しかし、ブートローダは512バイトとかなり小さなプログラムで関数全部を自分で作ってしまうと

かなり大きなプログラムになり512バイトにおさまらなくなってきます

そこでBIOSの割り込み処理を利用します

文字を表示する

文字を表示するにはBIOSの助けを借ります

BIOSは起動時に割り込み処理を用意してくれますのでそれを利用します

割り込み処理を行うにはINT命令を実行します

INT命令で指定する割り込み番号は0x10番です

これでベクタテーブルの0x10番目に登録されている文字表示処理が呼び出されます

文字表示処理には引数が要ります

引数は汎用レジスタに値を設定することで渡すことができます

どのように設定すればよいのでしょうか

INT 0x10(文字表示処理)の引数

INT 0x10(文字表示処理)の引数は汎用レジスタのAH、AL(AXレジスタの上位8ビットと下位8ビットです)、

BH、BL(BXレジスタの上位8ビットと下位8ビットです)を使います

そしてAH、AL、BH、BLに値を入れてINT 0x10を実行すれば文字が表示される仕組みとなっています

このようなINT命令の詳しい説明は General Software, Inc. が発行している

BIOSのマニュアル に載っていますので調べて見たい方は読んでみてください

では実際に文字を表示させてみましょう


	MOV	AH, 0x0E
	MOV	AL, 0x41
	MOV	BH, 0x00
	MOV	BL, 0x07
	INT	0x10

上記コードを今までのブートローダに組み込んでみてください

今まで真っ黒な画面でしたが、何か1文字表示されましたでしょうか

試しに他の文字や文字の色なんかも変えて色々試してみてはいかがでしょうか

文字を表示する

こんどは文字列を表示させてみましょう

文字列を表示させる関数です。これをブートローダに組み込んでみてください


;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
;
; DisplayMessage
; display ASCIIZ string
;
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
DisplayMessage:
	PUSH	AX
	PUSH	BX
StartDispMsg:
	LODSB
	OR	AL, AL
	JZ	.DONE
	MOV	AH, 0x0E
	MOV	BH, 0x00
	MOV	BL, 0x07
	INT	0x10
	JMP	StartDispMsg
.DONE:
	POP	BX
	POP	AX
	RET

10数行のプログラムですが、さっぱりわかがわかりません。。。

まずは動かしてみましょう

準備としてまず表示する文字列を用意します


ImageName		DB "Bood-bye Small World", 0x00

この関数では文字列の最後が0x00であれば、文字列の終わりとして判断しますので

文字列の直ぐ後に0x00を置いておきます

このように続けてデータを宣言することができます

ここで変数のラベル名はImageNameとしました

次に、
				
	MOV	SI, ImageName
	CALL	DisplayMessage	

表示させたい文字列の先頭アドレスをSIレジスタに格納します

SIレジスタはレジスタの説明のときにほんの少しだけ出てきました

SIレジスタは”DSレジスタが指すセグメント内のデータに対するポインタ。文字列操作では

読み取り元のアドレスを指します ”と出てきました。このときはなんのことやら

意味がわからなかった説明ですが、セグメントが理解できていれば簡単だと思います

文字列もデータですのでDSデータセグメントとデータセグメントの開始位置からのオフセットを

指定すればアクセスできます。特に文字列の場合はオフセットとしてSIレジスタを使用します

SIレジスタは汎用レジスタですが、文字列関係の命令の場合は文字列のポインタとして

専用の役割が与えられます。今回この関数は文字列を操作する命令を使っていますので

SIレジスタを使用しています。

SIレジスタに先ほどのImageNameのアドレスを格納した後、関数DisplayMessageを

CALLしてみてください

文字列が表示されましたでしょうか?

やっとこれで少しはOSっぽくなってきたでしょうか?

それでは次に関数の内容について見ていきたいと思います

文字を表示するDisplayMessage関数の動作

DisplayMessage関数を見てみてください

まずは前回で出てきましたPUSH命令とPOP命令です

この関数ではAXレジスタとBXレジスタの内容を変更する関数なので関数から抜けるときに

元のAX、BXの値に戻しておきたいので、関数の入れ口PUSH命令、出口にPOP命令を使い

レジスタAX、BXの値を元に戻しています

PUSH命令の次に見慣れない小難しい命令LODSBが鎮座されております

この命令はちょっと難しいです。しかし、DSセグメントとSIレジスタによるオフセットを理解していれば

何のこともありません



LODSBは”Load String”命令です。読んだ字の如く”文字列をロード”します

ではどこから読み込んでいるのでしょうか?もう簡単ですね

DSレジスタが指しているセグメント内の、SIレジスタが指しているオフセットの位置にある

1バイトをALレジスタに格納します。

この命令を実行するだけでAL(AXの下位8ビット)に文字列の1文字分のデータが入ります

更に、1文字読み込んだ後は自動的にSIレジスタの値を+1します

ですのでLODSB命令を実行した後は、SIレジスタは次の文字を指している状態になっています

なんとなくわかってきました。この関数はLDSBでALに1文字入れる→INT 0x10命令で1文字表示させる

を繰り返す関数となります。

繰り返しにはJMP命令を使っています

	
	JMP	StartDispMsg

JMP命令で”StartDispMsg”ラベルの場所まで飛んでいって、またLODSB命令から開始します

ではどうやって文字列がの終わりを判断しているのでしょうか?

それが次の2行が判断しています

				
	OR	AL, AL
	JZ	.DONE

1つ目はALの値をALの値でORしています

同じ値どうしでORすると結局ALの値は何も変わりません。元のままです

元のままですが、ALが0x00場合には違った結果となります

OR命令やADD命令など算術的にな計算を行った後結果が0だった場合は

CPUは”計算の結果が0だった”ということを憶えておいてくれます

その”計算の結果が0だった”ということを憶えておくフラグをZF(Zero flag:ゼロフラグ)といいます

命令が実行された後”計算の結果が0だった”場合このZFの値が1になります

逆に計算結果が0でない場合はZFの値は0になります

ちょっとややこしいです。とにかく計算結果が0かどうかを判断するフラグとなります

このOR命令はこの性質を利用しています

ImageName文字列の宣言の際に最後に0x00を置きました

SIレジスタが最後の0x00を指し、LODSB命令でALにx00が格納され、

OR命令で0x00どうしのALを計算した結果のみZFが1になります



ここで2つ目の命令です。JZは”Jamp if Zero”命令で、”もしゼロならジャンプすると”いう命令です

この”もしゼロなら”の部分がまさにZFを見ていて、ZFが1すなわち計算結果が0もっと言えば

文字列の終端0x00だった場合に、.DONEラベルにジャンプします

C言語だとfor文で簡単に書ける繰り返し命令ですが、アセンブラだと結構めんどいですね。。。



さて、ここで説明いたしましたZFというフラグはどこにあるのでしょうか?

ここでまた重要で特殊なレジスタが出てきます。FLAGSレジスタというレジスタにZFの値が格納されています

EFLAGSレジスタ

FLAGSレジスタも重要ですので、何かとCPUのマニュアルでEFLAGSレジスタの説明を見るときが多いです

EFLAGSレジスタというものがでてきましたが、EFLAGSレジスタは32ビットのレジスタで、

16ビットのFLAGSレジスタを32ビットに拡張したレジスタです

ここでいきなり32ビットのレジスタですが、ブートローダを作った後は直ぐに32ビットの環境になるので、

ちょっと先取りして32ビットのレジスタとして説明します

(FLAGSレジスタは下記図の0ビット目から15ビット目までのことを言います)

とりあえずここではこんなレジスタがあるんだ、程度でいいと思います

それではEFLGSレジスタの中身を見ていきましょう

EFLAGSレジスタはCPUのステータスを32ビットの各ビットが0か1かで、その状態になっているかどうかを表します

ステータスを表す各ビットの割り当てが下記図となります

EFLAGSレジスタのビットアサイン

それでは各ビットについて。ビットの割り当て図の白い部分は予約領域で、該当の数字が入っています

EFLAGSレジスタ
ビット ステータス
フラグ
説明
0 CF Carry Flag キャリーフラグ
1:足し算なので最上位ビットが繰り上げた場合。(例:0xFFFF+1や0x0000-1をした場合)
0:繰り上げが発生しなかった場合。(例:x0FFFE+1をした場合)
2 PF Parity Flag パリティフラグ
1:演算した結果の最下位バイトに値1のビットが偶数個ある場合(例:0xFF03など)
0:逆に奇数個の場合(例:0x0F10など)
4 AF Adjust Flag 調整フラグ
1:演算した結果のビット3(最下位バイトの最上位ビット)に繰り上げが発生した場合(例:0x0080+0x08など)
0:逆に繰り上げが発生しなかった場合
6 ZF Zero Flag ゼロフラグ
1:演算した結果が0の場合
0:逆に0位外の場合
7 SF Sign Flag 符号フラグ
1:演算した結果が負の場合
0:逆に正の場合
8 TF Trap Flag トラップフラグ
1:デバッグのシングルステップモードが有効
0:逆にシングルステップモードが無効
9 IF Interrupt Enable Flag 割り込み可能フラグ
1:割り込みが有効。STI命令を実行するとこのフラグが1になります
0:割り込みが無効。CLI命令を実行するとこのフラグが0になります
割り込みを有効にするか無効にするかするフラグです。
10 DF Direction Flag 方向フラグ
1:文字列関係の命令(LODSBなど)で実行した後例えばSIレジスタの値がインクリメントされる
0:逆にデクリメントされる
11 OF Overflow Flag オバーフローフラグ
1:演算結果が格納するレジスタより大きい数字になった場合(例:0xFF+0x01をALレジスタに格納)
0:逆に格納するレジスタより小さい場合
12-13 IOPL I/O privilege level field I/O特権レベルフィールド
このフィールドには特権レベルの値を入れます(通常0から3)

0以外:ユーザモードとして動作します
0   :特権モードとして動作します
このフィールドの値は特にCPL(Current Privilege Level)と言います。後々出てきます。
14 NT Nested Task Flag ネストタスクフラグ
1:現在のタスクが直前に実行されたタスクにリンクされている場合
0:逆にリンクされていない場合
16 RF Resume Flag 再開フラグ
デバッグ例外に対するプロセッサの応答を制御する
17 VM Virtual-8086 mode Flag 仮想8086モードフラグ
1:仮想8086モードが有効
0:仮想8086モードが無効(プロテクティッドモード)
18 AC Alignment check Flag アライメントチェックフラグ
1:アライメントチェックが有効(CR0レジスタのAMビットもセットしておく必要があります)
0:アライメントチェックが無効
19 VIF Virtual interrupt Flag 仮想割り込みフラグ
1:仮想割り込みが有効(CR4レジスタのVMEビットもセットしておく必要があります)
0:仮想割り込みが無効
20 VIP Virtual interrupt pending Flag 仮想割り込み保留フラグ
1:仮想割り込みが保留状態(CR4レジスタのVMEビットもセットしておく必要があります)
0:仮想割り込みに保留無し
21 ID Identification Flag 識別フラグ
プログラムから値0か1を書き込むことができると、CPUID命令が使えることを意味します


当面はZFとIFとIOPLを使用していきます

次回はフロッピーディスクからのファイル読込について説明いたします

inserted by FC2 system