locoreでのページング設定

OpenBSDなどのOSでは、デマンドページングや仮想記憶などの機能を使用します。このためにはページングの設定を行うことが不可欠であり、OpenBSDでは物理メモリを4K byteのページに区切り管理しています。

Page Directory Page(PDP)とPage Table Page(PTP)

locoreでは、ページング機構で使用するPage Directory Page(PDP)Page Table Page(PTP)をメモリ上に作成して、それらのテーブルにエントリーを作成します。

Page Directory Page(PDP)

i386でのページングは、リニア・アドレスから物理アドレスに変換するための、二階層テーブルによって行わています。

第一階層のテーブルはPage Directory (PD)と呼ばれ、1024個のPage Directory Entry (PDE)を保持しています。各Page Directory Entry(PDE)のサイズは 4byteで、ページディレクトリ全体では 4*1024 = 4K byte となり、ちょうど1 Pageに収まることから、Page Directory Page (PDP)と呼ばれます。1つのPDEはリニアアドレスを4M byte単位でマップし、PDは1024個の PDE を持つので4M byte * 1024 = 4GB のリニアアドレスをマップすることが出来ます。

PDPとリニアアドレスの関係
PDPとリニアアドレスの関係

OpenBSDではPage Directory Page (PDP)はKernelの直下に作成されます。

Page Table Page(PTP)

二階層のテーブルはPage Table(PT)と呼ばれ、複数のテーブルが存在します。一つのテーブルは、1024個のPage Table Entry (PTE)を保持しており、このPage Table Entry(PTE)もサイズが 4byteなので、これらのページをPage Table Page (PTP)と呼ばれます。各PTEは、1ページ(4Kbyte)マップし、それぞれのページに対応する物理メモリアドレスを保持しています。

PDPとリニアアドレス・物理アドレスの関係
PDPとリニアアドレス・物理アドレスの関係

Kernel Page Table

Kernelのアドレス空間をマップするためのPage Tableは、Kernel Page Tableと呼ばれKernel Stackの下に作られます。このテーブルの大きさは、最小でNKPTP_MIN(4 Page) 最大でNKPTP_MAX(191 Page)の大きさを取ることができ、デフォルト大きさは4 Pageになっています、これはnkpdeに定義されています。

ページングの設定

Page Tableの領域確保

Kernelの最終アドレス

ページングで必要なPDPやPTPはKernelの直下に作成されるので、最初にKernelの最終を求めます。求めたKernel最終アドレスはEDIレジスタにセットされます(その後も度々使用されるので覚えておこう)。

Kernelの最終アドレス
Kernelの最終アドレス
テーブルの先頭アドレス

PDPやPTPはページ単位で作成するので、Kernel最終アドレスをページ境界に丸め、そこからテーブルの作成を行う。求めたテーブル先頭アドレスはESIレジスタにセットされる(これも、度々使用されるので覚えておこう)。

テーブルの先頭アドレス
テーブルの先頭アドレス
Table関係のマクロ

locoreには、Page Tableに関するいつかのマクロが定義されています。

  • PROC0PDIR : Page Directory Page(PDP)の位置(テーブルの先頭アドレスからの相対offset)
  • PROC0STACK : Kernek Stackの位置(テーブルの先頭アドレスからの相対offset)
  • SYSMAP : Kernel Page Tableの位置(テーブルの先頭アドレスからの相対offset)
  • TABLESIZE : PDPとKernek Stackを合わせたサイズ
マクロ定義
マクロ定義
Kernel Page Tableのページ数

Kernel Page TableのDefault Page数は変数nkpdeに格納されています。この変数には、pmap.hでdefineされているNKPTP(Default 4 Page)の値が入っています。この変数nkpdeの値をチェックしてNKPTP_MIN(PTPの最小ページ数)NKPTP_MAX(PTPの最大ページ数)の範囲であるかチェックし、範囲外の場合は修正します。

table領域のクリア

Kernel Page Table PageのサイズとTABLESIZE(Page Directory Table Page+Kernel Stack)のサイズ分ゼロクリアを行います。

クリア領域
クリア領域

Page Table Entryの登録

fillkptマクロ
text領域を登録

text領域の最後(etext)をページ境界に丸め、read only属性でPage Table Entryに登録します。

caddr_t text_end,pte_add;
int32_t pte_no,count,pte_val;

/* ページ境界に丸める */
text_end  = #etext + PGOFSET;
text_end &= (~PGOFSET);

/* 登録するpteのアドレスを求める */
pte_no   = RELOC(KERNTEXTOFF);
pte_no  /= 1024;  /* 登録するpte No.を求める */
pte_add  = tableの先頭アドレス(esi) + SYSMAP*(pte_no*4); /* 登録先pteのアドレス */

/* 登録するpteの数を求める  */
count  = text_end - RELOC(KERNTEXTOFF); /* text領域のレングス */
count /= 1024;                          /* text領域のページ数(登録pte数) */

pte_val = RELOC(KERNTEXTOFF)+(PG_V|PG_KR); /* pteの値(read) */
fillkpt(pte_val,&pte_add,count);           /* pte登録 */	  
text領域をPTEに登録
text領域をPTEに登録
bss date table領域を登録

text領域の最後(etext)からKernel Page Tableまでをwrite属性でPage Table Entryに登録します。

pte_val = text_end+(PG_V|PG_KR);  /* pteの値(write) */

/* 登録するpteの数を求める  */
count  = nkpde * 1024;  /* Kernel Page Tableのサイズ */
count += TABLESIZE + tableの先頭アドレス(esi); /* Tableの最終アドレス */
count -= text_end;               /* bssからテーブルまでのサイズ  */
count /= 1024;                   /* 登録するpteの数  */
fillkpt(pte_val,&pte_add,count); /* pte登録 */	  
bss date table領域をPTEに登録
bss date table領域をPTEに登録
ISA I/O MEM領域を登録

I/O Memoryの領域はIOM_BEGINからIOM_ENDまでの領域です。この領域は、ISAカードなどの古いカードがマザーボードと情報をやり取りするために使用されています。 この場所はメインメモリとして使うことが出来ないためISA I/O holeと呼ばれています。

この領域を、Kernel Page Tableの後ろにマッピングします。

I/O MEM領域をPTEに登録
I/O MEM領域をPTEに登録

一時的なKernelメモリ空間の割当て

ページングが無効になっている時、Kernelプログラムは物理アドレス上のアドレスで動いています(0x1000000番台付近)。この時 kernelをリニアアドレスの0xd0100000番地からマップし、ページングを有効にした場合どうなるでしょうか?

例えば、ページングを有効にしたときのプログラム カウンタ(PC)の値が0x10003deの示していたとすると、ページングが有効になった瞬間 Kernelプログラムは、リニアアドレスの0xd0100000番地に移動してしまうので、CPUは次の0x10003de番地の命令を実行しようとするが、そのプログラムは消えてなくなってしまう事になるはずです。

これを回避するために、物理アドレスと同じようなメモリ空間のマッピングを一時的に行い、次の命令を適切に実行されるようにし、ページングが有効になったら、kernelは仮想アドレス上で動くように、仮想アドレス上のラベルにjumpまたはcallを行います。そのあとはこのマップは必要なくなるので消去されます。

一時的なメモリ空間
一時的なメモリ空間
一時的なメモリ空間のPDEを登録

先程作成した、PTPのアドレスを仮想アドレスの0x0番地から始まるエントリー(PTE#0)に追加し一時的なメモリ空間を作成します。 これにより物理メモリと同じような、仮想メモリ空間が作られます。

一時的なメモリ空間をPTEに登録
一時的なメモリ空間をPTEに登録

この登録により、ページングが有効になると物理アドレスとリニアアドレスの関係は次のようになります。

一時的メモリ空間の構成
一時的メモリ空間の構成

Kernelメモリ空間の割当て

PTPのアドレスを仮想アドレスの0xd0000000から始まるエントリー(PTE#832)に追加します。 これでKernelのアドレス空間が作られます。
この登録により、ページングが有効になると物理アドレスとリニアアドレスの関係は次のようになります。

Kernelメモリ空間の構成
Kernelメモリ空間の構成

Recursive PDP

PDP自体をPDE#831に登録します。

Recursive PDPの登録
Recursive PDPの登録

これにより、0xcfc00000-0xcfffffffをアクセスすることで全てのページテーブルの内容を読み出す事が出来るようになります。例えば、0xcfc00000は論理アドレスの0x00000000〜0x00000fffのページテーブルに相当し、0xcfc00004は論理アドレスの0x00001000-0x00001fffにページテーブルに相当する事になります。

Recursive PDP
Recursive PDP

これの利点は、Page Tableが物理アドレス上では分散して配置されていても、リニアアドレスでは、0xcfc00000-0xcfffffffをアクセスすることで、全てのPage Tableが連続したデータとして扱える事です。

Recursive PDP
Recursive PDP

ページングの設定

PDPアドレスを保存

物理アドレス上のPDPアドレスを変数PTDpaddrに保存する。

CRレジスタに値をセット

CRレジスタに値をセットしてページング機能を有効にします。

Kernelアドレスにジャンプ

スタックに飛び先(begin)アドレスをスタックに積んでreturn。これにより0xd0100000番地のkernelに処理が遷移します。

begin

一時的なメモリ空間を削除

一時的なKernelメモリ空間の割当てで作成した、一時的なメモリ空間はもう使用しないので、PDE#0-#3のエントリをクリアします。これにより0x100000番地にマッピングされていたKernelは存在しなくなります。

一時的なメモリ空間を削除
一時的なメモリ空間を削除

atdevbase変数

ISA I/O memoryの先頭アドレスを変数atdevbaseにセット。

スタックの設定

Kernel Stackの最初の部分は、プロセスの動きを制御する情報が入った、user構造体(u-area)の領域として使用されます。また最後の部分はシステムコール時に起る、例外/ソフトウエア割込み(trap)用のStack Frameとして使用されます。

Kernel Stack
Kernel Stack
proc0paddr変数

Kernel Stack内に存在する、u-areaのアドレスを変数proc0paddrにセットする。

スタックレジスタの設定

スタックの始まるアドレスを計算してESPレジスタにセット。

CR3レジスタの内容を保存

CR3レジスタの内容(PDPのアドレス)をu-areaのpcb_cr3にセットする。

init386関数をcall

PTPのアドレスを引数にして、init386関数をcallする。init386関数は386マシンに依存した初期化処理を行う。

Last modified: Wed May 14 17:00:27 2008 JST