Linux kernel 分析之二十二:内存管理-page fault处理流程

page fault是Linux内存管理中比较关键的部分。理解了page fault的处理流程,有助于对Linux内核的内存管理机制的全面理解。因为要考虑到各种异常情况,并且为了使内核健壮高效,所以page fault的处理流程是比较复杂的。我把这个繁琐的处理流程放在最后。在page fault处理函数中使用了很多lazy algorithm。它的核心思想是,由于磁盘IO非常耗时,所以把这些操作尽可能的延迟,从而省略不必要的操作。
以下是几种会导致page fault的情景:1.用户态按需调页:
为了提高效率,Linux实现了按需调页。应用程序在装载时,并不立即把所有内容读到内存里,而仅仅是设置一下mm_struct,直到产生page fault时,才真正地分配物理内存。如果没有分配对应的页表,首先分配页表。这种情况下的缺页可能是匿名页(调用do_no_page),可能是映射到文件中的页(调用do_file_page),也可能是交换分区的页(调用do_swap_page)。此外,还可以判断是不是COW(写时复制)。2.主内核页目录的同步:
内核页表信息保存在主内核页全局目录中,虚存段信息放在vm_struct中。进程页表的内核部分要保持与主内核页全局目录的同步。当内核调用vmalloc等函数,对内核态虚拟地址进行非线性映射时,修改主内核页全局目录,但是不修改进程页表的内核部分。这会引起page fault。page fault 处理函数会执行vmalloc_fault里的代码,对进程的页表进行同步。
3.对exception table中的异常操作的处理
内核函数通过系统调用等方式访问用户态的buffer,可能会在内核态导致page fault。这一类page fault是可以被fixup的,所有这些代码的地址都放在exception table中。并且这些代码有异常处理函数,被称为fixup code。page fault 处理函数查找对应的fixup code,并且把返回时的rip设置为fixup code。当page fault处理完毕,内核会调用fixup code,对异常进行处理。典型的例子是copy_from_user。4.堆栈自动扩展
并不是所有的指针越界都会导致SEGV段错误。当指针越界的量很小,并且正好在当前堆栈的下方时,内核会认为这是正常的堆栈扩展,为堆栈分配更多物理内存。5.对用户态指针越界的检查
如果指针越界,并且不是堆栈扩展,那么内核认为是应用程序的段错误,向应用程序强制发送SEGV信号。6.oops
如果page fault不是应用程序引起,并且不是内核中正常的缺页,那么内核认为是内核自己的错误。page fault会调用__die()打印这时的内核状态,包括寄存器,堆栈等等。
Page fault的处理流程如下:1.对参数有效性的检查:
a)如果出错地址在内核态,并且不是vmallloc引起的,那么oops,内核bug
b)如果内核在执行内核线程或者进行不容打断的操作(中断处理程序,延迟函数,禁止抢占的代码),oops
c)如果出错地址在用户态,并且可以在exception table中找到,那么执行exception的处理函数,正常返回,否则,oops2.如果在进程的地址空间vma找不到对应的vma,
a)判断是不是堆栈扩展,如果是,扩展堆栈。
b)如果错误在内核态发生,在exception table寻找异常处理函数:fixupc)如果在用户态,向当前进程发送一个SIGV的信号。

3.如果在进程的地址空间内
a)如果是写访问
i.如果没有写权限,非法访问
ii.如果vma有写权限,pte没有写权限,判断是不是COW,是的话调用
do_wp_page。
b)如果是读访问,没有读权限,非法访问
c)如果不是权限问题,是普通的缺页,调用handle_mm_fault来解决
i.handle_mm_fault,如有需要,分配pud,pmd,pteii.如果页不在内存中
1.如果还没有分配物理内存,调用do_no_page
2.如果映射到文件中,还没有读入内存,调用do_file_page3.如果该页的内容在交换分区上,调用do_swap_page
4.如果在内核态缺页,并且是由于vmalloc引起
a)根据master kernel page table 同步

你可能感兴趣的:(Linux)