當我們通過頁式存儲管理機制将線性位址轉換成實體位址時,如果這個過程由于遇到某種阻礙而使得CPU無法通路到相應的實體單元,映射便失敗了。 映射失敗時,CPU會觸發一種異常,這種異常稱之為 缺頁異常(page fault exception),進而執行頁面異常處理程式。 失敗的原因: 1)虛拟、實體映射尚未建立 2)實體頁面被swap off 3)通路權限不對應(想要寫隻讀權限記憶體) 可能還有别的原因
CPU在發生頁面異常時,會觸發page_fault_exception處理,最後執行do_page_fault() do_page_fault的部分代碼(開頭):
- 106 asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)
- 107 {
- 108 struct task_struct *tsk;
- 109 struct mm_struct *mm;
- 110 struct vm_area_struct * vma;
- 111 unsigned long address;
- 112 unsigned long page;
- 113 unsigned long fixup;
- 114 int write;
- 115 siginfo_t info;
- 116
- 117 /* get the address */
- 118 __asm__("movl %%cr2,%0":"=r" (address));
- 119
- 120 tsk = current;
- 121
- 122 /*
- 123 * We fault-in kernel-space virtual memory on-demand. The
- 124 * 'reference' page table is init_mm.pgd.
- 58
- 125 *
- 126 * NOTE! We MUST NOT take any locks for this case. We may
- 127 * be in an interrupt or a critical region, and should
- 128 * only copy the information from the master page table,
- 129 * nothing more.
- 130 */
- 131 if (address >= TASK_SIZE)
- 132 goto vmalloc_fault;
- 133
- 134 mm = tsk->mm;
- 135 info.si_code = SEGV_MAPERR;
- 136
- 137 /*
- 138 * If we're in an interrupt or have no user
- 139 * context, we must not take the fault..
- 140 */
- 141 if (in_interrupt() || !mm)
- 142 goto no_context;
- 143
- 144 down(&mm->mmap_sem);
- 145
- 146 vma = find_vma(mm, address);
- 147 if (!vma)
- 148 goto bad_area;
- 149 if (vma->vm_start <= address)
- 150 goto good_area;
- 151 if (!(vma->vm_flags & VM_GROWSDOWN))
- 152 goto bad_area;
(參見 arch/i386/mm/fault.c) 代碼說明:
1. 擷取産生缺頁異常的虛拟位址(線性位址)。産生缺頁異常的虛拟位址被儲存在CR2寄存器中,由于c語言無法讀取CR2寄存器,需要 使用gcc内嵌 彙編實作。 2. 核心中斷/異常機制還會傳回兩個參數:struct pt_regs *regs, unsigned long error_code, regs指向寄存器的副本(儲存“現場”),error_code指明映射失敗的原因。 3. 核心中 可以使用current(宏)取得目前程序的task結構體 ,然後我們可以擷取目前程序的mm_struct(儲存虛存管理和映射相關資訊),CPU的映射過程不影響mm_struct結構,但是mm_struct描述了這種映射。 4. 程序出錯: find_vma傳回尋找到的第一個 末尾位址大于address的vma ,根據傳回的vma結果進一步判斷錯誤原因。4.1 第一種 goto bad_area:沒有找到合适的vma,說明address過大,通路的位址屬于系統空間4.2 goto good_area:說明找到合适vma,映射已經建立,原因需要進一步查找除了前面兩種情況,還有可能落在vma之間的洞裡面 4.3 第一種空洞:堆棧區下面的大空洞 堆棧區的vma的vm_flags有VM_GROWSDOWN這個标志位 (這種情況的處理,也是goto good_area,裡面會進一步判斷) 4.4 第二種空洞:vma之間的小空洞
5. 出錯特殊情況
中斷處理程式出錯(程式能夠檢測):
若in_interrupt()傳回非0,說明中斷處理程式出錯
mm_struct為空(程式不能檢測出):
因為mm_struct為空,說明程序的映射尚未建立,因而不可能與目前程序有關,而 in_interrupt()又傳回0,說明中斷異常進行中出錯,不過該錯誤 in_interrupt()無法檢測出來
do_page_fault的部分代碼(bad_area處理):
- 220 /*
- 221 * Something tried to access memory that isn't in our memory map..
- 222 * Fix it, but check if it's kernel or user first..
- 223 */
- 224 bad_area:
- 225 up(&mm->mmap_sem);
- 226
- 227 bad_area_nosemaphore:
- 228 /* User mode accesses just cause a SIGSEGV */
- 229 if (error_code & 4) {
- 230 tsk->thread.cr2 = address;
- 231 tsk->thread.error_code = error_code;
- 232 tsk->thread.trap_no = 14;
- 233 info.si_signo = SIGSEGV;
-
- 234 info.si_errno = 0;
- 235 /* info.si_code has been set above */
- 236 info.si_addr = (void *)address;
- 237 force_sig_info(SIGSEGV, &info, tsk);
- 238 return;
- 239 }
-
error_code的取值和含義:
- 96 /*
- 97 * This routine handles page faults. It determines the address,
- 98 * and the problem, and then passes it off to one of the appropriate
- 99 * routines.
- 100 *
- 101 * error_code:
- 102 * bit 0 == 0 means no page found, 1 means protection fault
- 103 * bit 1 == 0 means read, 1 means write
- 104 * bit 2 == 0 means kernel, 1 means user-mode
- 105 */