Linux Kernel(2.6)の実装に関するメモ書き

ページフォルト


Rev.21を表示中。最新版はこちら

ページフォルトが発生した時の処理のまとめ。

関連関数

page_fault()
ページフォルトのエントリルーチン
C言語の関数do_page_fault()を呼び出す。

do_page_fault(regs, error_code)
ページフォルトが発生した要因を調べて、各処理に振り分ける。

表1 error_codeの意味
Bit
意味
2^0
0 - ページが存在しなかった
1 - Protection Fault
2^1
0 - Readアクセスだった
1 - Writeアクセスだった
2^2
0 - カーネルモードだった
1 - ユーザーモードだった


do_page_fault()の処理概要
/* 例外発生仮想アドレス取得 */
address = read_cr2();

/* 例外発生アドレスがカーネル空間かチェック */
if (unlikely(address >= TASK_SIZE)) {
    /* カーネル空間(TASK_SIZE(0xC0000000)以降)で
     * 例外が発生していた */

    /* カーネル空間でページが存在しない場合は、
     * vmalloc()で確保した領域である可能性があるので
     * vmalloc_faultに飛ばす */
    if (!(error_code & 5))
        goto vmalloc_fault;
    goto bad_area_nosemaphore;
}

/* カレントプロセスのアドレス空間mmから
 * 仮想アドレスaddressに該当するvmaを取得 */
vma = find_vma(mm, address);
if (!vma)
    goto bad_area;  /* 仮想アドレス空間外だった */
if (vma->vm_start <= address)
    goto good_area;
if (!(vma->vm_flags & VM_GROWSDOWN))
    goto bad_area;
if (error_code & 4) {
    if (address + 32 < regs->esp)
        goto bad_area;
}
if (expand_stack(vma, address))
    goto bad_area;

/* 以下、各ラベル毎に正常/異常処理がある */
good_area:
 <以下参照>
survive:
 <以下参照>
bad_area:
 <以下参照>


good_area: の処理内容
error_codeを調べて処理を振り分ける。

表2 error_codeによる処理の振り分け
(error_code & 3) 意味
処理
0
Readしたらページがなかった
アクセスしたアドレスのvmaがReadも実行もできない領域だったなら(*1)、bad_area:へ。
そうでなければ、survive:へ。
1
ReadしたらProtection Faultになった
bad_area:へ
2
Writeしたらページがなかった アクセスしたアドレスのvmaがWrite不可の領域だったら(*2)、bad_area:へ。
そうでなければ、survive:へ。
3
WriteしたらProtection Faultになった 2と同じ。
(*1) !(vma->vm_flags & (VM_READ | VM_EXEC)) が真
(*2) !(vma->vm_flags & VM_WRITE) が真

survive: の処理内容
handle_mm_fault()を呼び出して、ページフォルト要因に応じた処理を行わせる。

bad_area: の処理内容
不正な領域にアクセスしていた時の処理を行う。ユーザーモードでのアクセスだったらプロセスにSIGSEGVを発生させる。

vmalloc_fault:の処理内容
vmalloc()で割り当てたアドレス空間にアクセスしたが、まだページが割り当てられていなかった場合の処理を行う。アクセスしたアドレスがvmalloc()した領域でない場合は、no_contextに飛ばしてエラー処理を行う。

vmalloc()をすると、カーネルのメモリマップであるinit_mmにのみPGD、PTEを設定する。このため、各プロセスのコンテキストでVMALLOC領域にアクセスした場合は、ページのマッピングがされていないため、ページフォルトが発生する。ページフォルト発生後、ここでマップを設定済みのinit_mmからPGDをコピーして物理ページへのマップを作成している。「カーネル空間のメモリマップ」参照。

vmalloc_fault: の処理概要
/* 例外発生アドレスに該当するPage Directory内の
 * インデクスを求める */
int index = pgd_index(address);

/* Page Directoryの物理アドレス */
pgd_paddr = read_cr3();
/* addressに該当するPage Directoryの
 * エントリへのポインタを取得 */
pgd = index + (pgd_t *)__va(pgd_paddr);
/* マスターであるカーネルメモリマップの
 * Page Directoryのエントリへのポインタを取得 */
pgd_k = init_mm.pgd + index;

/* マスターのPage Directoryにエントリが存在しなければ
 * addressはマップされていない(vmalloc()されていない)領域
 * なのでno_contextへ */
if (!pgd_present(*pgd_k))
    goto no_context;

/*
 * 以下でマスターのPage Directoryの内容を
 * カレントプロセスのPage Directoryにコピーして
 * 物理ページをマッピングする
 */

/* i386系ではpudは存在しないので、pud_offset()は
 * pgdをそのまま返す */
pud = pud_offset(pgd, address);
pud_k = pud_offset(pgd_k, address);
if (!pud_present(*pud_k))
    goto no_context;

/* i386系ではpmdは存在しないので、pmd_offset()は
 * pudをそのまま返す */
pmd = pmd_offset(pud, address);
pmd_k = pmd_offset(pud_k, address);
if (!pmd_present(*pmd_k))
    goto no_context;
/* マスターのPage Directoryのエントリを
 * カレントプロセスのPage Directoryにコピーして
 * メモリマップを同期させる */
set_pmd(pmd, *pmd_k);

/* addressに該当するPage Table Entryのアドレス */
pte_k = pte_offset_kernel(pmd_k, address);
/* Page Table Entryがなかったら不正な領域への
 * アクセスなのでno_contextへ */
if (!pte_present(*pte_k))
    goto no_context;
return;


__handle_mm_fault(mm, vma, address, write_access)
handle_mm_fault()の処理の実体。

メモリマップ(mm)からaddressに該当するPage Table Entry(PTE)を求めてhandle_pte_fault()を呼び出す。

addressに該当するPage Tableがまだ存在しない場合は、ここで割り当てる (pte_alloc_map())。


handle_pte_fault()
要因に応じたハンドラルーチンを呼び出す。

handle_pte_fault()の処理概要
old_entry = entry = *pte;
if (!pte_present(entry)) {
    if (pte_none(entry)) {
        /* ページが存在しない */
        /* アドレス空間(vma)にPage Fault時のハンドラが
         * ないのなら匿名ページ用ハンドラへ         * そうでなければ、do_no_page()へ。
         * vma->vm_ops->nopageはdo_no_page()から
         * 呼び出される */
        if (!vma->vm_ops || !vma->vm_ops->nopage)
            return do_anonymous_page(mm, vma, address,
                                     pte, pmd, write_access);
        return do_no_page(mm, vma, address,
                          pte, pmd, write_access);
    }

    /* pte_present()が偽だがpte_none()が真である状態とは
     * PTEのPresent bitは落ちているが、
     * 以下のBitが立っている状態
     *   #define _PAGE_FILE      0x040
     *   #define _PAGE_PROTNONE  0x080
     * これらのBitはPresent bitが落ちているときに
     * ソフトウェアが勝手に使用している */

    if (pte_file(entry))
        return do_file_page(mm, vma, address,
                            pte, pmd, write_access, entry);
    return do_swap_page(mm, vma, address,
                        pte, pmd, write_access, entry);
}

/* ページが存在する */

if (write_access) {
    /* Write不可のページにWriteした場合は.
     * do_wp_page()を呼び出す */
    if (!pte_write(entry))
        return do_wp_page(mm, vma, address,
                          pte, pmd, ptl, entry);
    entry = pte_mkdirty(entry);
}
:
:

関連ページ

handle_pte_fault()から呼び出される個々のハンドラは「ページフォルト - 個々のハンドラ」参照。


最終更新 2006/12/30 21:25:25 - kztomita
(2006/03/27 12:53:15 作成)
添付ファイル
pagefault.png - kztomita
pagefault2.png - kztomita


リンク
最近更新したページ
検索