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

日々勉強中。。。

0から作るOS開発

環境準備

環境設定

ブートローダ

カーネルローダ

GRUB

カーネル

ドライバーその他
補足説明 >

ドライバーその他 補足説明 カーネルをメモリーの3GB(0xC0000000)にロードするには

 カーネルをメモリーの3GB(0xC0000000)にロードするには

ここでは、 ページングその2 仮想メモリ管理 で見て行きました、

カーネルを仮想アドレス0xC0000000に置く方法について見ていきます。

ここで紹介する方法は OSDev.org で紹介されている方法となります。

それでは、見ていきます。

カーネルをなぜ0xC0000000に置くのか

カーネルを32ビットアドレス空間(4GB)の0xC0000000(3GB)に何故置くのか

という疑問が出てくるかと思います。もっともな疑問だと思います。そこで、カーネルを

0xC0000000に置く方法を見る前にその疑問を少し解決したいと思います。

物理アドレスアドレスから仮想アドレスへのマッピング

カーネルを0x00000000からストレートマッピングする場合

物理アドレスを仮想アドレスアドレスにマッピングするときに単純に、物理アドレスの

0x00000000を仮想アドレスの0x00000000に割り当ててストレートにマッピング

する場合について見てみます。

カーネルを0x00000000からストレートにマッピングした場合

この場合ですと、昔の16ビットアプリケーションを動作させたいときにカーネルの先頭

領域が邪魔になってしまいます。。。アプリケーションからはカーネルのシステムコール

をすぐに呼び出したいので、通常はカーネル領域の部分は各プロセス共通に持って

います。このような場合、16ビットアプリケーションを実行することができなくなってしまい

ます。また、仮想8086などの実行もできないことになってしまいますので、ソフトウェアの

過去資産が利用できないといったデメリットがあります。

カーネルを0x00100000からストレートにマッピングする場合

では、16ビットアプリケーションを実行できる環境を作るために、カーネルを仮想アドレス

の0x00100000からストレートにマッピングする場合について見ていきます。この場合

では、16ビットのアプリケーションを実行できるようにはなったのですが、よくよく考えて

みると、アプリケーション領域が2つに分断されてしまいます。このため、メモリー管理を

する場合にちょっと複雑になってしまいます。その他に、カーネルに新機能を追加したり

するとカーネルイメージが延びるので、管理の仕方によってはアプリケーションのアドレス

その都度ずらす必要がでてきます。

カーネルを0x00100000からストレートにマッピングした場合

カーネルを0xC0000000からストレートにマッピングする場合

この場合は単純にメモリーの最初の3G空間をアプリケーション領域とし、メモリーの

後ろ1Gをカーネル空間で利用する場合となります。この場合では、アプリケーションと

カーネル領域が完全に分離して、それぞれストレートなメモリーマップとなりますので

メモリー管理も楽になります。しかし、パソコンでカーネルを0xC0000000にロードする

にはちょっと知恵を絞る必要があります。Windows、Linuxなどの一般的なOSでは

このようなメモリーマッピングとなっています。

カーネルを0xC0000000からストレートにマッピングした場合

このサイトでもカーネルを0xC0000000にロードする方針とします。

カーネルを0xC0000000にロードする

カーネルを0xC0000000にロードしていきます。ここでは、 マルチブート仕様

基づいたカーネルを作成し、 GRUBで起動する 場合について見ていきます。

また、GRUBからの起動でなくてもこのサイトで見て来ましたブートローダーでも

同様でいけます。

マルチブートヘッダーとカーネルのロード部を作成する

GRUBその3 で見て来たマルチブートヘッダーを基にしてそこから変更を加えていきます。

GRUBまたはブートローダーが物理アドレス0x00100000にカーネルイメージを

ロードした後に、物理アドレス0x00100000からカーネルの処理を開始して

くれます。(ELFヘッダーが付いている場合はエントリーアドレスから開始します)。

そこから次のような処理を行います。

カーネルを0xC0000000にロードする手順概要

  1. ページディレクトリの作成
  2. あらかじめ0x00000000から4MB分と0xC0000000から4MB分の
    ページディレクトリを作成します。(そしてコンパイルします)。

  3. ページディレクトリの設定
  4. あらかじめ作成しておいたページディレクトリのベースアドレスを
    制御レジスターCR3にいれます。

  5. ページサイズを4MBに設定
  6. 制御レジスターCR4のPSE(ページサイズ拡張)ビットを1にして
    4MBのページサイズを有効にします。

  7. ページングON
  8. 制御レジスターCR0のPG(ページング)ビットを1にしてページングを
    ONにします。ここで仮想アドレス0xC0000000で動き出します。

  9. パイプラインのフラッシュ
  10. JMP命令でパイプラインをフラッシュします。

  11. カーネルのエントリーを呼び出します。


では各工程についてソースコードを見ていきます。

1.ページディレクトリの作成

今回の作成例ではなるべくページディレクトリとページテーブルの容量を減らすため

4MBサイズのページを使用しています。(今回の例で4KBのページを使用するには

ページテーブル分カーネルイメージ増えてしまうのと、プログラム作成の手間を削減

するためです。特に面倒がなければ、4KBのページ設定をプログラム中で行っても

問題はありません)。4MBのページを使用する場合には、ページテーブルエントリーを

用意する必要がありません。ページディレクトリのみ作ればいいのでお手軽に実装

できます。(但しこの方法はPentium III以降のプロセッサーでしか使用できません。

それより古いプロセッサーではPSEに対応していませんので。。。PSEに対応しているか

どうかは CPUID命令 で確認することができます)


4MBサイズのページを使用する場合には、ページテーブルが必要ありませんので、

仮想アドレスと物理アドレスの対応は次の図のように、仮想アドレスの上位10ビットが

ページディレクトリのインデックスで、インデックスに対応するPDEに設定されている

フレームアドレスがページのベースアドレスとなります。そして、オフセットが22ビットに

拡張されていますので、1つのページは4MBのサイズとしてアクセスできます。

4MBページの場合の仮想アドレスと物理アドレスの対応

4MBサイズのページを使用するページディレクトリエントリーは4KBのページの場合と

違っています。それでは、4MBの場合のページディレクトリエントリーを見てみましょう。

(厳密には4MBページと36ビット物理アドレスを使用する場合となります)。

ページディレクトリエントリー(4MBページ用)

PDE(ページディレクトリエントリ)のフォーマット(4MBページ用)
ビット ラベル名 説明
0 P Presetフラグ。ページテーブルのページが物理メモリにロードされているかどうかを設定します
0:ページテーブルのページは物理メモリ上に存在していません
1:ページテーブルのページは物理メモリ上に存在しています

<詳細>
物理メモリに現在ロードされているかどうかを示します。0にクリアされている場合、CPUがこのページテーブルのページにアクセス しようとするとページフォルト例外(#PF)が発生します。CPUはこのフラグをセットしたりクリアしたりしません。 OSがこのフラグを管理します。ページフォルト例外が発生した場合OSは次の操作を実行します
  1. ページをディスクから物理メモリにコピーします
  2. ページアドレスをPTE、PDEにロードし、Pフラグをセットします。Dフラグ、Aフラグなどもこの時点でセットします
  3. TLBにキャッシュされているPTEを無効化します
  4. ページフォルト例外を終了し、元のプロセスの処理を再開します
1 R/W Read/Writeフラグ。ページテーブルのページの読み込み/書き込み属性を設定します
0:ページテーブルのページは読み込みり専用です
1:ページテーブルのページは読み込み/書き込みができます

<詳細>
0で読み込み専用、1で読み込み/書き込みができます。このフラグは制御レジスタCR0のU/Sフラグおよび WPフラグと相互作用します
2 U/S User/Superviorフラグ。ページテーブルのページのユーザ/スーパーバイザ特権を設定します
0:ページテーブルのページはスーパーバイザ特権(カーネルモード)が割り当てられています。ユーザモードではアクセスできません
1:ページテーブルのページはユーザ特権(ユーザモード)が割り当てられています。

<詳細>
0でスーパーバイザ特権、1でユーザ特権が割り当てられます。このフラグは制御レジスタCR0のR/Wフラグおよび WPフラグと相互作用します
3 PWT Page level Write Throghフラグ。ページテーブルのページのキャッシュ方式を設定します
0:キャッシュのライトバックが有効です
1:キャッシュのライトスルーが有効です

<詳細>
0だとライトバックが有効になり、1だとライトスルーが有効になります。CR0のCDフラグがセットされていると プロセッサはこのフラグを無視します。
4 PCD Page leve Cache Disableフラグ。ページテーブルのページのキャッシュを有効/無効に設定します
0:このページのキャッシュを有効にします
1:このページのキャッシュを無効にします

<詳細>
このフラグは、メモリマップドI/Oポートを含むページか、キャッシュをすることによって性能が上がることがない ページに対してキャッシュを無効化することができます。CR0のCDフラグがセットされていると、プロセッサは このフラグを無視します。
5 A Accessフラグ。ページテーブルのページがアクセスされたかどうかを示します
0:このページテーブルのページはアクセスされていません。(CPUは0にクリアしません。物理メモリロード時にOSがクリアします)
1:このページテーブルのページはアクセスされました。(アクセス時にCPUが自動的にこのフラグをセットします)
(4MBページを使用しますので必ず1にします)

<詳細>
このフラグはスティッキー(一度CPUが1に設定すると、その後CPUは0にすることはありません)なフラグです。 必要なときにOSが0にクリアする必要があります。AフラグとDフラグはOSがスワップ用にメモリ管理するために使用します
6 D Dirtyフラグ。ページが変更(書き込まれた)かどうかを示します
0:このページは変更されていません。(CPUは0にクリアしません。物理メモリロード時にOSがクリアします)
1:このページは変更されました。(変更時にCPUが自動的にこのフラグをセットします)

<詳細>
このフラグもスティッキー(一度CPUが1に設定すると、その後CPUは0にすることはありません)なフラグです。 必要なときにOSが0にクリアする必要があります。AフラグとDフラグはOSがスワップ用にメモリ管理するために使用します
7 PS Page Sizeフラグ。ページのサイズを決定します
0:このPDEに含まれるページのサイズは4KBです
1:このPDEに含まれるページのサイズは4MBです(拡張物理アドレスが有効になっている場合は2MB)

<詳細>
このPDEがページテーブルを指している場合は、そのページテーブルの全てのページは4KBのページとなります
8 G Globalフラグ。このページテーブルのページをグローバルページとして設定します。
0:このページテーブルのページはグローバルページとして設定しません
1:このページテーブルのページをグローバルページとして設定します
(グローバルページに設定されたページはTLBフラッシュされません。頻繁に使用するページに設定します)

<詳細>
このフラグが1にセットされていて、制御レジスタCR4のPGEフラグがセットされていると、そのページのPTE、PDEはCR3がロードされても、 タスクスイッチが行われてもTLBフラッシュされません。頻繁に使用されるページがTLBフラッシュされるのを防ぎます。 ソフトウェアだけがこのフラグを変更することができます。
9-11 Available このビットにどんな値がセットされていてもCPUは無視します
OSがこのページを管理するために、自由に設定可能です
12 PAT Page Attribute Tableフラグ
ページ属性テーブル(PAT)機能で使用します

<詳細>
ページ属性テーブル(PAT)をサポートするプロセッサの場合、PCDフラグとPWTフラグと共にPAT内のエントリを 選択するために使用します。これにより、このページのメモリタイプが選択されます
13-16 Address of Page Table (bit32-35) ページフテーブルのアドレスのビット32-35を設定します
このページディレクトリが指しているページテーブルの36ビット物理アドレスの上位4ビットをセットします
17-21 Rsvd 予約です。
22-31 Address of Page Table (bit22-31) ページフテーブルのアドレスのビット22-31を設定します
このページディレクトリが指しているページテーブルの36ビット物理アドレスのビット22-31をセットします


今回は起動後にページディレクトリエントリーのビット0(Pビット)、ビット1(R/Wビット)、

ビット7(PSビット)をセットしたページディレクトリを使用します。そして、ページディレクトリ

で割り当てるページは0x00000000から4MBと0xC0000000から4MBとなります。

最初の4MBは起動直後は0x00100000で動作しているために、マップしておきます。


それでは、カーネルを呼び出す処理multiboot.Sにページディレクトリを作る

プログラムを見ていきます。


/* kernel base address                                                  */
#define KERNEL_BASE_ADDR        0xC0000000
#define KERNEL_PAGE_NUM	        ( KERNEL_BASE_ADDR >> 22 )

/*
==================================================================================

	page directories

==================================================================================
*/
.data
.align 0x1000
/*
----------------------------------------------------------------------------------
    Page Directories as Booting
----------------------------------------------------------------------------------
*/
_PageDirectory:
    /* ------------------------------------------------------------------------ */
    /* map first 4MB                                                            */
    /* bit7:PS=1 page size is 4MB                                               */
    /* bit1:RW=1 read/write access                                              */
    /* bit0:P =1 page is presence                                               */
    /* ------------------------------------------------------------------------ */
    .long   0x00000083                            /* first 4MB                  */
    .rept ( KERNEL_PAGE_NUM - 1 )
        .long 0x00000000
    .endr
    /* ------------------------------------------------------------------------ */
    /* map 4MB from 0xC0000000                                                  */
    /* ------------------------------------------------------------------------ */
    .long   0x00000083                          /* for 0xC0000000               */
    .rept ( 1024 - KERNEL_PAGE_NUM - 1 )
        .long 0x00000000
    .endr



ここで1024個のページディレクトリエントリーを作成しています。.longで

4バイト(32ビット)のデータを宣言しています。値としては0x00000083と

0x00000000の2つの値を設定しています。0x00000083のエントリーが

最初の4MBと0xC0000000からの4MBのページとなります。両方のページに

について、ベースアドレスに0x0000を設定していますので、物理アドレスの

最初の4MBを仮想アドレス空間に同時に割り当てています。(カーネルは

物理アドレスの最初の1MBにロードされていました。カーネルサイズが3MBを

超えていて、カーネル本体でページングの設定を行う前に3MBを超える領域

を使用する場合は、物理アドレスの最初の8MBを割り当てる必要があります。)

KERNLE_PAGE_NUMは0xC0000000から4MBのページにあたる、ページディレクトリの

インデックス値となります。仮想アドレスを22ビット右にシフトするとインデックス値

を計算することができます。


.reptは.endrまでの命令を指定した回数分繰り返します。


    .rept ( KERNEL_PAGE_NUM - 1 )
        .long 0x00000000
    .endr		



とすることで、0xC0000000までのページディレクトリエントリーを繰り返し

作成しています。

補足
カーネル本体の処理で4KBページサイズに戻したい場合にはPSEビットを1にしたままで、 ページディレクトリエントリーのPS(ページサイズ)ビットを0にしておきます。 (または、ページディレクトリにページテーブルのフレームアドレスをセットしておくと4KBページとなります)。


2.ページディレクトリの設定

1.で作成したページディレクトリをページングで使用するために制御レジスター

CR3(PDBR)に設定します。制御レジスターCR3は次のようになっています。

制御レジスターCR3

制御レジスターCR3
ビット ラベル名 説明
0-2 Rsvd 予約です。
3 PWT Page level Write Throghフラグ。ページディレクトリのページのキャッシュ方式を設定します
0:キャッシュのライトバックが有効です
1:キャッシュのライトスルーが有効です

<詳細>
このフラグはプロセッサーの内部キャッシュ(L1およびL2)にのみ影響があります。 ページングがOFFまたはCR0のCDフラグがセットされているとプロセッサはこのフラグを無視します。
4 PCD Page leve Cache Disableフラグ。現在のページディレクトリのページのキャッシュを有効/無効に設定します
0:このページディレクトリのキャッシュを有効にします
1:このページディレクトリのキャッシュを無効にします

このフラグはプロセッサーの内部キャッシュ(L1およびL2)にのみ影響があります。 ページングがOFFまたはCR0のCDフラグがセットされているとプロセッサはこのフラグを無視します。
5-11 Reserved 予約です。
12-31 Page Directory Base Address ページングディレクトリの物理アドレスの上位20ビットを設定します。


では、制御レジスターCR3にページディレクトリのアドレスを設定していきます。ページディレクトリ

の物理アドレスはプログラム中で.align 0x1000でアライメントしてありますので、

下位12ビットは常に0x000になっています。ですので、ページディレクトリのアドレスを

そのままセットするイメージとなります。


/*
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    < open functions >

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
.text
.code32

.equ    start,      ( _start - KERNEL_BASE_ADDR )  /* adjust to pa 0x00100000   */
.globl start

/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :start, _start
    Input       :void
    Output      :void
    Return      :void
    Description :void
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
start:
_start:
    jmp     multi_boot_entry

/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	Funtion     :_multi_boot_entry
	Input       :void
	Output      :void
	Return      :void
	Description :void
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
multi_boot_entry:
_multi_boot_entry:
    /* ------------------------------------------------------------------------ */
    /* set up paeg( note : use physical address instead of virtual address      */
    /* untill paging on )                                                       */
    /* ------------------------------------------------------------------------ */
    lea     ( _PageDirectory - KERNEL_BASE_ADDR ), %ecx
    mov     %ecx, %cr3		



アクセスするアドレスについてはリンカーでベースアドレスを仮想アドレス0xC0100000に

設定しますので、KERNEL_BASE_ADDR分だけアドレスを引いて物理アドレスに変換します。

(startのアドレスについても同様に計算しています)。また、アドレスを汎用レジスターに

格納するには LEA命令 を使用します。

3.ページサイズを4MBに設定

ページサイズを4MBに設定する場合は制御レジスターCR4のPSEビットを1にする必要が

あります。制御レジスターCR4は次のようになっています。

制御レジスターCR4

制御レジスターCR4
ビット ラベル名 説明
0 VME 仮想8086モード拡張ビットです。
0 = 仮想8086モードの拡張が無効となります。
1 = 仮想8086モードでの割り込み/例外処理拡張が有効となります。

仮想モード拡張を有効にすると、8086プログラム実行中の割り込み/例外に8086モニターではなく、 8086プログラムのハンドラーで処理するようになります。 これにより仮想8086アプリケーションの性能を向上させることができます。 また、マルチタスクと複数プロセッサー環境で使用する仮想割り込みフラグ(VIF)も使用できるようになります。
1 PVI プロテクティッドモード仮想割り込みビットです。
0 = プロテクティッドモードで仮想割り込みフラグ(VIF)が無効になります。
1 = プロテクティッドモードで仮想割り込みフラグ(VIF)が有効になります。
2 TSD タイムスタンプ無効ビットです。
0 = どの特権レベルでも RDTSC命令 を実行することがでいます。
1 = 特権レベル0のみ RDTSC命令 を実行することがでいます。
3 DE デバッグ拡張ビットです。
0 = デバッグレジスターDR4とDR5の参照エイリスを使用します(旧プログラムの互換性のために使用します)。
1 = デバッグレジスターDR4とDR5を参照すると未定義オペコード(#UD)例外が発生するようになります。
4 PSE ページサイズ拡張ビットです。
0 = 4KBページまでしか使用できません。
1 = 4MBページが有効になります。
5 PAE 物理アドレス拡張ビットです。
0 = 32ビット物理アドレスまでしかアクセスすることができません。
1 = 36ビット物理アドレスまでアクセスすることができるようになります。
6 MCE マシンチェック有効ビットです。
0 = マシンチェック例外が無効になります。
1 = マシンチェック例外が有効になります。
7 PGE ページグローバル有効ビットです。
0 = グローバルページ機能が無効になります。
1 = グローバルページ機能が有効になります。

グローバルページ機能は頻繁に使用されるページまたは共用されるページにグローバルビットを付与します。 (PDEまたはPTEのビット8グローバルフラグに1をセットします)。グローバルページは、タスクスイッチやCR3書き込み時にTLBからフラッシュされることはありません。
8 PCE 性能モニタリングカウンター有効ビットです。
0 = 特権レベル0のみ RDPMC命令 を実行することがでいます。
1 = どの特権レベルでも RDPMC命令 を実行することがでいます。
9 OSFXSR OSによるFXSAVE/FXRSTOR命令のサポートビットです。
このビットに1をセットすると
  1. OSがFXSAVE命令とFXRSTOR命令をサポートしていることを示すビットです。 ソフトウェアはこのビットを見て命令がOSにサポートされているかどうか判断します。

  2. FXSAVE命令とFXRSTOR命令を使用してx87 FPUレジスターとMMXレジスター、 XMMレジスターとMXCSRレジスターの内容の保存/復元をできるようにします。

  3. プロセッサーでSSE命令、SSE2命令、SSE3命令を実行できるようにします。 (ただし、PAUSE、PREFETCH、SFENCE、LFENCE、MFENCE、MOVNTI、CLFLUSH命令は除きます)。
このビットに0をセットすると、FXSAVE命令とFXRSTOR命令によりx87 FPUレジスターとMMXレジスターの内容は保存/復元されますが、 XMMレジスターとMXCSRレジスターの内容は保存/復元されません。 また0の場合、SSE命令、SSE2命令、SSE3命令を実行しようとするたびに、 無効オペコード例外(#UD)が発生します。 (ただし、PAUSE、PREFETCH、SFENCE、LFENCE、MFENCE、MOVNTI、CLFLUSH命令は除きます)。 OSはこのフラグをセットする必要があります。

補足
CPUID命令 を使用して、CPUID命令のFXSR、SSE、SSE2、SSE3機能フラグからそれぞれ、 FXSAVE/FXRESTOR命令、SSE拡張命令、SSE2拡張命令、SSE3拡張命令がそのプロセッサーでサポートされているかどうか判断することができます。 OSFXSRビットでOS上のこれらの機能が有効であるということをプログラムに示します。
10 OSXMMEXCPT OSによるマスクされていないSIMD浮動小数点例外のサポートビットです。
SIMD浮動小数点例外(#XF)の例外ハンドラーでマスクされていないSIMD浮動小数点例外処理をOSがサポートしていることを示します。 SIMD浮動小数点例外が発生するのはSSE、SSE2、SSE3のSIMD浮動小数点命令だけとなります。 OSはSIMD浮動小数点例外処理をサポートする場合にこのビットをセットします。 セットしていないとプロセッサーはマスクされていないSIMD浮動小数点例外を検出するたびに、 無効オペコード例外(#UD)を生成します。


制御レジスターCR4のビット4PSEビットを1にして4MBページを有効にします。


    /* ------------------------------------------------------------------------ */
    /* set pse bit to enable 4MB pages                                          */
    /* ------------------------------------------------------------------------ */
    mov     %cr4, %ecx
    or      $0x00000010, %ecx
    mov     %ecx, %cr4



制御レジスターCR4の内容を一旦読みだしてからビット4を1にして書き込みます。

4.ページングON

ページングをONにする方法については ページングその2 仮想メモリ管理

参照してください。ページングをONにするには制御レジスターCR3のビット31に

1をセットします。


    /* ------------------------------------------------------------------------ */
    /* enable paging                                                            */
    /* ------------------------------------------------------------------------ */
    mov     %cr0, %ecx
    or      $0x80000000, %ecx
    mov     %ecx, %cr0



制御レジスターCR3もレジスターの内容を一旦読みだしてからセットします。

5.パイプラインのフラッシュ

ページングをONにした後、パイプライン内の物理アドレス0x00000000で処理する

命令をフラッシュしておきます。フラッシュには JMP命令 を使用します。

このあたりのもう少し詳細な話は 割り込みその1 割り込みとIDTとGDT

GRUBから起動した場合のGDTの設定について参照してください。


    lea     StartInHigherHalf, %ecx

    jmp     *%ecx

StartInHigherHalf:



ここではJMP命令ですぐあとの行にあるStartInHigherHalfに分岐しています。

特に意味のない処理内容のように見えますが、これでパイプラインの内容を

フラッシュすることができます。またGASで%ecxなどのレジスターに*をつけると

アドレスを指定していることになります。

6.カーネルのエントリーを呼び出します

特に注意する内容はありません。カーネルエントリーをコールします。


    call    _kernel_entry



ここまでのまとめ。multiboot.Sの全体。

ここまでのまとめとしてmultiboot.Sの内容全体を見ていきます。


/*********************************************************************************
 File:multiboot.s
 Description:Multiboot Header

*********************************************************************************/
#define __MULTIBOOT__S  1
#include "multiboot.h"

/* kernel entry */
.extern _kernel_entry

/*
==================================================================================

    Prototype Statement

==================================================================================
*/
/* Open Functions */

/* Local Functions */

/*
==================================================================================

    DEFINE 

==================================================================================
*/
/* kernel base address                                                          */
#define	KERNEL_BASE_ADDR        0xC0000000
#define	KERNEL_PAGE_NUM         ( KERNEL_BASE_ADDR >> 22 )
#define	STACK_SIZE              0x00004000

/*
==================================================================================

	page directories

==================================================================================
*/
.data
.align 0x1000
/*
----------------------------------------------------------------------------------
    Page Directories
----------------------------------------------------------------------------------
*/
_PageDirectory:
    /* ------------------------------------------------------------------------ */
    /* map first 4MB                                                            */
    /* bit7:PS=1 page size is 4MB                                               */
    /* bit1:RW=1 read/write access                                              */
    /* bit0:P =1 page is presence                                               */
    /* ------------------------------------------------------------------------ */
    .long   0x00000083                          /* first 4MB                    */
    .rept ( KERNEL_PAGE_NUM - 1 )
        .long 0x00000000
    .endr
    /* ------------------------------------------------------------------------ */
    /* map 4MB from 0xC0000000                                                  */
    /* ------------------------------------------------------------------------ */
    .long	0x00000083                          /* for 0xC0000000               */
    .rept ( 1024 - KERNEL_PAGE_NUM - 1 )
        .long 0x00000000
    .endr

/*
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    < open functions >

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
.text
.code32

.equ    start,       ( _start - KERNEL_BASE_ADDR )  /* adjust to pa 0x00100000  */
.globl start

/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :start, _start
    Input       :void
    Output      :void
    Return      :void
    Description :void
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
start:
_start:
    jmp     multi_boot_entry

/* align 32 bits boundary	*/
.align 4

/*
==================================================================================

    multiboot header

==================================================================================
*/
multiboot_header:
    .long   DEF_MBH_MAGIC
    .long   DEF_MBH_FLAGS
    .long   DEF_MBH_CHECKSUM
    /* ------------------------------------------------------------------------ */
    /* for not ELF                                                              */
    /* ------------------------------------------------------------------------ */
#ifndef __ELF__
    .long   ( multiboot_header  - KERNEL_BASE_ADDR )
    .long   start
    .long   ( _data_end         - KERNEL_BASE_ADDR )
    .long   ( _bss_end          - KERNEL_BASE_ADDR )
    .long   ( _multi_boot_entry - KERNEL_BASE_ADDR )
#endif

/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	Funtion     :_multi_boot_entry
	Input       :void
	Output      :void
	Return      :void
	Description :void
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
multi_boot_entry:
_multi_boot_entry:
    /* ------------------------------------------------------------------------ */
    /* set up paeg( note : use physical address instead of virtual address      */
    /* untill paging on )                                                       */
    /* ------------------------------------------------------------------------ */
    lea     ( _PageDirectory - KERNEL_BASE_ADDR ), %ecx
    mov     %ecx, %cr3

    /* ------------------------------------------------------------------------ */
    /* set pse bit to enable 4MB pages                                          */
    /* ------------------------------------------------------------------------ */
    mov     %cr4, %ecx
    or      $0x00000010, %ecx
    mov     %ecx, %cr4

    /* ------------------------------------------------------------------------ */
    /* enable paging                                                            */
    /* ------------------------------------------------------------------------ */
    mov     %cr0, %ecx
    or      $0x80000000, %ecx
    mov     %ecx, %cr0

    lea     StartInHighHerHalf, %ecx

    jmp     *%ecx

StartInHighHerHalf:
    /* ------------------------------------------------------------------------ */
    /* initialize the stack pointer                                             */
    /* ------------------------------------------------------------------------ */
    movl    $(stack + STACK_SIZE), %esp

    /* ------------------------------------------------------------------------ */
    /* reset eflags                                                             */
    /* ------------------------------------------------------------------------ */
    pushl   $0
    popf

    /* ------------------------------------------------------------------------ */
    /* push the pointer to the multiboot information                            */
    /* ------------------------------------------------------------------------ */
    pushl   %ebx    # 1st argument
    pushl   %eax    # 2nd argument

    call    _kernel_entry

loop_infinite:
    hlt
    jmp     loop_infinite

.align 32
stack:
    .rept STACK_SIZE
        .byte 0x00
    .endr

/*
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    < Local Functions >

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
/*
==================================================================================
	Funtion     :void
	Input       :void
	Output      :void
	Return      :void
	Description :void
==================================================================================
*/



ここでスタックの大きさは16KBにしています。

リンカースクリプトの作成

カーネルのリンカースクリプトについて作成していきます。リンカーにカーネルの

仮想アドレスとカーネルイメージが実際にロードされる物理アドレスを設定します。


ENTRY(start)
OUTPUT_FORMAT(elf32-i386)

SECTIONS
{
    . = 0xC0100000;

    .text : AT( ADDR( .text ) - 0xC0000000 )
    {
        _text_start = .;
        *(.text)
        _text_end = .;
    }

    .rodata : AT( ADDR( .rodata ) - 0xC0000000 )
    {
        _rodata_start = .;
        *(.rodata)
        _rodata_end = .;
    }

    .data ALIGN ( 0x1000 ): AT( ADDR( .data ) - 0xC0000000 )
    {
        _data_start = .;
        *(.data)
        _data_end = .;
    }

    .bss : AT( ADDR( .bss ) - 0xC0000000 )
    {
        stack = .;
        _bss_start = .;
        *(COMMON)
        *(.bss)
        _bss_end = .;
    }
}



ここでATキーワードは実際にロードするアドレスを指定するキーワードとなります。

(ATは物理アドレスを指定します。リンカーはロードする物理アドレスとは別に

仮想アドレスについても別管理しますので注意してください)。カーネル自体は

0xC0000000をベースアドレスとして動作します。実際には物理アドレス

0x00100000にロードされるので、カーネルイメージ自体は仮想アドレス

0xC0100000から始まるようにリンカースクリプトで指定しています。実際に

ロードされる物理アドレスは0xC0000000を引くと物理アドレスを計算できます。

また、スタックの位置についても今回は特別セクションとして定義していませんので

.bssセクションに配置されます。この場合、 カーネルことはじめ で.bssセクションを

クリアーしていますが、そのクリアーする対象アドレスからスタック領域を外すように

置いておきます。あとは普通にコンパイルして起動させるだけとなります。

(終わり)

inserted by FC2 system