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

日々勉強中。。。

0から作るOS開発

環境準備

環境設定

ブートローダ

カーネルローダ

GRUB

カーネル

ドライバーその他

0から作るOS開発 ページングその2 仮想メモリ管理

前回までの内容

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

今回は前回謎のままとなっていました、そのページがどの仮想アドレスから開始するか

についてと仮想メモリ管理の実装について見ていきたいと思います

ページと仮想アドレス

ページと仮想アドレスの対応は非常にシンプルな対応付けがされています

1番目のPDEが指すページテーブルの1番目のPTEが仮想アドレス0x00000000から4096(0x1000)バイトに対応しています

2番目のPDEが指すページテーブルの1番目のPTEが仮想アドレス0x00000000から4096(0x1000)バイトに対応しています

・・・

1024番目のPDEが指すページテーブルの1024番目のPTEが仮想アドレス0xFFFFF000から4096(0x1000)バイトに対応しています



非常に単純に対応付けされています。文字だけだと訳がわからないので図に描いてみます

ページと仮想アドレスの対応関係

対応する仮想アドレスはページが1つ進むごとに0x1000(4096)ずつ増えていきます。また、

次のPDE(ページテーブル)に進むごとにアドレスは0x00400000ずつ増えていきます。

仮想アドレスから物理アドレスに変換する法則が、なんとなく見えてきた気がします

仮想アドレスから物理アドレスへの変換

プロセッサがプログラムを実行するときに仮想アドレスから物理アドレスに変換します

このときに仮想アドレスからページの設定(PDEやPTE)を参照しています

ではどのように、参照しているのでしょうか?そのロジックを見ていきたいと思います



プロセッサは物理アドレスに変換するときに、仮想アドレスからPDEの何番目か、PTEの何番目かを決めます

そして、参照するPTE(ページ)がわかりましたので、次にPTEからページフレームアドレスを取得し、ページの開始物理アドレスが

わかります。ページの開始アドレスがわかりましたので、開始アドレスにオフセットを足せば、物理アドレスとなります

もう少し詳しくみていきます。ページテーブルはPTEが1024(0から1023)個ありました、また、ページディレクトリテーブルには

PDEが1024(0から1023)個ありました。そしてページのサイズは4096(0から4095)バイトありました。

ここからそれぞれの数字を表すことができるビット数を見ていきます

PDEとPTEとページサイズを表すことができるビット数
エントリ エントリの数 16進数 2進数 必要なビット数
PDE 0-1023 0x000-0x3FF 0000000000b-1111111111b 10ビット
PTE 0-1023 0x000-0x3FF 0000000000b-1111111111b 10ビット
ページサイズ 0-4095 0x000-0xFFF 000000000000b-111111111111b 12ビット


となります。そして、各エントリが必要とするビット数を全部足すと・・・・・・・・32ビットです!!!

そうです、仮想アドレスも32ビット、各エントリを表すビット数も32ビットです。

それぞれを対応させると・・・・

仮想アドレスを物理アドレスに変換

このように仮想アドレス0x00C03100を物理アドレスに変換する場合を考えてみます

0x00C03100は2進数で0000000011bと0000000011bと000100000000bにとなります

それぞれ、PDEは3番目を、PTEは3番目を、ページ内のオフセットは0x100となります

ですので、図のようにまずPDE[2]にアクセスして、エントリに格納されているページテーブルの物理アドレスを

求めます。そして、そのページテーブルの配列からPTE[2]にアクセスして対応するページの開始物理アドレスを

求めます。そこに最後にオフセットを足すと最終的にアクセスしたい物理アドレスが計算できます

このように、計算していると時間がかかりますので、先に紹介しましたTLBというキャッシュを

使って、MMUは計算済みの物理アドレスを素早く取得します

このような関係から次のように計算できます

これで仮想アドレスからPDEインデックス、PTEインデックスに変換できます

マルチタスクとメモリ空間

マルチタスク環境では、プロセスが各々の4GBの仮想メモリ空間を持っています。

そして、各プロセスは個別に仮想メモリ空間を持っていて、お互いの仮想メモリにアクセスすることができません。

各プロセスにどうやって、個別の仮想メモリ空間を持たせているのでしょうか?

ここまで見てきましたように、PDEとPTEで4GBの仮想メモリ空間を作ることができます

マルチタスクと言いましても、プロセスの実行時間を分割して各プロセスを切り替えて実行していきますので、

ある時間では、1つのCPUでは1つのプロセスだけが実行されています。プロセスを切り替えるときに、ついでに

PDEとPTEを各プロセスで切り替えることで各プロセス個別の仮想メモリ空間がなんとなくできそうな感じです。。。

この詳細は別途作っていきたいと思います。



世の中の一般的なOSは、処理とメモリを効率化するために、マルチタスクを行うときに、カーネルを実行する仮想メモリ空間は

変更せずに常に固定となります。(例えば、システムコールを呼び出したときに、カーネルの処理が実行されますが、プロセスは

システムコールを頻繁にに使用するので、その度に切り替えていたのでは処理が遅くなってしまいます。また、かと言って

各プロセスにカーネルのPDE、PTEを個別に持たせていてはメモリが無駄ですので、固定にします)



このように考えていくと、カーネルの仮想メモリ空間とプロセス(ユーザ)が使用するユーザの仮想メモリ空間を分けると

上手くいきそうです。このため、一般的なOSでは4GBのメモリ空間の最初の2GB(または3GB)をユーザ空間、

後ろの2GB(または3GB)をカーネル空間として分けています。(PTEとPDEのフラグにU/Sフラグがありました)

ユーザ空間のPTEとPDEにはユーザ特権を、カーネル空間のPTEとPDEにはスーパーバイザ特権を割り当てることで

ユーザ空間で実行中のプロセスはカーネル空間にアクセスできないようにします

仮想アドレス空間のアドレスマップ

一般的なOSでは上図のようにカーネル空間とユーザ空間を分けて管理しています

例えばLinuxであれば図のように0x00000000から0xBFFFFFFFの3GBをユーザ空間として、また

0xC0000000から0xFFFFFFFFの1GBをカーネル空間として割り当てています。Windowsでは

0x00000000から0x7FFFFFFFの2GBをユーザ空間として、0x80000000から0xFFFFFFFFを

カーネル空間としているものもあります。(WindowsはLinuxのようなアドレス空間も割り当て可能です。)

ユーザ空間が0x00000000からはじまる理由は、過去のソフトウェアがそのように作られているので、

今まで作ってきたソフトウェアを動作させるためです。(ブートローダで見てきましたように、昔のプログラムは

リアルモードで1MBまでのメモリ領域で実行されるように作られているからです。このようなソフトウェアを

再利用するためには、ユーザ空間を前にもってきたほうがよかったためです)

仮想メモリ管理

カーネルことはじめで見てきましたように、このサイトで0から作っているカーネルは

アドレス0x00010000で動作するようにコンパイルしていました。しかし、カーネルは0xC0000000で動作するように作ったほうが

今までの作ったソフトウェアを利用できそうです。(実際にコンパイルしたアプリケーションは仮想アドレスの128MBから開始するように

コンパイルされていました)。しかし、アドレス0x00010000で動作するように作ったカーネルに、0xC0000000のページを割り当てて

ページングを有効にしてしますと、まずそうです。(実際に、動きません。。。)。ではどのようにすればよいのでしょうか?

カーネルを0xC0000000で動かす方策その1

カーネルを0xC0000000をベースアドレスとして動作するようにコンパイルします。そしてカーネルローダで、

カーネルを0x00100000にロードします。そして、カーネルローダのある物理メモリ領域には、

同じアドレスの仮想アドレスのページを割り当て、カーネルをロードする0x0001000以降を

0xC0000000の仮想アドレスとしてページを割り当てるように、PTE、PDEを作ります。

そして、ページングを有効にし、カーネルにジャンプし、カーネルのメモリ初期化処理で、PTE、PDEを

再度セットアップします。この方策が一番実装しやすい方法だと思います。

しかし、マルチブート仕様を満たしたカーネルを作り、GRUBからカーネルを起動させたいときには、

問題が発生します。GRUBではページングを有効にしませんので、0x00100000をベースアドレスとして

カーネルが起動されてしまいますので動きません。。。

カーネルを0xC0000000で動かす方策その2

方策2では、Linuxなどの一般的なOSでとられている方法です。同様にカーネルを0xC0000000を

ベースアドレスとしてコンパイルします。カーネルローダでは今までと同じようにページングは無効のままで

カーネルにジャンプします。ここで、かなり小難しいテクニックが必要となってきます。カーネルは0x00100000に

ロードだれているのに0xC0000000で動こうとしています。ここで問題が発生してしまします。

グローバル変数や、関数はリンケージによって0xC0000000を基準にアドレスが割り当てられています

ですので、グローバル変数や関数などアドレスが割り当てられているものにアクセスするときには

アクセスする前にアドレスを(0xC0000000-0x00100000=)0xBF00000分を引いたアドレスに

アクセスするようにプログラムしておいきます。難しいです。。。



最初からこれはなかなか難しいので、最初にページング試してみるときは、仮想アドレス0x00100000以降を

カーネルに割り当て、物理アドレス=仮想アドレスとしておけば、ページングのプログラムできているかどうかが

比較的簡単になります。ページングのプログラムがちゃんと動くことを確認してから、方策その2を実装したほうが

やりやすいと思います。(いちいち0xC0000000をマッピングするんじゃなくて、自分のやり方で実装したいと

いう方はそれはそれでいいと思います。実際にいろいろやってみることをおすすめします)


0xC0000000にカーネルをロードするシンプルな実装方法につきましては、

補足説明のカーネルをメモリーの3GB(0xC0000000)にロードするには を参照してください。

仮想メモリ管理の実装

まずは、仮想メモリ管理で使用する定数などを定義していきます


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

	Description : Virtual Memory

==================================================================================
*/
#define	DEF_MM_PAGE_SIZE            4096

#define	DEF_MM_KERNEL_START         0x00010000

#define	DEF_MM_PTE_INDEX_MASK       0x000003FF
#define	DEF_MM_PTE_INDEX_SHIFT      12

#define	DEF_MM_PDE_INDEX_MASK       0x000003FF
#define	DEF_MM_PDE_INDEX_SHIFT      22

typedef PAGE_TABLE_ENTRY            PAGE_TABLE
typedef PAGE_DIRECTORY_ENTRY        PAGE_DIRECTORY

#define	DEF_MM_NUM_PTE              1024
#define	DEF_MM_PAGE_TABLE_SIZE      ( sizeof( PAGE_TABLE_ENTRY ) * DEF_MM_NUM_PTE )
#define	DEF_MM_NUM_PDE              1024
#define	DEF_MM_PAGE_DIRECTORY_SIZE  ( sizeof( PAGE_DIRECTORY_ENTRY ) * DEF_MM_NUM_PDE )

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

	Description : Virtual Memory Status

==================================================================================
*/
#define	DEF_MM_OK                   0
#define	DEF_MM_ERROR                (-1)



ページテーブルはPTEの配列で、ページディレクトリはPDEの配列となります。

ページ領域と物理領域の割り当てと解放

ページに物理領域を割り当てます。

まず物理メモリ管理で作成した、

物理メモリ割り当て関数allocSingleMemoryBlockで4096バイトの物理メモリを割り当てます

(PTEは4バイトで、ページテーブルはPTEが1024個=4096バイトとなります。不思議と

ページのサイズと一緒になります。よく考えられているなあと思います)

そして割り当てられた物理メモリのアドレスをPTEのページフレームアドレスフィールドに設定します

これで物理メモリとページの対応付けができましたので、Pフラグ(プレゼントフラグ)もセットします

ページに割り当てられた物理アドレスを返します


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :allocPage
    Input       :PAGE_TABLE_ENTRY* entry
                 < PTE to be allocated >
    Output      :void
    Return      :PAGE_TABLE_ENTRY*
                 < allocated pte >

    Description :allocate a page
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC PAGE_TABLE_ENTRY*
allocPage( PAGE_TABLE_ENTRY* entry )
{
    VOID	*physical_address;

    physical_address = allocSingleMemoryBlock( );

    if( physical_address == Null )
    {
        return( ( PAGE_TABLE_ENTRY* )physical_address ) );
    }

    setPtePageFrameAddress( entry, ( unsigned long )physical_address );
    setPteFlags( entry, DEF_PTE_FLAGS_P );

    return( ( PAGE_TABLE_ENTRY* )physical_address ) );
}



ページに割り当てていた物理メモリを解放します

物理メモリ管理で作成した、

物理メモリ解放関数freeSingleMemoryBlockで4096バイトの物理メモリを解放し、

ページに割り当てられていた物理メモリブロックを解放します。その後に、Pフラグを

クリアして、ページが物理メモリに無い状態にしておきます


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :freePage
    Input       :PAGE_TABLE_ENTRY* entry
                 < PTE to be freed >
    Output      :void
    Return      :void

    Description :free a page
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC VOID
freePage( PAGE_TABLE_ENTRY* entry )
{
    VOID	*physical_address;

    physical_address = ( VOID* )getPtePageFrameAddress( entry );

    if( physical_address == Null )
    {
        return;
    }

    freeSingleMemoryBlock( ( VOID* )physical_address );
    clearPteFlags( entry, DEF_PTE_FLAGS_P );
}



仮想メモリアドレスからPTE、PDEを取得する

仮想メモリアドレスに対応する、PTEをページテーブルから取得します

まずは、ページテーブルが持つ1024個あるPTEの配列のインデックスを計算します

仮想アドレスを右に12ビットシフトして0x3FFでANDします


/*
==================================================================================
    Funtion     :getPteIndex
    Input       :unsigned long virtual_address
                 < virtual address >
    Output      :void
    Return      :unsigned long
                 < pte index >
    Description :get pte index by virtual address
==================================================================================
*/
PRIVATE INLINE unsigned long
getPteIndex( unsigned long virtual_address )
{
    unsigned long index;

    index = ( virtual_address >> DEF_MEMORY_PTE_INDEX_SHIFT )
            & DEF_MEMORY_PTE_INDEX_MASK

    return( index );
}



次に取得したPTEインデックスでページテーブルのエントリを取得します


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :getPTE
    Input       :PAGE_TABLE* table
                 < page table >
                 unsigned long virtual_address
                 < virtual address >
    Output      :void
    Return      :PAGE_TABLE_ENTRY*
                 < indexed pte >

    Description :get page table entry indexed by virtual address
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC INLINE PAGE_TABLE_ENTRY*
getPTE( PAGE_TABLE* table, unsigned long virtual_address )
{
    PAGE_TABLE_ENTRY *entry;

    if( table == Null )
    {
        return( ( PAGE_TABLE_ENTRY* )Null );
    }

    /* ------------------------------------------------------------------------ */
    /*  get first pte in the talbe                                              */
    /* ------------------------------------------------------------------------ */
	entry = ( PAGE_TABLE_ENTRY* )table;

    return( &entry[ getPteIndex( virtual_address ) ] );
}



仮想メモリアドレスに対応する、PDEをページディレクトリから取得します

まずは、ページディレクトリが持つ1024個あるPDEの配列のインデックスを計算します

仮想アドレスを右に22ビットシフトして0x3FFでANDします


/*
==================================================================================
    Funtion     :getPdeIndex
    Input       :unsigned long virtual_address
                 < virtual address >
    Output      :void
    Return      :unsigned long
                 < pde index >
    Description :get pde index by virtual address
==================================================================================
*/
PRIVATE INLINE unsigned long
getPdeIndex( unsigned long virtual_address )
{
    unsigned long index;

    index = ( virtual_address >> DEF_MM_PDE_INDEX_SHIFT )
            & DEF_MM_PDE_INDEX_MASK

    return( index );
}



次に取得したPDEインデックスでページディレクトリからエントリを取得します


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :getPDE
    Input       :PAGE_DIRECTORY* directory
                 < page directory >
                 unsigned long virtual_address
                 < virtual address >
    Output      :void
    Return      :PAGE_DIRECTORY_ENTRY*
                 < indexed pde >

    Description :get page directory entry indexed by virtual address
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC INLINE PAGE_DIRECTORY_ENTRY*
getPDE( PAGE_DIRECTORY* directory, unsigned long virtual_address )
{
    PAGE_DIRECTORY_ENTRY *entry;

    if( table == Null )
    {
        return( ( PAGE_DIRECTORY* )Null );
    }

    /* ------------------------------------------------------------------------ */
    /*  get first pde in the director                                           */
    /* ------------------------------------------------------------------------ */
    entry = ( PAGE_DIRECTORY_ENTRY* )directory;

    return( &entry[ getPdeIndex( virtual_address ) ] );
}



ページディレクトリの読み込み

ページングを行うには、CPUにページディレクトリの場所を教える必要があります

制御レジスタCR3にページディレクトリのアドレスを格納することで、

CPUはどこにページディレクトリがあるのかがわかります



制御レジスタに値を書き込む関数を作ります


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :writeCPUCR3
    Input       :Unsigned long value
                 < value to write >
    Output      :void
    Return      :void

    Description :write to CR3 register
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC INLINE VOID
writeCPUCR3( unsigned long value )
{
    __asm__ __volatile__( "mov %0, %%cr3" : : "r"( value ) );
}



作成したページディレクトリを制御レジスタCR3に書き込みます

書き込む際に現在CPUが読み込んでいるページディレクトリを保存しておきます

いつでも取得できるように取得関数を作っておきます


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :switchNewPDE
    Input       : PAGE_DIRECTORY *directory
                 < page directory >
    Output      :void
    Return      :void

    Description :switch page direcotry from current to new one
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC INLINE VOID
switchNewPDE( PAGE_DIRECTORY *directory )
{
    if( directory != Null )
    {
        /* ------------------------------------------------------------------- */
        /*  switch new directory                                               */
        /* ------------------------------------------------------------------- */
        writeCPUCR3( ( unsigned long )directory );
    }
}

PRIVATE PAGE_DIRECTORY		*current_pd;

/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :getCurrentPageDirectory
    Input       :void
    Output      :void
    Return      :PAGE_DIRECTORY *directory
                 < current page directory >

    Description :get current page diretory
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC INLINE PAGE_DIRECTORY*
getCurrentPageDirectory( VOID )
{
    return( current_pd );
}



TLBをフラッシュする

TLBは仮想メモリアドレスを物理メモリアドレスに変換するキャッシュを持っていました。

自動的にキャッシュしてくれますが、ページディレクトリを変更した直後はページが

がらりと変わってしばらくの間はキャッシュが役に立たない(TLBミスばかりする)状況となります

このような場合に、手動でTLBをフラッシュ(現在のキャッシュを捨てる)することができます

TLBをフラッシュするにはINVLPG命令を使用します

TLBをフラッシュする関数を用意しておきます

(さしあたっては必要ありません)


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :invlpg
    Input       :unsigned long virtual_address
                 < virtual address >
    Output      :void
    Return      :void

    Description :invoke invlpg instruction
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC INLINE VOID
invlpg( unsigned long virtual_address )
{
    __asm__ __volatile__( "cli" );
    __asm__ __volatile__( "invlpg %0" : : "m"( virtual_address ) : "memory" );
    __asm__ __volatile__( "sti" );
}

/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :flushTLB
    Input       :unsigned long virtual_address 
                 < virtual address to be flushed >
    Output      :void
    Return      :void

    Description :flush tlb
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC INLINE VOID
flushTLB( unsigned long virtual_address )
{
    invlpg( virtual_address );
}



仮想メモリと物理メモリの紐付け

指定した仮想メモリアドレスと物理メモリアドレスを紐付けます

関数の中身を分割して見ていきます


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :mapPage
    Input       :unsigned long physical_address
                 < physical address to map page >
                 unsigned long viertual_address
                 < virtual address which page describes >
    Output      :void
    Return      :STATUS
                 < memory management status >

    Description :map page to physical address
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC STATUS
mapPage( unsigned long physical_address, unsigned long virtual_address )
{
    PAGE_DIRECTORY        *page_directory;
    PAGE_DIRECTORY_ENTRY  *pde;
    PAGE_TABLE            *page_table;
    PAGE_TABLE_ENTRY      *pte;

    /* ------------------------------------------------------------------------ */
    /*  get pages                                                               */
    /* ------------------------------------------------------------------------ */
    page_directory = getCurrentPageDirectory( );

    if( page_directory == Null )
    {
        return( DEF_MM_ERROR );
    }

    pde            = getPDE( page_directory, virtual_address );
    



関数の引数は物理メモリアドレスと仮想メモリアドレスです。大まかな処理としては

仮想メモリアドレスに対応するページを取得して、そのページのPTEに引数の物理メモリアドレスを

設定します。最初の部分では、引数の仮想メモリアドレスからPDEを取得します

ページテーブルが物理メモリ上に存在しているかどうかをPDEのフラグを見て判断します

PDEのPフラグがセットされていなければ、物理メモリ上にページテーブルが無いので

新しくページテーブルの物理メモリを割り当てるようにします


    if( !isPdePresent( pde ) )
    {
        page_table = ( PAGE_TABLE* )allocSingleMemoryBlock( );

        if( page_table == Null )
        {
            return( DEF_MM_ERROR );
        }

        /* -------------------------------------------------------------------- */
        /*  clear page table                                                    */
        /* -------------------------------------------------------------------- */
        kmemset( ( VOID* )page_table, 0x00, DEF_MM_PAGE_TABLE_SIZE );

        /* -------------------------------------------------------------------- */
        /*  create new pde                                                      */
        /* -------------------------------------------------------------------- */
        setPdeFlags( pde, DEF_PDE_FLAGS_P | DEF_PDE_FLAGS_RW );
        setPdePageFrameAddress( pde, ( unsigned long )page_table );
    }
    else
    {
        /* -------------------------------------------------------------------- */
        /*  get page table from page directory entry                            */
        /* -------------------------------------------------------------------- */
        page_table = ( PAGE_TABLE* )getPdePageTableAddress( pde );
    }



ページテーブルが物理メモリ上にありませんので、物理メモリ管理で作成しました、

allocSingleMemoryBlock関数で物理メモリを1ブロック分(4096バイト)割り当てます

そして、ページテーブルの内容をクリアし、PDEにページテーブルを登録します(フラグと物理アドレス)

フラグはPとR/Wフラグをセットしています


    /* ------------------------------------------------------------------------ */
    /*  set up pte                                                              */
    /* ------------------------------------------------------------------------ */
    pte            = getPTE( page_table, virtual_address );

    setPteFlags( pte, DEF_PTE_FLAGS_P | DEF_PTE_FLAGS_RW );
    setPtePageFrameAddress( pte, physical_address );

    return( DEF_MM_OK );
}



ページテーブルが確保できましたので、仮想メモリアドレスから対応したページのPTEを取得します

フラグ(P、R/W)をセットして、引数で指定された物理アドレスをPTEに設定して

仮想メモリアドレスと物理メモリアドレスを対応付けができました

仮想メモリ管理初期化

仮想メモリ管理の初期化で、ページディレクトリ、ページテーブルを作り、それぞれPDE、PTEを

初期化していきます。この初期化例では物理メモリ0x00000000から4MBを仮想メモリ0x00000000から

4MBと対応付けたページの設定を行います。また、物理メモリ0x00100000から4MBを仮想メモリ

0xC0000000から4MBと対応付けたページを設定しています。そして最後にページングを有効にします


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :initVirtualMemoryManagement
    Input       :void
    Output      :void
    Return      :STATUS
                 < memory management status >

    Description :initialize virtual memory management
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC STATUS
initVirtualMemoryManagement( VOID )
{
    PAGE_TABLE              *table_low;
    PAGE_TABLE              *table_high;
    PAGE_TABLE_ENTRY        *pte;

    PAGE_DIRECTORY          *directory;
    PAGE_DIRECTORY_ENTRY    *pde

    int                     i;
    unsigned long           frame;
    unsigned long           virtual_address;

    table_low  = ( PAGE_TABLE* )allocSingleMemoryBlock( );

    if( table_low == Null )
    {
        return( DEF_MM_ERROR );
    }

    table_high = ( PAGE_TABLE* )allocSingleMemoryBlock( );

    if( table_high == Null )
    {
        return( DEF_MM_ERROR );
    }

    /* ------------------------------------------------------------------------ */
    /*  clear page table                                                        */
    /* ------------------------------------------------------------------------ */
    kmemset( ( VOID* )table_low,  0x00, DEF_MM_PAGE_TABLE_SIZE );
    kmemset( ( VOID* )table_high, 0x00, DEF_MM_PAGE_TABLE_SIZE );



0x00000000用のページテーブルと0xC0000000用のページテーブルに、物理メモリ管理で作成しました、

allocSingleMemoryBlock関数で物理メモリブロックを割り当てます

(ここでもページテーブルはPTEが1024個ですので4096バイト割り当てます)

そして、それぞれ初期化しておきます


    /* ------------------------------------------------------------------------ */
    /*  set up pages 0x00000000 - 0x003FF000                                    */
    /* ------------------------------------------------------------------------ */
    for( i = 0, frame = 0x00000000, virtual_address = 0x00000000 ;
         i < DEF_MM_NUM_PTE ;
         frame += DEF_MM_PAGE_SIZE, virtual_address += DEF_MM_PAGE_SIZE )
    {
        pte = getPTE( table_low, virtual_address );
        setPteFlags( pte, DEF_PTE_FLAGS_P | DEF_PTE_FLAGS_RW );
        setPtePageFrameAddress( pte, frame );
    }



物理メモリの最初の0x00000000から4MB分のページを仮想メモリの0x00000000から4MBに

対応付けます。このプログラムのframeが物理アドレス、virtual_addressが仮想メモリアドレスとして

それぞれfor文で4096バイトずつ増やしていき、1ページずつPTEを設定していきます

ページはPフラグをR/Wフラグを設定しています。

    /* ------------------------------------------------------------------------ */
    /*  set up pages 0xC0000000 - 0xC03FF000                                    */
    /* ------------------------------------------------------------------------ */
    for( i = 0, frame = 0x00100000, virtual_address = 0xC0000000 ;
         i < DEF_MM_NUM_PTE ;
         frame += DEF_MM_PAGE_SIZE, virtual_address += DEF_MM_PAGE_SIZE )
    {
        pte = getPTE( table_high, virtual_address );
        setPteFlags( pte, DEF_PTE_FLAGS_P | DEF_PTE_FLAGS_RW );
        setPtePageFrameAddress( pte, frame );
    }



同様に、物理メモリの0x00100000から4MB分を仮想メモリ0xC0000000から4MBに対応づけます

ページングを初めてプログラムしたときは、0x0010000を仮想メモリ0x00100000に対応付けて

ページングが動いているかどうかを確認したほうがテストしやすいかと思います


    directory = ( PAGE_DIRECTORY* )allocSingleMemoryBlock( );

    if( directory == Null )
    {
        return( DEF_MM_ERROR );
    }

    /* ------------------------------------------------------------------------ */
    /*  set up pdes 0x00000000 - 0x003FF000                                     */
    /* ------------------------------------------------------------------------ */
    pde = getPDE( directory, 0x00000000 );
    setPdeFlags( pde, DEF_PDE_FLAGS_P | DEF_PDE_FLAGS_RW );
    setPdePageFrameAddress( pde, ( unsigned long )table_low );

    /* ------------------------------------------------------------------------ */
    /* 	set up pdes 0xC0000000 - 0xC03FF000                                     */
    /* ------------------------------------------------------------------------ */
    pde = getPDE( directory, 0x00000000 );
    setPdeFlags( pde, DEF_PDE_FLAGS_P | DEF_PDE_FLAGS_RW );
    setPdePageFrameAddress( pde, ( unsigned long )table_high );



そして、ページディレクトリに物理アドレスを確保します。確保できたら、先ほど設定した

ページングテーブルをページディレクトリのPDEに設定していきます


    /* ------------------------------------------------------------------------ */
    /*  switch to the new pdes                                                  */
    /* ------------------------------------------------------------------------ */
    switchNewPDE( directory );

    /* ------------------------------------------------------------------------ */
    /*  entering the paging                                                     */
    /* ------------------------------------------------------------------------ */
    pagingOn( );



そして、ページディレクトリを制御レジスタCR3にロードして、CPUにPDEの場所を教えます

最後にページングを有効にして、いよいよページングが動き出します。



ここまでのプログラム例は単純なページの設定例となります

実際には特権レベル別にページを設定する必要がでてきますので、

いろいろ設定を変えてお試しください

ページングを有効にする

ページングを有効にするには制御レジスタCR0のビット31を1にセットします

カーネルローダその3で出てきました制御レジスタCR0をもう一度載せます

32ビットの制御レジスタCR0。CPUの動作モードと状態を制御するレジスタでビット31のPGビットを1にセットするとページングが有効になります

CR0レジスタ
ビット ビット名称 説明
0 PE Protection Enableビット。このビットをONにするとプロテクティッドモードへ移行します
1 MP Monitor Co-Processorビット。モニタ・コプロセッサビット
WAIT命令を実行したときの動作を変更することができます
0:TSビットが0でも1でも関係なく無視する
1:TSビットが1であればコプロセッサ使用不可能例外を発生させる
2 EM Emulationビット。エミュレーションビット
浮動小数点演算(x87 FPU)命令を実行したときの動作を変更することができます
0:x87 FPUを持っているので命令実行可能
1:命令実行時x87 FPUを持っていないのでコプロセッサ使用不可例外が発生する
  ソフトウェアによってエミュレーションを行う
※細かい条件はIntelのCPU仕様書をご確認ください
3 TS Task Switchビット。タスクスイッチビット
CPUはタスクスイッチする時にこのビットを1にします
※EM、MPビットの設定によっては影響があります
4 ET Extended Typeビット。拡張タイプビット
0:80287以前のCPU
1:80387以後のCPU
5 NE 数値演算エラービット
0:x87 FPUエラーレポート無効
1:x87 FPUエラーレポート有効
16 WP Write Protectビット。書き込み保護ビット
0:リング0のプログラムが読み取り専用のユーザ空間に書き込むことができる
1:リング0のプログラムが読み取り専用のユーザ空間に書き込むことを禁止
18 AM Alignment Maskビット。アライメントマスクビット
0:自動アライメントチェック無効
1:自動アライメントチェック有効
29 NW Not Write throughビット。ノットライトスルービット
0:CDビットが0の場合キャッシュをライトバックまたはライトスルーが有効
1:CDビットとNWビットの組み合わせはCPUのマニュアル10-17ページを参照ください
30 CD Cache Disableビット。キャッシュ無効ビット
0:NWビットが0の場合キャッシュ操作が有効
1:CDビットとNWビットの組み合わせはCPUのマニュアル10-17ページを参照ください
31 PG PaGingビット。ページングビット
0:ページング無効
1:ページング有効


制御レジスタCR0に値を書き込む場合は、予約ビットは読みだした値そのままを書き込む必要があります

ですので、一度CR0の値を読みだしてから書き込みます


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :pagingOn
    Input       :void
    Output      :void
    Return      :void

    Description :set page enable bit
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC INLINE void pagingOn( void )
{
    /* ------------------------------------------------------------------------- */
    /*  disable interrupt                                                        */
    /* ------------------------------------------------------------------------- */
    disableInterrupt( );

    /* ------------------------------------------------------------------------- */
    /*  paging on                                                                */
    /* ------------------------------------------------------------------------- */
    __asm__ __volatile__( "PUSH %eax"           );
    __asm__ __volatile__( "MOV %eax, %cr0 "     );
    __asm__ __volatile__( "OR %eax, 0x80000000" );
    __asm__ __volatile__( "MOV %cr0, %eax"      );
    __asm__ __volatile__( "POP %eax"            );
}



このプログラム例では、EAXレジスタの値を退避させ、CR0の値をEAXに格納します

そして、EAXのビット31を1にORでセットします。次にEAXの値をCR0に書き込み

退避させていたEAXを元に戻します。

(各命令の詳細は汎用命令一覧でご確認ください)

ページフォルト

ページングが動き出した後には、Pフラグがセットされていないページにアクセスしようとすると

ページフォルト例外が発生します。割り込みその1割り込みその3で示しましたように

割り込みベクタ番号14がページフォルト例外(#PF)となります。

ページが正しく設定(物理メモリにマップ)されていないと一般保護例外(#GF)が発生します

ページフォルト例外が発生すると、CPUはエラーコードをスタックにPUSHします

割り込みその3の「割り込みハンドラ」をご参照ください)



ページフォルト例外のエラーコードは下記フォーマットとなっています

ページフォルトのエラーコードフォーマット

ページフォルトのエラーコード
ビット ビット名称 説明
0 P 0:ページが物理メモリに存在しいないので、ページフォルトが発生しました
1:ページのアクセス特権レベル以下の特権でアクセスしたので、ページフォルトが発生しました
1 W/R 0:ページフォルトが発生した原因となったアクセスは、読み込みです
1:ページフォルトが発生した原因となったアクセスは、書き込みです
2 U/S 0:ページフォルトが発生した原因となったアクセスは、特権レベルで行われました
1:ページフォルトが発生した原因となったアクセスは、ユーザ特権レベルで行われました
3 RSVD 0:ページフォルトが発生した原因は、PDEの予約ビットが1にセットされたからではありません
1:ページフォルトが発生した原因は、PDEの予約ビットが1にセットされたからです


ページフォルト例外が発生するとCPUはCR2レジスタに例外が発生したリニアアドレスを書き込みます

ページフォルト例外中に更にページフォルトが発生するとCR2レジスタの値は新しい値に変更されて

しまいますので、ご注意ください



一般的なOSはページフォルトのエラーコードとCR2レジスタに格納されるリニアアドレスから判断して

オンデマンドページングを実現しています



次回はキーボードドライバについて説明していきたいと思います

inserted by FC2 system