在Linux中,进程和内核都是通过页表PTE访问一个物理页面的,如果无法访问到正确的地址,将产生page fault(缺页异常)。
由于造成内核空间和用户空间的page fault的原因不尽相同,因此其处理流程也有所区别(以x86为例,代码位于"/arch/x86/x86-mm/fault.c")。
void __do_page_fault(...)
{
if (unlikely(fault_in_kernel_space(address)))
do_kern_addr_fault(regs, hw_error_code, address);
else
do_user_addr_fault(regs, hw_error_code, address);
...
}
内核页面由于使用频繁,通常不会被换出,所以这里是"unlikely"。
do_kern_addr_fault()
--> vmalloc_fault()
对于用户空间,需要区分多种情况,page fault的处理显得更为复杂。
do_user_addr_fault()
--> handle_mm_fault()
--> handle_pte_fault()
首先,访问的内存地址必须是合法的,所谓「合法」,就是该地址一定是落在进程的某个VMA区间内。
struct vm_area_struct vma = find_vma(mm, address);
if (unlikely(!vma)) {
bad_area(regs, hw_error_code, address);
return;
}
if (likely(vma->vm_start <= address))
goto good_area;
假设现在一个进程的地址空间分布如下,那么address B是合法的(good area),address A就是非法的(bad area)。
地址落在进程的地址空间内,但对地址的访问权限不对(比如试图写入一个readonly的区域),也是非法的。访问了非法的地址,或者非法地访问了地址,就不是page fault那么简单了,将进一步上升到segmentation fault。
如果地址合法,权限也正确,那么还得分两种情况来讨论。第一种情况是PTE不存在,这会出现在:
- 对于anonymous page,用户空间使用malloc()进行内存申请时(对应底层的实现是mmap或者brk),内核并不会立刻为其分配物理内存,而只是为请求的进程的rbtree管理的vma信息中记录(添加或更改)诸如内存范围和标志之类的信息。
只有当内存被真正使用,触发page fault,才会真正分配物理页面和对应的页表项,即demand alloction,对应的函数实现是do_anonymous_page()。通过mmap映射建立的heap和stack等内存区域,在初始未使用时,也适用于这样的规则。
- 对于page cache, 在发生内存回收后,部分text(code)段的页面会被discard,部分data段的页面会被writeback,之后再次访问这些页面,也将出现page fault。此时,需要从外部存储介质中,将页面内容调回内存,即demand paging,对应的函数实现是do_fault()。
vm_fault_t handle_pte_fault(struct vm_fault *vmf)
{
if (!vmf->pte) {
if (vma_is_anonymous(vmf->vma))
return do_anonymous_page(vmf);
else
return do_fault(vmf);
}
...
}
第二种情况是PTE存在,但其中的"P(resent)"位为0,说明这是一个之前被swap out出去的anonymous page。现在PTE里存储的不是物理页面的编号PPN,而是外部swap area中slot的编号swp_entry_t,需要通过do_swap_page(),执行swap in操作将页面的内容拷贝回内存。
/* orig_pte是指发生page fault时的PTE */
if (!pte_present(vmf->orig_pte))
return do_swap_page(vmf);
发生page fault时,如果目标页面驻留在外部存储器,那么需要开销较大的I/O操作,这种page fault被称为"major"的。而如果目标页面就在内存中(比如swap cache),只是缺少一个对该页面的引用而已,这种page fault不需要重新分配内存页面,代价较小,因此被称为"minor"的。
还是那个图书馆借书的例子,前台相当于内存,书库相当于磁盘,从前台直接取走就是"minor page fault",比如书到期了你还没有看完,可以在前台办完还书手续后马上再借(前提是这本书没有被其他读者预约),付出的代价就是多一次借书手续而已。
而如果你还了两个月再去借这本书,书已经被管理员上架了,你就需要自己去书架上按照类别寻找这本书,花费的时间自然较多,这就是"major page fault"。
参考:
- Understanding page faults and memory swap-in/outs
- http://www.chudov.com/tmp/LinuxVM/html/understand/node35.html
- http://jake.dothome.co.kr/mm-fault/
原创文章,转载请注明出处。