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

日々勉強中。。。

0から作るOS開発

環境準備

環境設定

ブートローダ

カーネルローダ

GRUB

カーネル

ドライバーその他

0から作るOS開発 カーネルローダその1 メモリマップ

前回までの内容

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

前回までで、ファイルをメモリに読み込むことができるようになりました

読み込みができるようになったので、読み込み先のアドレスを決めるためにメモリマップを見てきます

メモリマップ

パソコンのメモリは好き勝手に使ってよい場所と、そうでない場所があります

どのアドレスを使っていけばよいのかを見て行きましょう

リアルモードで使用する0x00000000から0x00100000までのメモリマップ

このメモリマップは”リアルモード”で使用できるメモリのマップとなります

リアルモードでアクセスできるメモリは約1MBで0x0010FFEFまででした

この図では0x00100000までのメモリが何に使われているのかを示しています

リアルモードでRAMとして自由に使えるエリアは未使用のエリアとなります

ですので、0x00000500から0x000A0000にOS(カーネル)をロードすれば良いのでしょうか

そこにはいくつか問題があります

OS(カーネル)ロードの問題点

リアルモードでOS(カーネル)をロードするときの問題点として以下が考えられます



そこで、約4Gバイト(0xFFFFFFFF)のメモリまでアクセスできる32ビット動作の

プロテクティッドモードに移行してからカーネルをロードします

32ビットプロテクティッドモードの利点としては



となります。ですので、カーネルをロードする前に32ビットのプロテクティッドモードに移行してからロードします

しかし、そこでも問題点が浮上してきます

プロテクティッドモードに移行するときの問題点としては



32ビットモードでは扱うデータのサイズが基本的に32ビット(4バイト)となります

ですので、指定するアドレスも4バイト、命令も4バイト、使うメモリもレジスタも4バイトとなり

コードサイズが劇的に増えてしまうので、512バイトでは収まりきらなくなってきます

そこで、一般的なOSをロードする手法として、ブートローダを2段階構成にします

1段階目はここまで紹介してきました512バイトのブートローダで、2段階目のローダをロードします

2段階目は32ビットモードへの移行とカーネルをロードする方針をとります

このホームページでは2段階目のブートローダをカーネルローダとして位置づけます

カーネルローダをロードする

ではどこに2段階目のローダをロードするかを見て行きましょう

メモリの未使用領域のどこに読み込んでもいいのですが、決めないと作れないので決めておきます

ここではただの例ですので、自由に決めていただいてなんら問題ありません

一応下記例を載せておりますので、ご参考になればと思います

FAT領域を0x00007E00に、ルートディレクトリ領域を0x0000A200に、ブートローダ(2段階目)を0x00000500に読み込む

この例ではブートローダの1段階目で読み込むFAT領域を0x00007E00に、

ルートディレクトリ領域を0x0000A200に、ブートローダ(2段階目)を0x00000500に読み込む例としております

今回のブートローダではこのようにメモリを使うことを想定して作ってまいります

そして、2段階目のブートローダでいよいよカーネルをロードしていきます

カーネルは0x00100000にロードします

一般的なOSではカーネルをこの場所にロードしているようですので、その慣例通りにしてみます

それではカーネルローダを作っていきましょう

はじめてのカーネルローダ

まずは簡単なソースを作って、1段階目から2段階目のブートローダ(カーネルローダ)を読み込めるかどうかテストします

まずはカーネルローダを作ってみます。この例では”Starting.asm”というファイル名にしました

(名前はなんでもいいです)

メッセージを表示するだけのプログラムとなります


[BITS 16]
ORG 0x500
	JMP	MAIN2


;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
;
; Preprocessor directives
;
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
%include "Print.inc"


;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
;
; Data Section 
;
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
msgphello		DB 0x0D, 0x0A, "Hello", 0x0D, 0x0A, 0x00


;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
;
; Starting Kernel Procedure
;
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
MAIN2:
; 汎用レジスタ初期化
	XOR	AX, AX
	XOR	BX, BX
	XOR	CX, CX
	XOR	DX, DX
; データセグメント初期化
	MOV	DS, AX
	MOV	ES, AX
; メッセージ表示
	MOV	SI, msgphello
	CALL	DisplayMessage
	
	HLT



ここでカーネルローダは0x00000500にロードすることを想定していますので、

ベースアドレスを0x00000500としました


[BITS 16]
ORG 0x500
	JMP	MAIN2



あたらしい、NASMの擬似命令がでてきました

これは”Print.inc”ファイルをインクルードする命令です


%include "Print.inc"



別途"Print.inc"というファイル名を作ります

"Print.inc"には1段階目のブートローダででてきたDisplayMessage関数を書いております


;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
;
; Display Message
;

;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
%ifndef __Print_INC_INCLUDED__
%define __Print_INC_INCLUDED__

[BITS 16]


;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
;
; DisplayMessage
;
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
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

%endif



このようにアセンブラ言語を別のファイルに書いてモジュールとして管理できます

カーネルローダをアセンブルします


nasm -o STARTING.IMG Starting.asm



すると”STARTING.IMG”(ファイル名はなんでもいいです)ができますので、

"STARTING.IMG"をAドライブにコピーします

cp STARTING.IMG /cygdrive/a/



CygwinでAドライブを指定する場合は"/cygdrive/a/"と記述します

1段階目のブートローダからカーネルローダへ

では1段階目のブートローダのソースに処理を追加してみます


;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
;
; Load Image 
;
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
LOAD_IMAGE:
	MOV	AX, WORD [cluster]
	POP	BX
	CALL	ClusterLBA
	XOR	CX, CX						
	MOV	CL, BYTE [BPB_SecPerClus]
	CALL	ReadSector
	ADD	BX, 0x0200
	PUSH	BX
; ここから次のクラスタを調べる
	MOV	AX, WORD [cluster]
	MOV	CX, AX
	MOV	DX, AX
	SHR	DX, 0x0001
	ADD	CX, DX
	MOV	BX, WORD[BX_FAT_ADDR]
	ADD	BX, CX
	MOV	DX, WORD [BX]
	TEST	AX, 0x0001
	JNZ	ODD_CLUSTER
EVEN_CLUSTER:
	AND	DX, 0x0FFF
	JMP	LOCAL_DONE
ODD_CLUSTER:
	SHR	DX, 0x0004
LOCAL_DONE:
	MOV	WORD [cluster], DX
	CMP	DX, 0x0FF0
	JB	LOAD_IMAGE
		
ALL_DONE:
	POP	BX
	PUSH	WORD [ES_IMAGE_ADDR]	; ここを追加します。ES_IMAGE_ADDRは0x0050
	PUSH	WORD 0x0000		; ここを追加します
	RETF				; ここを追加します

	HLT				; 停止
	
ここでRETF命令を使用しています。RETF命令は関数からのリターン命令です

通常のRET命令は同じコードセグメント内にあるアドレスに戻りますが、

カーネルローダを遠くに置く場合、コードセグメントの開始位置を変える必要があります

(例えば0x00100000に置く場合コードセグメントCSは0x0001にしたほうが都合がいいと思います)

そのため、コードセグメントを変更しつつ、ジャンプしたい場合に使用しています

(今回の例では遠く離れたアドレスではないので、通常のジャンプでもよいかと思います)

RETF命令はスタックの一番上に積んであるデータをPOPしてきて、それをRETF命令後のIPとして使用します

また、スタックの一番上から2番目のデータをPOPしてきてコードセグメントCSの値として使用します

ですので、RETF命令の前にCSの値と、IPの値をPUSHしてからRETFしております

この例ではCSの値として0x0050、IPの値として0x0000をPUSHしています

これでRETF命令を実行すれば0x0050:0x0000(0x00000500)にジャンプします



これでカーネルローダへ移行することができました

次は32ビットのプロテクティッドモードについて見ていきます

inserted by FC2 system