カーネルのページフォルト


Linuxのページング機構は移植性を考慮して、制御レジスタ(cr3)->ページグルーバルディレクトリ(pgd)->ページアッパディレクトリ(pud)->ページミドルディレクトリ(pmd)->ページテーブル(pte)の4段構えでアドレッシングを行っている。X86-32では2段構成のアーキテクチャで、与えられたテーブルディレクトリをそのまま返すことで実装している。
#define pud_offset(pgd, start)          (pgd)
static inline pmd_t *pmd_offset(pgd_t * dir, unsigned long address)
{
       return (pmd_t *) dir;
}
ユーザプロセスのメモリはそれぞれに上記のページテーブルを有していて、実際使っている空間をtask_struct->mm_struct->vm_area_structで管理している。その内ユーザが使えるメモリー空間は32ビットでは3G、それ以降はカーネルが使うようになっている。

カーネルはvm_area_structを管理しながら、プロセスのメモリー管理をしている。そしてvm_area_structで管理される実メモリ(ページ)をページテーブルで管理されている。

プロセスが動作するには、mm_structが必要となる。mm_structにはcr3のアドレスも有している。しかしカーネルはこのメモリ管理構造体を有していない。ユーザプロセスからカーネルに切り替わるとそのユーザのmm_structを使っている。ユーザプロセスが起動するとき、3G以降のページテーブルはinitのmm_struct(マスターカーネルテーブル)が複写されている。もしカーネルも独自のmm_struct下のページテーブルで動作するような実装だと、ユーザ空間、カーネル空間とも4Gのアドレッシングが可能となる。しかしこのような実装となっているのは、もしカーネルも独自のページテーブルを有していると、ユーザプロセスからカーネルに切り替わる毎に、cr3レジスタの変更を必要とする。cr3レジスターを変更するとTLBを無効にする。TLBをアドレス変換を行うためのキャッシュである。その負荷が大きいからだということだ。

そのような分けで、カーネルはユーザプロセスのページテーブルを使って動作する。

そこで、カーネルもvmallocで動的にメモリを確保する。ユーザプロセスならメモリー獲得において自分のページテーブルのみを更新すればいい。しかしカーネルでは全てのユーザプロセスのカーネル部分のページテーブルを変更する必要があるわけだ。

ページフォルトが発生すると、do_page_fault関数がコールされる。
void __kprobes do_page_fault(struct pt_regs *regs, unsigned long error_code)
{
   :
       address = read_cr2();
   :
#ifdef CONFIG_X86_32
       if (unlikely(address >= TASK_SIZE)) {
#else
       if (unlikely(address >= TASK_SIZE64)) {
#endif
               if (!(error_code & (PF_RSVD|PF_USER|PF_PROT)) &&
                   vmalloc_fault(address) >= 0)
                       return;
   :
   :
       }
   :
   :
addressにはエラーが発生したアドレスが設定される。そしてそのアドレスがTASK_SIZEより大きいとき、これはカーネル空間で発生したことになる。TASK_SIZEは32ビットでは0xC0000000となっている。そしてこれがカーネルが実行したとか、もろもろのチェックとともに、vmalloc_fault関数が実行され、実際の処理はvmalloc_sync_one関数で行われる。ここがカーネルのページデマンド処理となる。要はマスターグローバルテーブルを該当するプロセスのページテーブルに複写する。
static inline pmd_t *vmalloc_sync_one(pgd_t *pgd, unsigned long address)
{
       unsigned index = pgd_index(address);
       pgd_t *pgd_k;
       pud_t *pud, *pud_k;
       pmd_t *pmd, *pmd_k;

       pgd += index;
       pgd_k = init_mm.pgd + index;

       if (!pgd_present(*pgd_k))
               return NULL;

       pud = pud_offset(pgd, address);
       pud_k = pud_offset(pgd_k, address);
       if (!pud_present(*pud_k))
               return NULL;

       pmd = pmd_offset(pud, address);
       pmd_k = pmd_offset(pud_k, address);
       if (!pmd_present(*pmd_k))
               return NULL;
       if (!pmd_present(*pmd)) {
               set_pmd(pmd, *pmd_k);
               arch_flush_lazy_mmu_mode();
       } else
               BUG_ON(pmd_page(*pmd) != pmd_page(*pmd_k));
       return pmd_k;
}
引数はエラーが発生した時のpgdとアドレスである。まずアドレスのpgdのインデックスからinit_mm.pgdのpud、pmd(変数pud_k,pmd_kに設定)を取得し(pud_offset,pmd_offset関数は該当するページディレクトリがなければ確保している。たぶん)、そして、エラーが発生したpmdのエントリーがなければ、set_pmd関数でinit_mmのpmdをエラーのpmdにセットしている。


最終更新 2010/06/11 17:09:43 - north
(2010/06/11 17:08:41 作成)


検索

アクセス数
3676107
最近のコメント
コアダンプファイル - sakaia
list_head構造体 - yocto_no_yomikata
勧告ロックと強制ロック - wataash
LKMからのファイル出力 - 重松 宏昌
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
Adsense
広告情報が設定されていません。