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

日々勉強中。。。

0から作るOS開発

環境準備

環境設定

ブートローダ

カーネルローダ

GRUB

カーネル

ドライバーその他

0から作るOS開発 ページングその1 ページとPTEとPDE

前回までの内容

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

今回はページングついて説明します

物理メモリ管理の限界

前回物理メモリの割り当て、解放について見てきましたが、

物理メモリを管理するだけでは困った問題が起きてきます

たくさんのプログラムをプロセスとして動かすといった場合に、

全てのプログラムをメモリ上にロードできない場合があります

各プロセスは各々ファイルをオープンして読み込みや書き込み

をしたりするので、プログラムだけでは無く、ファイルのメモリを確保したりする必要が出てきました

また、あるプログラムがバグまたは悪意をもって別のプログラムや

カーネル自体にもアクセスできてしまうというセキュリティ上の問題も出てきて

物理メモリの管理だけでは限界が見えてきました

そこで、考え出されたのが、仮想メモリと呼ばれるメモリ空間です

仮想メモリではHDDなどの記憶装置のスペースも利用することで、

小さいメモリでも仮想的に大きなメモリを扱うことができるようになりました

仮想メモリを実現するためにセグメンテーションやページングという

仕組みが開発されました。今回はページングに焦点をあてています

仮想メモリ

仮想メモリにアクセスするには、これまでのブートローダとカーネルローダで見てきたように

CPUのセグメントレジスタを変えてメモリにアクセスしてきました

いままで何気なくセグメント機構を使っていましたが、

仮想メモリ機構はハードウェアの処理も重要な役割を担っています

(セグメントレジスタ・セグメントセレクタを利用するセグメンテーションも仮想メモリ機構です)

ページングも同様にハードウェアとソフトウェアが協調して動作することで、仮想メモリシステムとして

機能します。仮想メモリシステムで重要な要素として、

メモリの制御。CPUとメモリの間にはMMU、TLB、メモリコントローラがあります

があります

仮想アドレス空間

仮想アドレス空間はプログラムが利用するアドレス空間です。普段C言語、C++言語などで

main関数を用意し、プログラムをコンパイルしたときに利用しているのがまさに仮想アドレス空間の

仮想アドレスです。プログラムを少し覗いて見ましょう

まずはいつも作っているように、C言語のアプリケーションプログラムをテキストエディタで作ります


int main( char argc, char **argv )
{
    return 0;
}



ごくごく普通のプログラムです。これを”test.c”という名前で保存して、 Cygwin上でコンパイルします

(LinuxなどのUNIX系OSを使用されている方は普通にgccをご使用いただけます)


i686-pc-linux-gnu-gcc.exe -o test test.c



コンパイルするとtestという実行ファイルができていると思いますので、

実行ファイルのヘッダ情報を表示してみます。実行ファイのヘッダ情報はobjdumpコマンドを使用します。

同様にCygwin上で次のようにコマンドを実行します


$objdump -f test

test:     file format elf32-i386
architecture: i386, flags 0x00000112
EXEC_P, HAS_SYMS, D_PAGED
start address 0x080482c0



と表示されました(それぞれの環境で違う結果かもしれませんがご容赦ください。。。)

ここで表示されたstart addressの0x080482c0がtest実行ファイルを実行したときに、

実際に実行を始める仮想メモリのアドレスとなります

(main関数のアドレスではありませんのでご注意ください)

普段全く気にしたことがありませんでしたが、プログラムのアドレスは仮想メモリのアドレスを指しています

実際にポインタのアドレスの値を表示させるとこの付近のアドレスが表示されると思います

今度は少し変えて別のプログラムを作ってみます


#include <stdio.h>

int main( char argc, char **argv )
{
    printf( "test2 executing" );
    
    for(;;)
    
    return 0;
}



何の変哲もないもない文字を表示して無限ループするだけのプログラムです。

これを”test2.c”の名前で保存して、先ほどと同様にコンパイルしてobjdumpコマンドで

実行ファイルの情報を表示してみましょう


$objdump -f test2

test:     file format elf32-i386
architecture: i386, flags 0x00000112
EXEC_P, HAS_SYMS, D_PAGED
start address 0x080482c0



このように表示されました。ここでもやはり、仮想アドレスがstart addressの0x080482c0と

表示されます。両方とも同じ開始アドレスです!!!!

この2つのプログラムはもちろん同時に実行可能です。

普段ダブルクリックしてアプリケーションを起動していますが、プログラムは同じアドレスからスタートしています

(もちろんアプリケーションを作るコンパイラ、実行したい環境によってアドレスは違ってきます)

ここが、仮想アドレスのポイントで、この2つのアプリケーションは別々のアドレス空間

(大げさに言えば32ビットアドレスでは4GBのサイズのメモリ空間です)を持っていることになります



カーネルはMMUを制御することで、仮想アドレスをプログラムに割り当てていきます

MMU(Memory Management Unit:メモリ管理ユニット)

MMUはCPUとメモリコントローラ(TLBが動作していればTLB)の間にあります。

物理メモリ管理その1でも少し紹介しましたが、 メモリコントローラはリニアアドレスRAM上のアドレスに変換します

そして、MMUはセグメントセレクタとオフセット(インテルが言うところの論理アドレス)※

メモリコントローラに渡すリニアアドレスに変換します。

アドレス変換

 ここで言う論理アドレスはインテルが定義している仮想アドレスです。インテルが定義する仮想アドレスは

セグメント機構上の仮想アドレスでGDTのディスクリプタを選択するセグメントセレクタとセグメント内の

オフセットの組み合わせのことを言います。このサイトではページング機構上のアドレスを仮想アドレスとしていきます

(インテルプロセッサではセグメンテーションを必ず行う必要があります。一般的なOSではリニアアドレスの全てを

1つのセグメントとしてGDTのディスクリプタで設定していて、仮想メモリ管理は事実上ページングで行なっています)

上の例で見てきたように、プログラムは仮想アドレス空間上で実行されることを想定してコンパルされています。

プログラムの仮想アドレスはインテルプロセッサ上で次の図のように、

セグメンテーションとページング機構で物理アドレスに変換されます

(ページング機構で使用するPDEとPTEについては今回で説明いたします)

プログラムの仮想アドレスとセグメンテーションとパージング

ページングが無効になっていると、ページング機構の部分がスルーになって、リニアアドレス=物理アドレスとなります

ページングを有効にすると、MMUでリニアアドレスを物理アドレスに変換するようになります

TLB(Translation Lookaside Buffer:アドレス変換バッファ)

TLBはキャッシュです。MMUで論理アドレスをリニアアドレスに変換し、メモリコントローラでRAM上のアドレスの変換を

していては、結構時間がかかってしまいます(CPUはそれ以上の速度で命令を実行しています。)

そこで、一度アドレス変換を行った論理アドレスと物理アドレスを”憶えておく”ことで、スピードアップをします

TLBは論理アドレスを検索キーとした連想キャッシュ(CAM:Content-Addressable Memory)で、

MMUから論理アドレスを受け取ると、まずそれに対応した物理アドレスがあるかどうかキャッシュ内を検索します

対応する物理アドレスがあると、メモリコントローラにリニアアドレスを渡すことはなく、MMUに物理アドレスを

教えます。この一連の検索をTLBヒットと呼ばれています。見つからない場合はTLBミスと呼ばれます

TLBミスが発生すると、キャッシュ内に対応する物理アドレスが無いので、ページテーブルを見に行きます

ページテーブルを参照して、一度物理アドレスに変換すると、仮想アドレスと物理アドレスを対応付けて

キャッシュに保存します。ページテーブルを参照してページを辿っていくことをページウォークと呼ばれます

ページウォークしても、ページが見つからなかった場合(無効なページが見つかった場合も)

ページフォルトが発生しますので、例外ハンドラで必要な処理を行います

ページ

ここまで見てきたように、MMUで仮想アドレスを物理アドレスに変換しています。この変換で、参照しているのが

ページです。セグメンテーションでは、セグメントの開始アドレスと、そのサイズは任意に選ぶことができましたが

ページは、仮想アドレスの単位で、1ページ4096バイト(4KB)固定でそのアドレスも4096の

倍数となっています

セグメントとページの違い

ページは仮想メモリですので、通常4GB(0x00000000-0xFFFFFFFF)までの連続したアドレスです。

ですが、各ページは物理メモリのどのアドレスに置こうが、自由です。(但し4KBの倍数のアドレス上です)

もっと言えば、物理アドレス上に無くても、HDDなどの記憶装置上に置くこともできます。

HDDなどにページをファイルとして書き出すことページアウト(スワップアウト)と言い、

HDDなどから必要なページを読み込むことをページイン(スワップイン)と言い、

一連の動作はスワップと呼ばれています

スワップを行うことで、わずかしか無い物理メモリでも、HDDを使うことで、膨大な仮想メモリが使用できる

ことになります

ページと物理メモリブロックとスワップ

このように、物理メモリが足りなくなれば、HDD上に保存するページをじゃんじゃん増やしていけば、

膨大な数のメモリ空間が利用できるようになります。HDD上に保存されているページにアクセスしたときに

ページが物理メモリ上にないのでページフォルトが発生します。ページフォルト例外ハンドラで

例外が発生したページを物理メモリにスワップインします。逆に、物理メモリ上のページをHDDに

スワップアウトします



このような仮想メモリ(ページ)を各プログラムが個別に持っています。少し上で紹介しましたように、

別のプログラムでも、同じ仮想アドレスを使用していますが、実は別々の仮想メモリ上で動作させます



それでは、ページングについて見て行きましょう

ページング

ここまで見てきましたように、ページングの概要についてはなんとなく分かってきました

それでは、いったどうやって、ページングをしていけばいいのでしょう?

実際に簡単なページングの処理を作っていきます

ページと物理アドレス

ページはサイズが固定の仮想メモリブロックということでした。

ページは仮想のメモリブロックですが、実際には実態が物理メモリ上にあります

このため、ページングでページを扱うには最低限2つの情報が必要でとなります。

です。

インテルのプロセッサではこの2つの情報をページテーブルで管理する仕組みがあります

ページテーブルを管理する仕組みとして、PTE(ページテーブルエントリ)とPDE(ページディレクトリエントリ)、 そしてそれらをまとめたページテーブルとページディレクトリテーブルがあります

これから、ページテーブルとページテーブルエントリ、

ページディレクトリエントリとページディレクトリテーブルについて見ていきます

まずは、1つのページを表すPTE(ページテーブルエントリ)について見て行きましょう

ページテーブルエントリ(PTE:Page Table Entries)

PTE(ページテーブルエントリ)は1つのページそのもので、ページについての情報を保存しています

PTE(ページテーブルエントリ)は32ビットのサイズで次のようなフォーマットになっています

PTE(ページテーブルエントリ)のフォーマット

PTE(ページテーブルエントリ)のフォーマット
ビット ラベル名 説明
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 level Cache Disableフラグ。ページのキャッシュを有効/無効に設定します
0:このページのキャッシュを有効にします
1:このページのキャッシュを無効にします

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

<詳細>
このフラグはスティッキー(一度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 PAT Page Attribute Tableフラグ
ページ属性テーブル(PAT)機能で使用します

<詳細>
ページ属性テーブル(PAT)をサポートするプロセッサの場合、PCDフラグとPWTフラグと共にPAT内のエントリを 選択するために使用します。これにより、このページのメモリタイプが選択されます
8 G Globalフラグ。このページをグローバルページとして設定します。
0:このページはグローバルページとして設定しません
1:このページをグローバルページとして設定します
(グローバルページに設定されたページはTLBフラッシュされません。頻繁に使用するページに設定します)

<詳細>
このフラグが1にセットされていて、制御レジスタCR4のPGEフラグがセットされていると、そのページのPTE、PDEはCR3がロードされても、 タスクスイッチが行われてもTLBフラッシュされません。頻繁に使用されるページがTLBフラッシュされるのを防ぎます。 ソフトウェアだけがこのフラグを変更することができます。
9-11 Ignored このビットにどんな値がセットされていてもCPUは無視します
OSがこのページを管理するために、自由に設定可能です
12-31 Address of Page Frame ページフレームアドレスを設定します
このページに割り当てられている物理アドレスの上位20ビットをセットします


いろいろゴチャゴチャしたフラグがあって、わけわからんようになってきますが

一番重要なポイント(さしあたって必要なもの)はページフレームアドレスの設定です

ここにページに割り当てたい4KBの物理メモリブロックの開始アドレスを設定します

ページはサイズが4KB固定で、4096(4KB)の倍数の物理アドレスを開始アドレスとします

ですので、PTEのページフレームに設定するアドレスは物理アドレスの上位20ビットを設定します

(アドレス0-4095は0x000-0xFFFの12ビットで表せます。ですので、アドレスの下位12ビットは特に設定する必要はありません)

これだけ分かっていれば、さしあたってはOKだと思います。その他のフラグはオプションです。

すきなように設定して動作を確認してみるのも面白いかと思います



D(ダーティ)フラグとA(アクセス)フラグを1にセットするのは、CPUです。OSは特に1にする必要はありません

0にセットするのはOSで、CPUは0にセットすることはありません



P(プレゼント)フラグはフラグのなかで一番重要なフラグです。このフラグはページが

物理アドレス上に存在しているかどうかを設定するフラグです。このフラグを0に設定すると

CPUはPTEのPフラグ以外のビットを全て無視します。ですので、HDDとかにスワップアウトしている

ページはP(プレゼント)フラグを0にしておいて、他のビットを使ってHDDのどこにページを保存しているのか

なんかにも使えそうです。他にも、0に設定しておくと、物理メモリに存在していないので、ページフォルト例外が

発生します。例外が発生したら、例外ハンドラで(または、カーネルのスレッドで)ページを物理メモリに

ロードするような仕組みを作っておけば、オンデマンドページングなんかにも使えそうです。。。



なんのことやら、さっぱりです。。。

でも少しづつ作りながらやってみると、だんだん分かった気になってきますので不思議です。。。



ちょっと説明ばっかりになってしまっているので、ここらへんでプログラムをしてみます

PTEを実装してみる

まずはPTEの型を用意します。

PTEは32ビットのサイズですのでlong型(32ビットCPUの場合は32ビットになります)として扱います


/*****************************************************************************
 File:virtual_memory.h
 Description:definition of virtual memory

*****************************************************************************/
#ifndef __MEMORY__H
#define __MEMORY__H

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

    Description : Page Table Entry


    bit number  label   description
    0           P       0:page is not in memory
                        1:page is present in memory
    1           R/W     0:page is read only
                        1:page is writable
    2           U/S     0:page is kernel( supervisor )mode 
                        1:page is user mode.cannnot read or write 
                          supervisor pages
    3           PWT     0:cache write back
                        1:cache write through
    4           PCD     0:enable cache
                        1:disable cache
    5           A       0:page has not been accessed
                        1:page has been accessed
    6           D       0:page has not been written to
                        1:page has been written to
    7           PAT     0:pat is not supported
                        1:pat is supported
    8           G       0:not global page
                        1:global page
    9-11        Ignore  available for kernel
    12-31       frame address
==============================================================================
*/
typedef unsigned long PAGE_TABLE_ENTRY;

#endif	/* __MEMORY__H */



PTEのフラグを定義します。PTEのフラグをセットしたり、クリアしたりするときに使用します


#define DEF_PTE_FLAGS_P             0x00000001
#define DEF_PTE_FLAGS_RW            0x00000002
#define DEF_PTE_FLAGS_US            0x00000004
#define DEF_PTE_FLAGS_PWT           0x00000008
#define DEF_PTE_FLAGS_PCD           0x00000010
#define DEF_PTE_FLAGS_A             0x00000020
#define DEF_PTE_FLAGS_D             0x00000040
#define DEF_PTE_FLAGS_PAT           0x00000080
#define DEF_PTE_FLAGS_G             0x00000100
#define DEF_PTE_FLAGS_AVAILABLE     0x00000E00
#define DEF_PTE_FRAME_ADDRESS       0xFFFFF000



PTEにフラグをセットしたり、クリアしたりする関数を作ります

この関数の引数に定義していたフラグを使用してPTEにフラグをセットしたり、クリアしたりします


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :setPteFlags
    Input       :PAGE_TABLE_ENTRY* entry
                 < PTE to be set to flags >
                 unsigned long flags
                 < flags of PTE to be set >
    Output      :void
    Return      :void

    Description :set flas to PTE
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC INLINE VOID
setPteFlags( PAGE_TABLE_ENTRY* entry, unsigned long flags )
{
    *entry |= flags;
}

/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :clearPteFlags
    Input       :PAGE_TABLE_ENTRY* entry
                 < PTE to be cleared of flags >
                 unsigned long flags
                 < flags of PTE to be cleared >
    Output      :void
    Return      :void

    Description :clear attributes of PTE
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC INLINE VOID
clearPteFlags( PAGE_TABLE_ENTRY* entry, unsigned long flags )
{
    *entry &= ~flags;
}



そして、PTEに割り当てる物理アドレスをセットする関数をつくります


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :setPtePageFrameAddress
    Input       :PAGE_TABLE_ENTRY* entry
                 < PTE to be set to page frame address >
                 unsigned long page_frame_address
                 < page frame address of PTE to be set >
    Output      :void
    Return      :void

    Description :set page frame address to PTE
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC INLINE VOID
setPtePageFrameAddress( PAGE_TABLE_ENTRY* entry, unsigned long page_frame_address )
{
    page_frame_address &= DEF_PTE_FRAME_ADDRESS;
    *entry             |= page_frame_address;
}



この関数で物理アドレスをPTEにセットするときに、下位12ビットをマスクすることに注意してください

PTEに割り当てられている物理アドレスを取得する関数を作ります


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	Funtion     :getPtePageFrameAddress
	Input       :PAGE_TABLE_ENTRY* entry
				 < PTE to be set to page frame address >
	Output      :void
	Return      :unsigned long
				 < page frame address >

	Description :get page frame address of PTE
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC INLINE unsigned long
getPtePageFrameAddress( PAGE_TABLE_ENTRY* entry )
{
	return( *entry & DEF_PTE_FRAME_ADDRESS );
}



おまけで、PフラグとR/Wフラグがセットされているかどうか検証する関数を作ります

(次の関数は1以上の真値を返してもいいかと思いますが、記述の正確性を優先させてます)


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	Funtion     :isPtePresent
	Input       :PAGE_TABLE_ENTRY* entry
				 < testee >
	Output      :void
	Return      :BOOL

	Description :test present flag of PTE
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC INLINE BOOL
isPtePresent( PAGE_TABLE_ENTRY* entry )
{
	return( ( *entry & DEF_PTE_FLAGS_P ) == DEF_PTE_FLAGS_P );
}

/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	Funtion     :isPteWritable
	Input       :PAGE_TABLE_ENTRY* entry
				 < testee >
	Output      :void
	Return      :BOOL

	Description :test R/W flag of PTE
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC INLINE BOOL
isPteWritable( PAGE_TABLE_ENTRY* entry )
{
	return( ( *entry & DEF_PTE_FLAGS_RW ) == DEF_PTE_FLAGS_RW );
}



ページを設定するといいましても、C言語的にはこのようにビットをON/OFFするだけの

操作になります。難しいフラグの説明なんかをいろいろ載せてしまいましたが、

プログラム自体は結構簡単だったりします。。。



これで、1つのページとページに対応する物理アドレスが設定できるようになりました。



しかし、、、、、これだけでは、1つのページしか設定できていません

メモリはたくさんのページが置けますので、PTEをまとめてテーブルを作ります

このテーブルをページテーブルと言います(そのまんまですね。。。)

ページテーブル

ページテーブルはただ単に、PTEを集めただけのものです。もう既にご想像いただけたかと思いますが、

PTEの配列を作れば、その配列がページテーブルとなります。(もうそのまんまで。。。)



但し、条件があります。ページテーブルはPTEを1024個を集めたものです。それより多くても、

それよりも少なくてもダメです。(そのページを絶対に参照しなければ少なくてもいいのですが。。。)

ページテーブルはPTEが1024集まった配列です

ページのサイズは4096バイト固定でした。1つのPTEが1つのページを表しますので、

1つのページテーブルは、4096バイト×1024個=4194304バイト(4MB)のメモリサイズを表せます

また、ページテーブル自体のサイズは、PTEが32ビットでそれが1024個ありますので

4バイト(32ビット)×1024個=4096バイトとなります。



これをC言語的に宣言しますと、先ほど定義しておいたPTE型を使って


    PAGE_TABLE_ENTRY page_table[ 1024 ];



と宣言するような感じになります



このように、ページを複数集めてページテーブルを作りましたが、先ほどの計算のとおり

ページテーブルは4MBまでしか表すことができます。ここで、もうご想像いただけたかと思いますが、

複数のページテーブルをまとめて、ディレクトリを作ってしまいます(概念の話です。。。)

ページテーブルを集めたものをPDT(ページディレクトリテーブル)と言います

そして、その1つ1つのページテーブルを表したものをPDE(ページディレクトリエントリ)と言います

ページディレクトリエントリ(PDE:Page Directory Entries)

ページディレクトリテーブルはページテーブルを集めたものです

ページテーブルがPTEを1024個まとめたものでしたが、ページディレクトリテーブルは

ページテーブルを1024個まとめたものです。但し、ただのページテーブルの配列ではなくて、

ページテーブルを表したPDE(ページディレクトリエントリ)の配列となります

ページテーブルエントリとページテーブルとページディレクトリテーブルは次のような構成となっています

ページディレクトリテーブルはPDEが1024集まった配列です。PDEはページテーブルを指します

このようにページディレクトリテーブルのPDEが1つのページテーブルを表しています

ページテーブルは1つで4MBのメモリ領域を表していました。ページディレクトリテーブルは

ページテーブルが1024個ありますので、4MB×1024個=4096MB(4GB)のメモリを表しています

4GB!そうです、32ビットのメモリ空間の最大値の4GBをこれで表すことができます

後は、PDEの正体がわかれば仮想メモリ空間が作れそうです



では早速、ページディレクトリの1つ1つの要素、PDEを見て行きましょう

PDE

PDEは1つのページテーブルを表しています。PTEと非常に似ていまして、ページの物理アドレスを

設定するのではなく、もちろんページテーブルの物理アドレスを設定します。

PDEは次のようなフォーマットになっています

(PTEと異なる箇所は太字にしていますのでご参考にしてください)

PDEのフォーマット

PDE(ページディレクトリエントリ)のフォーマット
ビット ラベル名 説明
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 level Cache Disableフラグ。ページテーブルのページのキャッシュを有効/無効に設定します
0:このページのキャッシュを有効にします
1:このページのキャッシュを無効にします

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

<詳細>
このフラグはスティッキー(一度CPUが1に設定すると、その後CPUは0にすることはありません)なフラグです。 必要なときにOSが0にクリアする必要があります。AフラグとDフラグはOSがスワップ用にメモリ管理するために使用します
6 0 予約
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 Ignored このビットにどんな値がセットされていてもCPUは無視します
OSがこのページを管理するために、自由に設定可能です
12-31 Address of Page Table ページフテーブルのアドレスを設定します
このページディレクトリが指しているページテーブルの物理アドレスの上位20ビットをセットします


PTEとほとんど同じように実装していきます

PDEの実装

PTEと同じで32ビット型のPDEの型を準備します


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

    Description : Page Directory Entry


    bit number  label   description
    0           P       0:page group is not in memory
                        1:page gourp is present in memory
    1           R/W     0:page gourp is read only
                        1:page gourp is writable
    2           U/S     0:page gourp is kernel( supervisor )mode 
                        1:page gourp is user mode.cannnot read or write 
                          supervisor pages
    3           PWT     0:cache write back
                        1:cache write through
    4           PCD     0:enable cache
                        1:disable cache
    5           A       0:page gourp has not been accessed
                        1:page gourp has been accessed
    6           0		reserved

    7           PS      0:page size is 4KB
                        1:page size is 4MB
    8           G       0:not global page gourp 
                        1:global page gourp 
    9-11        Ignore  available for kernel
    12-31       address of page table
==============================================================================
*/
typedef unsigned long PAGE_DIRECTORY_ENTRY;



PDEのフラグを定義します。PDEのフラグをセットしたり、クリアしたりするときに使用します


#define DEF_PDE_FLAGS_P             0x00000001
#define DEF_PDE_FLAGS_RW            0x00000002
#define DEF_PDE_FLAGS_US            0x00000004
#define DEF_PDE_FLAGS_PWT           0x00000008
#define DEF_PDE_FLAGS_PCD           0x00000010
#define DEF_PDE_FLAGS_A             0x00000020
#define DEF_PDE_FLAGS_RESERVED      0x00000040
#define DEF_PDE_FLAGS_PS            0x00000080
#define DEF_PDE_FLAGS_G             0x00000100
#define DEF_PDE_FLAGS_AVAILABLE     0x00000E00
#define DEF_PDE_PAGE_TABLE_ADDRESS  0xFFFFF000



関数もPTEと同じように作成します


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :setPdeFlags
    Input       :PAGE_DIRECTORY_ENTRY* entry
                 < PDE to be set to flags >
                 unsigned long flags
                 < flags of PDE to be set >
    Output      :void
    Return      :void

    Description :set flas of PDE
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC INLINE VOID
setPdeFlags( PAGE_DIRECTORY_ENTRY* entry, unsigned long flags )
{
    *entry |= flags;
}

/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :clearPdeFlags
    Input       :PAGE_DIRECTORY_ENTRY* entry
                 < PDE to be cleared of flags >
                 unsigned long flags
                 < flags of PDE to be cleared >
    Output      :void
    Return      :void

    Description :clear attributes of PDE
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC INLINE VOID
clearPdeFlags( PAGE_DIRECTORY_ENTRY* entry, unsigned long flags )
{
    *entry &= ~flags;
}

/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :setPdePageFrameAddress
    Input       :PAGE_DIRECTORY_ENTRY* entry
                 < PDE to be set to page frame address >
                 unsigned long page_table_address
                 < page talbe address of PDE to be set >
    Output      :void
    Return      :void

    Description :set page table address of PDE
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC INLINE VOID
setPdePageFrameAddress( PAGE_DIRECTORY_ENTRY* entry, unsigned long page_table_address )
{
    page_frame_address &= DEF_PDE_PAGE_TABLE_ADDRESS;
    *entry             |= page_table_address;
}

/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :getPdePageTableAddress
    Input       :PAGE_DIRECTORY_ENTRY* entry
                 < PDE to be set to page frame address >
    Output      :void
    Return      :unsigned long
                 < page table address >

    Description :get page table address of PDE
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC INLINE unsigned long
getPdePageTableAddress( PAGE_DIRECTORY_ENTRY* entry )
{
    return( *entry & DEF_PDE_PAGE_TABLE_ADDRESS );
}

/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :isPdePresent
    Input       :PAGE_DIRECTORY_ENTRY* entry
                 < testee >
    Output      :void
    Return      :BOOL

    Description :test present flag of PDE
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC INLINE BOOL
isPdePresent( PAGE_DIRECTORY_ENTRY* entry )
{
    return( ( *entry & DEF_PDE_FLAGS_P ) == DEF_PDE_FLAGS_P );
}

/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :isPdeWritable
    Input       :PAGE_DIRECTORY_ENTRY* entry
                 < testee >
    Output      :void
    Return      :BOOL

    Description :test R/W flag of PDE
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC INLINE BOOL
isPdeWritable( PAGE_DIRECTORY_ENTRY* entry )
{
    return( ( *entry & DEF_PDE_FLAGS_RW ) == DEF_PDE_FLAGS_RW );
}



これでPDEも設定できるようになりました。早速ページングを動かして見ましょう、

といきたいところですが、まだ問題が残っていました。PTE、PDEにそれぞれ、

ページのアドレスとページテーブルのアドレスを設定することで、ページと物理アドレスの関係はわかりました

しかし、ページングを行うにはもう1つの情報そのページがどの仮想アドレスから開始するかという

謎が残っています。。。

これについては次回で説明いたします。



inserted by FC2 system