ページフォルト
Rev.22を表示中。最新版はこちら。
ページフォルトが発生した時の処理のまとめ。
関連関数
page_fault()
ページフォルトのエントリルーチン
C言語の関数do_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: の処理内容
survive: の処理内容
bad_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) が真
(*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()をすると、カーネルのメモリマップである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())。
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)にNo Page時のハンドラが * ないのならdo_anonymous_page()を呼び出して * 匿名ページを割り当てる。 * ハンドラがあれば、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); } : :