ページフォルト
Rev.25を表示中。最新版はこちら。
1. 概要
ページフォルトが発生した時の処理のまとめ。
2. 処理の流れ
ページフォルト発生時の処理の流れを図1に示す。
ページフォルトが発生するとCPU例外のエントリルーチンとして登録されているpage_fault()が呼び出される。page_fault()はC言語のエントリルーチンdo_page_fault()を呼び出す。
do_page_fault()は例外発生の要因となったメモリアクセスがカーネル空間に対するものかユーザ空間に対するものかで、まず処理が大きくわかれる。
カーネル空間(0xc0000000以降)へのアクセスだった場合、それがvmalloc領域へのアクセスであれば、対応する物理ページへのマップを作成してアクセスできるようにする。vmalloc領域でなければ、カーネルのバグなのでPanicする。
ユーザ空間へのアクセスだった場合は、そのアドレスがプロセスの正規に割り当てられているアドレス空間に収まっているかチェックをする。もしアドレス空間外であればプロセスが不正アドレスへアクセスしたことになるので、SIGSEGVを発行してプロセスをセグメンテーションフォルトさせる。また、アドレス空間内であっても、Read Onlyの領域にWriteアクセスをしたようなProtection不正の場合もSIGSEGVを発行する(プロセスが誤ってコードセグメントにデータを書こうした場合などがこのケースに該当する)。アクセスがアドレス空間内でProtectionも問題なければ、handle_mm_fault()を呼び出して、ページフォルトが発生した領域に対するページの割り当て処理へ進む。
handle_mm_fault()ではページフォルトの発生要因となった仮想アドレス空間に対応するPageTableがまだ存在しなければ、ここで作成してhandle_pte_fault()を呼び出す。
handle_pte_fault()はページフォルトが発生した場所の状況に応じて、do_xxx()ルーチンを呼び出してページの割り当てやマップを行い、プロセスがデータにアクセスできるようにする(図2)。
図2 ページフォルトの流れ2
3. 関連関数
page_fault()C言語の関数do_page_fault()を呼び出す。
do_page_fault(regs, error_code)
Bit |
意味 |
---|---|
2^0 |
0 - ページが存在しなかった 1 - Protection Fault |
2^1 |
0 - Readアクセスだった 1 - Writeアクセスだった |
2^2 |
0 - カーネルモードだった 1 - ユーザーモードだった |
/* 例外発生仮想アドレス取得 */ 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: <以下参照>
(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と同じ。 |
(*2) !(vma->vm_flags & VM_WRITE) が真
survive: の処理内容
bad_area: の処理内容
vmalloc()をすると、カーネルのメモリマップであるinit_mmにのみPGD、PTEを設定する。このため、各プロセスのコンテキストでVMALLOC領域にアクセスした場合は、ページのマッピングがされていないため、ページフォルトが発生する。ページフォルト発生後、ここでマップを設定済みのinit_mmからPGDをコピーして物理ページへのマップを作成している。「カーネル空間のメモリマップ」参照。
/* 例外発生アドレスに該当する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)
addressに該当するPage Tableがまだ存在しない場合は、ここで割り当てる (pte_alloc_map())。
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は落ちているが、Present bit以外の * 領域に値が書き込まれている状態。 * * ページアウトされているアドレス空間のPTEには * ページアウト先の情報が書き込まれている。 * また非線型ファイルマッピングをしている領域のPTEには * 以下のBitが立っている * #define _PAGE_FILE 0x040 */ 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); } : :