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

日々勉強中。。。

0から作るOS開発

環境準備

環境設定

ブートローダ

カーネルローダ

GRUB

カーネル

ドライバーその他

0から作るOS開発 カーネルことはじめ

前回までの内容

これまでで、 ことがわかりました。それではカーネルを0から開発していきましょう!

カーネルことはじめ

この回ではカーネルのコンパイル方法について説明します

Cygwinをダウンロードした後にクロスコンパイラをインストールしました

このサイトではカーネルのコンパイルにはクロスコンパイラを使用していきます

また、リンカ、アセンブラもインストールしたクロスコンパイラパッケージに入っていますのでこれを使います

クロスコンパイラ

インストールしたクロスコンパイラの実行ファイルはCygwin上で


	/usr/bin



に入っているかと思います。このディレクトリに

[GCC]


	i686-pc-linux-gnu-gcc.exe



[アセンブラ]


	i686-pc-linux-gnu-as.exe



[リンカ]


	i686-pc-linux-gnu-ld.exe



のファイル名で有ると思います。コマンドをシェルに入力してコンパイラが実行できるかどうか

確認します。パスが通っていなくて、実行できない場合はホームディレクトリの.bash_profileに

パスを設定します

(64ビット版だとx86_64-pc-lnux-gnu-の後にgcc、as、ldの名前がついたファイルがあるかと

思います。適宜読み替えてください。64ビット版のコンパイラでは32ビットコードを生成するには

-m32オプションを付けてください。または、インラインアセンブラの命令を64ビット対応の命令に

します。難しい場合は、クロスコンパイラのi686-pc-linux-gnuのGCC、AS、LDをインストールして

そちらをご使用ください。)

[パスの設定]

ホームディレクトリの.bash_profileファイルを編集します

Cygwin上では


	/home/ユーザ名/.bash_profile



WindowsのExplorer上では


	コンピューター → Cドライブ → cygwin → home → ユーザ名フォルダの.bash_profile



に入っていますので、適当なテキストエディタで編集します

[.bash_profileの編集]

.bash_profileにPATHの環境設定を行います


	PATH="${PATH}:/usr/bin"



をファイルの最後にでも追加して保存します。保存した後はCygwinを再起動させると

Cygwin起動時にBashが.bash_profileを読み込んでPATHを自動的に設定してくれます

リンカの設定

リンカはプログラムの実行アドレスを最終的に決定してくれます

カーネルがロードされるアドレスは0x00100000でしたが、通常のプログラムは仮想メモリ環境の

ユーザメモリ空間で実行されるため、普通にコンパイルするとプログラムのアドレスはユーザ空間の

アドレスになってしまいます。そこで、意図的に0x00100000のアドレスで動作するプログラムを

作るために、リンカにアドレスを指定します。コンパイル時にオプションを設定してもできますが、

ここでは汎用的にリンカスクリプトを作ります

プログラムをどのアドレスに置くのかを決めるかをセクションを定義して決めます

セクション

セクションはプログラムのコード、定数、初期値有り変数、初期値無し変数を区切ったものをいいます

リンカはリンカスクリプトを読み込み各セクションを次のように解釈します

セクション定義
セクション名 説明
.text プログラムのコード(関数)が配置されるセクションとなります
通常プログラムのコードはROMに置きますがパソコンの場合はROMが無いのでメモリに置きます
.rodata プログラムの定数が配置されるセクションとなります
C言語でconstで宣言したものはここに配置されます
通常プログラムの定数はROMに置きますがパソコンの場合はROMが無いのでメモリに置きます
.data プログラムの初期値付き変数が配置されるセクションです
C言語ではグローバル変数に初期値を定義するとここに配置されます
通常初期値付き変数の初期値はROMに置いておき、カーネル起動直後に
このセクションをRAMにコピーする必要があります。パソコンではROMが無いので
最初からコピーされた領域として扱って差し障りありません
携帯電話等の他のシステムのOSを作るときは、.dataのROMセクションと
RAMセクションを定義しておき、ROMからRAMにコピーする作業が必要です
.bss プログラムの初期値無し変数が配置されるセクションです
C言語ではグローバル変数を宣言(初期値は定義しない)するとここに配置されます
このセクションは通常RAMに置きます。通常初期値無し変数は初期化されていませんので、
カーネル起動直後はこのセクションは不定値となっています。 ANSI C規格 に従うため
カーネル起動した後に.bssセクションを0で初期化する必要があります
.vectors 割り込みベクタが配置されるセクションです
CPU依存部分です。通常ROMに配置されます
IntelのCPUでは、割り込みベクタはRAMに自由に配置できますので、
パソコンでは特に定義する必要はありません
組み込みシステムでは必要となってきます
.stack スタックが配置されるセクションです
通常RAMに配置されます。スタック領域を自分で管理できる限りは特に
定義する必要はありません。組み込みシステムなどRAM容量が少ない
システムでは.stackを定義したりします
(組み込みシステムで定義しておくと、.dataや.bssが使用するRAM領域が
.stackまであふれてしまったときにリンカエラーがでるのでRAM領域の使用量を管理できます)


これらのセクションをリンカスクリプトで定義していきます

リンカスクリプト

それではリンカスクリプトを作って行きましょう。セクションを定義するにはSECTIONSコマンドで

定義します。SECTIONSコマンドのフォーマットは次のようになります


SECTIONS
{
	SECTIONS-COMMAND
	SECTIONS-COMMAND
	...
}



SECTIONS-COMMANDのコマンドには次のコマンドがあります

SECTIONS-COMMAND
コマンド 説明
'Entry'コマンド プログラムのエントリーポイント(プログラムが一番最初に実行されるアドレス)を指定します
シンボルの割り当て シンボルを定義することができます
(変数の宣言みたいなものと考えて頂ければいいと思います)
アウトプットセクション アウトプットするセクションのコマンドを定義します
ここに.textなどのセクションを定義していきます
オーバーレイ セクション同士の領域を重ねるときにつかいます


SECTIONS-COMMANDのアウトプットセクションのフォーマットは次のようになります

(大分簡略化しています。詳しくはCygwinのシェルでinfo ldコマンドを実行すると見れます)


	SECTION [ADDRESS] :
	{
		OUTPUT-SECTION-COMMAND
		OUTPUT-SECTION-COMMAND
		...
	}



例えば.textセクションを定義する場合には


	.text 0x00100000 :
	{
		*(.text)
	}



のように書きます。ここで*はすべてのオブジェクトファイルを指定してます

*の代わりに例えばaaa.oを書けばaaa.oで定義されている関数が

.textセクションに配置されることになります

同様に.textの部分を.bssにするとすべてのオブジェクトファイルで

定義されている初期値無し変数が.bssセクションに配置されます



ここでまとめとして、シンプルなリンカスクリプトを作ってみます


SECTIONS
{
    . = 0x100000;
    .text :
    {
        _text_start = .;
        *(.text)
        _text_end = .;
    }

    .rodata :
    {
        _rodata_start = .;
        *(.rodata)
        _rodata_end = .;
    }

    .data :
    {
        _data_start = .;
        *(.data)
        _data_end = .;
    }

    .bss :
    {
        _bss_start = .;
        *(.bss)
        _bss_end = .;
    }
}



'.'は特殊なシンボルで現在のアドレスを意味しています


	. = 0x100000;



このように記述すると現在のアドレスを0x00100000として定義しています

シンボルにアドレスを定義することもできます。例えば


	_bss_start = .;



とすることで、この行のアドレスを_bss_startシンボルに定義しています

これで.bssセクションの開始アドレスを知ることができます

カーネルが起動した後には.bssセクションをクリアする必要がありますので、

C言語から.bssセクションにアクセスするときに使います

カーネルのコンパイルオプション

コンパイル、アセンブル、リンケージ時にオプションを指定することで、

それぞれの動作を変えることができます

コンパイラのオプション

カーネルをコンパイルするときのGCCにオプションを設定します

コンパイルオプション
オプション 説明
-Wall コンパイル時にすべての警告を出してくれます(Warning all)
コンパイルした時にWaringが出てきますので、なるべくWarningを消すように
すると自分では気付かない静的なバグが取り除けます
-O プログラムを最適化してくれます。-Oの後に数字の1から3を入れ、
最適化のレベルを指定します。一般的には-O2までを指定します
-I インクルードファイル(ヘッダファイル)が有るディレクトリを指定します
自分で作ったヘッダファイルがあるディレクトリを指定しておけば、
コンパイル時にヘッダファイルを読み込んでくれます
-c リンカを起動しないようにするオプションです
オブジェクトファイルを作るところまで実行します
このオプションは複数のファイルをコンパイルするときに使用すると便利です
-o 出力するファイル名を指定します


リンカのオプション

カーネルをリンケージするときに指定するオプションです

リンカオプション
オプション 説明
-T 読み込むリンカスクリプトのファイル名を指定します
-Map リンケージした後のメモリマップを出力するファイル名を指定します
-nostdlib 標準ライブラリをリンクしないようにします
カーネルはprintfなどの標準ライブラリの関数を使用することができません
(できることはできるのですが、それには多大な労力が必要です)
標準ライブラリとリンクしないようにオプション設定します
-e プログラムが一番最初に実行される関数アドレス(エントリ:entry)を指定します
カーネルのメイン関数名を指定します
このオプションを指定せずにリンカスクリプトで定義することもできます
--oformat --oformatで指定したファイル形式で実行ファイルを作成します
このサイトでは--oformat binaryを指定して実行バイナリのみ出力させます
Cygwinをインストールした時の標準GCCでは、このオプションを指定しても
効果が無く、Cygwin用のヘッダを強制的に付加してしまいます
それでも、カーネルの実行をすることはできるのですが、ちょっとめんどくさいので、
このサイトではクロスコンパイラを使用することにしています
-o 出力するファイル名を指定します
-r リロケータブル(再配置可能な)オブジェクトファイルを出力します
このオプションは部分的にリンキングをしたいときに使用します


カーネルをコンパイルする

コンパイルオプションを使ってカーネルをコンパイルします

ここではカーネルのソースコードをkernel.cとします


	i686-pc-linux-gnu-gcc -c -o kernel.o kernel.c -O2 -I../include -Wall



kernle.oといるカーネルのオブジェクトファイルが生成されます

次にリンケージを行います


	i686-pc-linux-gnu-ld -T linkerscript -Map kernel.map -nostdlib -e _kernel_entry \
			--oformat binary -o KImage kernel.o



ここで'\'は次の行をに続いていることを意味しています

Cygwinに入力するときは'\'は特に要りません

これでKImageというカーネルイメージが作成することができました

カーネルエントリは_kernle_entryにしています

また、カーネルのソース以外にドライバなどを作成した場合、リンカでカーネルイメージを

作る前にカーネルとドライバをリンキングしておきます

ドライバのコンパイルは


	i686-pc-linux-gnu-gcc -c -o driver.o driver.c -O2 -I../include -Wall



としてドライバのオブジェクトファイルを生成しておきます

そしてカーネルとリンキングします


	i686-pc-linux-gnu-ld -r -o kernel.o krnl.o driver.o



ここでkrnl.oはkernel.cをコンパイルした後のオブジェクトファイルで

リンキングされた出力ファイルはkernel.oです

(kernle.oからkernle.oを1回のコマンドで作れないので別名で作りました)

この後にリンカでカーネルイメージを作成すれば、ドライバとカーネルがリンキングされた

カーネルイメージができあがります

Make

カーネルのコンパイルは実に多くのファイルをコンパイルするようになっていきます

このため、いちいちコマンドを入力していては面倒なので、Makeでコンパイルを自動化します

Makeの基本

Makeを行うにはCygwinに次のようにコマンドを入力します


	make



これだけでMakeが動作します。Makeを実行すると現在のフォルダにあるMakefileという

ファイル名のファイルを読み込んで動作します。MakefileにはMakeの動作を指定しておきます

Makefileの書式

Makefileには書式が決まっています


ターゲット:ソース
	コマンド



基本的にはこのように記述します。この記述で”ターゲット”は作りたいファイル名、

”ソース”は”ターゲット”を作るためのソースコード、”コマンド”は”ターゲット”を

作るためのコマンドを書きます

ただし”ターゲット”は行頭に書いておく必要があります

また、”コマンド”の前にはタブを入力しておく必要があります

コメントは#を記述します。#以降がコメントとして扱われます

例えばカーネルをコンパイルするときは


kernle.o:kernel.c
	i686-pc-linux-gnu-gcc -c -o kernle.o kernel.c -O2 -I../include -Wall

KImage:kernel.o
	i686-pc-linux-gnu-ld -T linkerscript -Map kernel.map -nostdlib -e _kernel_entry \
			--oformat binary -o KImage kernel.o



と書いてMakefileに保存します。そして、Makefileがあるフォルダまでcdコマンドで移動して、

makeコマンドをCygwinに入力します。Makeが実行されると、ターゲットkernel.oファイルが

無いのでkernel.cをコンパイルしてkernel.oを作ってくれます

次に、KImageが無いのでkernel.oをコマンドi686-pc-linux-gnu-ldで

リンケージしてKImageを作ってくれます。これを自動でMakeが行います

しかし、複数のファイルをコンパイルしたいときには(この場合はドライバなど)

何度も同じようなことを書かなければなりません。そこでMakeのマクロを利用します

Makeのマクロ

Makefileにマクロを定義することができます


GCC	= i686-pc-linux-gnu-gcc
CFLAGS	= -O2 -I../include -Wall
LD	= i686-pc-linux-gnu-ld
LFLAGS	= -T linkerscript -Map kernel.map -nostdlib -e _kernel_entry --oformat binary
AS	= i686-pc-linux-gnu-ar


KOBJECT	= kernel.o
KSOURCE	= kernel.c
KIMAGE	= KImage

INCLUDE	= ../include



例えばこのように定義しておくと、このマクロを使用することができるようになります

このマクロを使ってMakefileを書き直してみます


$(KOBJECT):$(KSOURCE)
	$(GCC) -c -o $(KOBJECT) $(KSOUrCE) $(CFLAGS)

$(KIMAGE):$(KOBJECTC)
	$(LD) $(LFLAGS) -o $(KIMAGE) $(KOBJECT)



このようにマクロで定義しておけば修正も楽になってきます

複数のファルダのソースをMakeする方法について見ていきます

カーネルをMakeする

カーネルの開発環境のフォルダ構成

例えば、カーネルのフォルダ構成を次のように考えてみます

複数のフォルダに別れたソースのMake

bootフォルダにはブートローダ、カーネルローダで作成したアセンブラ言語で書かれたソースコードが入っています

driverフォルダにはdriver.cとMakefileファイルを置きます

includeフォルダにはカーネルで使用するヘッダファイルを置きます

kernelフォルダにはkernel.cとMakefileファイルを置きます

カーネルの開発環境のフォルダにはlinkerscriptファイルとMakefileファイルを置きます

この構成でそれぞれコンパイルしリンケージしていきます

開発環境のMakefile

開発環境環境のMakeファイルでは各フォルダに有るMakefileを読み込んで実行していきます

マクロ定義から見ていきます

マクロ定義


#===============================================================================
#	TARGETS
#===============================================================================
BOOT		= boot
LOADER		= STARTING.IMG
LOADERASM	= Starting.asm
KERNEL		= KImage
KOBJECTS	= kernel/krnl.o driver/driver.o

#===============================================================================
#	DEFINES
#===============================================================================
HOME_DIR	= "$(CURDIR)"

CC		= i686-pc-linux-gnu-gcc
CFLAGS		= -fomit-frame-pointer -O2 -I$(HOME_DIR)/include -masm=intel -Wall
ASFLAGS		= -msyntax=intel -march=i686 --32
LFLAGS		= -T linkerscript -Map kernel.map -nostdlib -e _kernel_entry --oformat binary 
ASM		= nasm
AS		= i686-pc-linux-gnu-as
LD		= i686-pc-linux-gnu-ld
DD		= dd
CP		= cp
RM		= rm
CD		= cd
MAKE		= make
IMDISK		= /proc/sys/Device/ImDisk0
ADRIVE		= /cygdrive/a

export HOME_DIR
export CC
export CFLAGS
export LD
export ASFLAGS
export AS



このように定義しました。ここでexportとしておけば、サブディレクトリのMakeが実行されてときに

このマクロ定義が引き継がれます

ブートローダとカーネルローダをMakeするコマンドを見ていきます

ブートローダとカーネルローダのMakeコマンド

ブートローダとカーネルローダのソースはbootフォルダに入っています

このソースをNASMでアセンブルします


$(BOOT).img:$(BOOT)/$(BOOT).asm
	$(ASM) -o $(BOOT).img $(BOOT)/$(BOOT).asm

$(LOADER):$(BOOT)/$(LOADERASM)
	$(ASM) -I$(BOOT) -o $(LOADER) $(BOOT)/$(LOADERASM)			



最初の行でブートローダをアセンブルしてboot.imgブートイメージを作ります

その次の行でStarting.asmからSTARTING.IMGイメージを作ります

次にカーネルをMakeするコマンドを見ていきます

カーネルのMakeコマンド

カーネルのMakeはkernelフォルダに移動してカーネルをMakeします


kernel/krnl.o:
	($(CD) kernel;$(MAKE))	



この例ではcdコマンドでkernelフォルダに移動して、makeコマンドを実行しています

このコマンドによりkernelフォルダにあるMakefileが実行されます

次にドライバをMakeするコマンドを見ていきます

ドライバをMakeするコマンド


driver/driver.o:
	($(CD) driver;$(MAKE))



この例ではcdコマンドでdriverフォルダに移動して、makeコマンドを実行しています

このコマンドによりdriverフォルダにあるMakefileが実行されます

次にドライバとカーネルをリンキングするコマンドを見ていきます

ドライバとカーネルをリンキングするコマンド


$(KERNEL):$(KOBJECTS)
	$(LD) $(LFLAGS) -o $(KERNEL) $(KOBJECTS) 



このコマンドで、kernelフォルダとdriverフォルダにできたオブジェクトファイルをリンクして

KImageカーネルイメージを作ります

Makeでターゲットを指定して実行する

Makeにオプション指定して動作を変えることができます

例えば、作成したオブジェクトファイルを一旦消したい時には


	make clean



とCygwinにコマンド入力することでオブジェクトファイルを消すようにすることができます

ターゲットを指定して実行する場合、指定するターゲットのコマンドをMakefileに書いておきます

オブジェクトファイルの消去コマンド

オブジェクトファイルを消去するコマンドは


#-------------------------------------------------------------------------------
#	Clear Objects
#-------------------------------------------------------------------------------
.PHONY: clean
clean:
	$(RM) -f $(BOOT).img $(LOADER) $(KERNEL)
	($(CD) driver; $(MAKE) clean)
	($(CD) kernel; $(MAKE) clean)



と記述します。.PHONYはcleanが指定されたときに必ず実行することを意味しています

clean:はターゲットです。make ターゲットとコマンド入力すると個別にターゲットのコマンドを

実行することができます。この例では、最初のコマンドでboot.img、STARTING.ASMを消去し

次のコマンドでdriverフォルダに移動、driverフォルダでmake cleanを実行します

次のコマンドはkernelフォルダに移動、kernelフォルダでmake cleanを実行します

同様にブートローダをフロッピーディスクのブートセクタに書き込み、カーネルイメージをコピーすることもできます

OSのインストール

OSのインストールでは、ブートローダをフロッピーディスクのブートセクタに書き込み、カーネルイメージをコピーします


#-------------------------------------------------------------------------------
#	Install Kernel
#-------------------------------------------------------------------------------
.PHONY: install
install: $(BOOT).img $(LOADER) $(KERNEL)
	$(ASM) -o $(BOOT).img $(BOOT)/$(BOOT).asm
	($(CD) kernel;$(MAKE))
	($(CD) driver;$(MAKE))
	$(LD) $(LFLAGS) -o $(KERNEL) $(KOBJECTS) 

	$(DD) if=$(BOOT).img bs=512 count=1 of=$(IMDISK)
	$(CP) $(LOADER) $(ADRIVE)
	$(CP) $(KERNEL) $(ADRIVE)



ここで.PHONYはinstallが指定されたときに必ず実行されます

install:がターゲットの時のコマンドを記述しています

$(LD)コマンドまではこれまでのMakeと同様の動作をします

LDの次の行はddコマンドを実行して、フロッピーディスクのブートセクタにブートローダを書き込みます

その次のコマンドでSTARTING.IMGとKImageをAドライブにコピーしてインストールします

オブジェクトファイルの消去

リビルドしたいときに、一旦オブジェクトファイルを全て消去したいときがあります

rmコマンドでオブジェクトファイルを消去していきます


#-------------------------------------------------------------------------------
#	Clear Objects
#-------------------------------------------------------------------------------
.PHONY: clean
clean:
	$(RM) -f $(BOOT).img $(LOADER) $(KERNEL)
	($(CD) driver; $(MAKE) clean)
	($(CD) kernel; $(MAKE) clean)



最初のコマンドはカレントディレクトリにあるboot.imgとSTARTING.IMGとKImageファイルを

消去します、その次の行以降はdriver、kernelディレクトリに移動し、移動先で

make cleanを実行します

開発環境のMakefileまとめ

これまででてきたコマンドをまとめてみます


#===============================================================================
#	TARGETS
#===============================================================================
BOOT		= boot
LOADER		= STARTING.IMG
LOADERASM	= Starting.asm
KERNEL		= KImage
KOBJECTS	= kernel/krnl.o driver/driver.o

#===============================================================================
#	DEFINES
#===============================================================================
HOME_DIR	= "$(CURDIR)"

CC		= i686-pc-linux-gnu-gcc
CFLAGS		= -fomit-frame-pointer -O2 -I$(HOME_DIR)/include -masm=intel -Wall
ASFLAGS		= -msyntax=intel -march=i686 --32
LFLAGS		= -T linkerscript -Map kernel.map -nostdlib -e _kernel_entry --oformat binary 
ASM		= nasm
AS		= i686-pc-linux-gnu-as
LD		= i686-pc-linux-gnu-ld
DD		= dd
CP		= cp
RM		= rm
CD		= cd
MAKE		= make
IMDISK		= /proc/sys/Device/ImDisk0
ADRIVE		= /cygdrive/a

export HOME_DIR
export CC
export CFLAGS
export LD
export ASFLAGS
export AS

#===============================================================================
#	RULES
#===============================================================================
#-------------------------------------------------------------------------------
#	Kernel
#-------------------------------------------------------------------------------
.PHONY: all
all: $(BOOT).img $(LOADER) $(KERNEL) $(KOBJECTS)
#	;Make kernel objects
	($(CD) kernel;$(MAKE))
	($(CD) driver;$(MAKE))
	
#	;Make boot loader
	$(ASM) -o $(BOOT).img $(BOOT)/$(BOOT).asm
	$(ASM) -I$(BOOT) -o $(LOADER) $(BOOT)/$(LOADERASM)
	
#	;Make kernel image
	$(LD) $(LFLAGS) -o $(KERNEL) $(KOBJECTS) 


$(BOOT).img:$(BOOT)/$(BOOT).asm
	$(ASM) -o $(BOOT).img $(BOOT)/$(BOOT).asm

$(LOADER):$(BOOT)/$(LOADERASM)
	$(ASM) -I$(BOOT) -o $(LOADER) $(BOOT)/$(LOADERASM)

kernel/krnl.o:
	($(CD) kernel;$(MAKE))

driver/driver.o:
	($(CD) driver;$(MAKE))

$(KERNEL):$(KOBJECTS)
	$(LD) $(LFLAGS) -o $(KERNEL) $(KOBJECTS) 

#-------------------------------------------------------------------------------
#	Install Kernel
#-------------------------------------------------------------------------------
.PHONY: install
install: $(BOOT).img $(LOADER) $(KERNEL)
	$(ASM) -o $(BOOT).img $(BOOT)/$(BOOT).asm
	($(CD) kernel;$(MAKE))
	($(CD) driver;$(MAKE))
	$(LD) $(LFLAGS) -o $(KERNEL) $(KOBJECTS) 

	$(DD) if=$(BOOT).img bs=512 count=1 of=$(IMDISK)
	$(CP) $(LOADER) $(ADRIVE)
	$(CP) $(KERNEL) $(ADRIVE)

#-------------------------------------------------------------------------------
#	Clear Objects
#-------------------------------------------------------------------------------
.PHONY: clean
clean:
	$(RM) -f $(BOOT).img $(LOADER) $(KERNEL)
	($(CD) driver; $(MAKE) clean)
	($(CD) kernel; $(MAKE) clean)



ターゲットのなかで、all:ターゲットはmakeをオブション(ターゲット指定無し)で実行したときに

all:のコマンドが実行されます

kernelフォルダとdriverフォルダのMakefile

kernelフォルダとdriverフォルダにもそれぞれMakefileを置きます

そうすることで例えば、make cleanを実行したときに次のコマンドを自動的に実行します


#-------------------------------------------------------------------------------
#	Clear Objects
#-------------------------------------------------------------------------------
.PHONY: clean
clean:
	$(RM) -f $(BOOT).img $(LOADER) $(KERNEL)
	($(CD) driver; $(MAKE) clean)
	($(CD) kernel; $(MAKE) clean)



このコマンドではkernleフォルダに移動して、make cleanを実行してくれます

kernelフォルダにもMakefileを同様に置き、clean:ターゲットのコマンドを

用意しておけば再帰的にmake cleanを実行することができます

kernelフォルダのMakefile

kernelフォルダにもMakefileを置いておきます


#===============================================================================
#	TARGETS
#===============================================================================
KOBJECTS		= krnl.o
OBJECTS			= kernel.o

#===============================================================================
#	DEFINES
#===============================================================================
INCLUDES = ../include

#===============================================================================
#	RULES
#===============================================================================
.PHONY: all
all: $(KOBJECTS) 
	$(LD) -r -o $(KOBJECTS) $(OBJECTS)

.c.o:
	$(CC) $(CFLAGS) \
	-c -o $*.o $<

$(KOBJECTS):$(OBJECTS)
	$(LD) -r -o $(KOBJECTS) $(OBJECTS)

.PHONY: clean
clean:
	($(CD) $(MEM_DIR);$(MAKE) clean)
	($(CD) $(SCHED_DIR);$(MAKE) clean)
	$(RM) -f *.o



このように記述しておけば、make、make all、make cleanが実行されてたときに

自動的にkernelフォルダに移動し、それぞれのターゲットを実行します

同様にdriverフォルダにもMakefileを置きコマンドを書いておきます

(driverフォルダについては割愛させていただきます)



実際にカーネルをコンパイルする

それではカーネルを実際にコンパイルしていきたいとおもいます

まずは簡単なカーネルのソースを作ります


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	Funtion     :_kernel_entry
	Input       :void
	Output      :void
	Return      :int
	Description :Kernel Core
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
int _kernel_entry( void )
{
	for( ;; )
	{
	}
}



カーネルのエントリはリンカで_kernel_entryを指定しましたので、

その通りの関数名にしました。とにかく何もしないカーネルとなります。

カーネルはreturnして帰るアドレスが無いので、for文で無限ループをしています

これを


	make all



して、

	make install



して、コンパイルします。ここまでに説明してきました、bootフォルダのブートローダは

%includeのインクルードするパスはboot/を指定しておく必要があります

(開発環境のフォルダでmakeをしていますので、カレントディレクトリは開発環境フォルダになっています)

_kernel_entryが0x00100000に配置されているかどうかコンパイルした後に

Mapファイルkernel.mapでアドレスを確認しましょう


無事にMakeが終われば後はVirtual Boxを使って動かして行きます

BSSセクションを初期化する

BSSセクションは初期値無し変数が格納される領域ですが、起動直後は不定値が入っています

ANSI C規格 ではBSSセクションは0クリアされている必要があります。通常のアプリケーションはカーネルが0クリアしてくれますので

気にする必要はありませんでしたが、カーネルのBSSセクションはカーネル自身が0クリアする必要がありますし、

(カーネルをELFフォーマットで作って、GRUBで起動する場合はその限りではありません)

0クリアされている保証もありませんので、自分でクリアしておきます

まずは各セクションのアドレスを定義します

(別に定義しなくても、使用するときに直接参照しても問題ありません)

section.cファイルを作り、


unsigned int _TEXT_START 	= ( unsigned int )&_text_start;
unsigned int _TEXT_END 		= ( unsigned int )&_text_end;
unsigned int _BSS_START		= ( unsigned int )&_bss_start;
unsigned int _BSS_END 		= ( unsigned int )&_bss_end;



リンカスクリプトで定義しておいた、各アドレスを変数に保存しておきます

これをkernel.cで使用できるようにしておきます。section.hを作ってkernel.cでincludeしておきます

(ファイル名とか変数名は適当でいいです)


extern unsigned int _TEXT_START;
extern unsigned int _TEXT_END;
extern unsigned int _BSS_START;
extern unsigned int _BSS_END;



section.cをMakefileに追加しておきましょう

メモリ書き込み関数を作る

ある領域を0クリアすることはよくあるので、関数として作っておきます

C言語の標準ライブラリが使えませんので、自分で定義します

下記関数はstrが指すアドレスからsizeサイズまでcを書き込みします

				
/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	Funtion     :kmemset
	Input       :void *str
		    < start address to set >
		    unsigned char c
		    < value for setting > 
		    int size
		    < size of memory > 
	Output      :void
	Return      :void

	Description :set value to memory 
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
void kmemset( void *str, unsigned char c, int size )
{
	unsigned char *ptr	= ( unsigned char * )str;
	const unsigned char ch	= ( const unsigned char )c;
	
	while( size-- )
		*ptr++ = ch;
}			



これを適当にkstdlib.cファイルを作成してライブラリ(みたいなもの)を作って置きます

メモリ書き込み関数を使ってBSSセクションを初期化する

作った関数でBSSセクションを初期化していきます。memory.cファイルにBSSセクション初期化関数を

作ります


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	Funtion     :initBSS
	Input       :unsigned int kernel_bss_start
		    < start address of bss section of kernel >
		    int size
		    < size of bss section > 
	Output      :void
	Return      :void

	Description :initialize BSS section
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
void initBSS( unsigned int kernel_bss_start, int size )
{
	kmemset( ( void * )kernel_bss_start, 0x00, size );
}		



この関数をカーネル起動直後くらいに呼び出ししてBSSセクションを初期化します

BSSセクションのアドレスを指定してこの関数をコールします


	/* --------------------------------------------------------------------- */
	/* clear bss section of kernel						 */
	/* --------------------------------------------------------------------- */
	initBSS( _BSS_START, _BSS_END - _BSS_START );



カーネルコンパイル時にエラーが出る場合

カーネルコンパイル時に下記のようなエラーが出る場合があります。


      0 [main] git 3756 fhandler_disk_file::fixup_mmap_after_fork: requested 0xFFEA0000 != 0x0 mem alloc base 0xFFEA0000, state 0x1000, size 20480, Win32 error 487
    141 [main] git 3756 C:\cygwin\lib\git-core\git.exe: *** fatal error in forked process - recreate_mmaps_after_fork_failed
    468 [main] git 3756 open_stackdumpfile: Dumping stack trace to git.exe.stackdump
      0 [main] git 1528 fork: child -1 - forked process 3756 died unexpectedly, retry 0, exit code 256, errno 11
error: cannot fork() for ssh: Resource temporarily unavailable
fatal: unable to fork

このような場合.bash_profileの最後の方にでも次のように追加すると

エラーがでなくなります。


export LANG=C

カーネルのデバッグ

カーネルのデバッグは画面に文字を表示したりして基本的にデバッグを行います

しかし、ダブルフォルトなどが起こったときなどは、なかなか文字を表示するだけの

デバッグでは厳しくなってきます。そこで、レジスタなどの有力な情報が仮想PCの

ログファイルに出力されますので、ログのレジスタ情報を参考にしながらデバッグを行います

Bochsであれば、環境設定その1で指定したログ・ファイルに

レジスタ情報などが出力されます

また、Virtual Boxであれば、


	C:\Users\ユーザ名\VirtualBox VMs\MyOS(設定した仮想PC名)\Logs



にVBox.logというファイルが作成されています

(過去のログはVBox.log.1というようにナンバリングされています)

ダブルフォルトが発生したときなどはVirtual Boxのログファイルを参照して

レジスタの値を確認しながらデバッグすることができます

今回はカーネルを開発する上での開発手順についてでした

次回はまずデバッグ情報を出力するために画面に文字を表示するドライバを作成したいと思います



inserted by FC2 system