read the fucking source code!
--by 鲁迅a picture is worth a thousand words.
--by 高尔基说明:
上篇文章分析到malloc/mmap
函数中,内核实现只是在进程的地址空间建立好了vma
区域,并没有实际的虚拟地址到物理地址的映射操作。这部分就是在page fault
异常错误处理中实现的。
linux内核中的page fault
异常处理很复杂,涉及的细节也很多,malloc/mmap
的物理内存映射只是它的一个子集功能,下图大概涵盖了出现page fault
的情况:
下边就开始来啃啃硬骨头吧。
page fault
的异常处理,依赖于体系结构,因此有必要来介绍一下arm64
的处理。
代码主要参考:arch/arm64/kernel/entry.s
。
arm64在取指令或者访问数据时,需要把虚拟地址转换成物理地址,这个过程需要进行几种检查,在不满足的情况下都能造成异常:
从上图中可以看到,最后都会调到do_mem_abort
函数,这个函数比较简单,直接看代码,位于arch/arm64/mm/fault.c
:
/* * dispatch a data abort to the relevant handler. */ asmlinkage void __exception do_mem_abort(unsigned long addr, unsigned int esr, struct pt_regs *regs) { const struct fault_info *inf = esr_to_fault_info(esr); struct siginfo info; if (!inf->fn(addr, esr, regs)) return; pr_alert("unhandled fault: %s (0x%08x) at 0x%016lx\n", inf->name, esr, addr); mem_abort_decode(esr); info.si_signo = inf->sig; info.si_errno = 0; info.si_code = inf->code; info.si_addr = (void __user *)addr; arm64_notify_die("", regs, &info, esr); }
该函数中关键的处理:根据传进来的esr
获取fault_info
信息,从而去调用函数。struct fault_info
用于错误状态下对应的处理方法,而内核中也定义了全局结构fault_info
,存放了所有的情况。
主要的错误状态和处理函数对应如下:
static const struct fault_info fault_info[] = { { do_bad, sigbus, 0, "ttbr address size fault" }, { do_bad, sigbus, 0, "level 1 address size fault" }, { do_bad, sigbus, 0, "level 2 address size fault" }, { do_bad, sigbus, 0, "level 3 address size fault" }, { do_translation_fault, sigsegv, segv_maperr, "level 0 translation fault" }, { do_translation_fault, sigsegv, segv_maperr, "level 1 translation fault" }, { do_translation_fault, sigsegv, segv_maperr, "level 2 translation fault" }, { do_translation_fault, sigsegv, segv_maperr, "level 3 translation fault" }, { do_bad, sigbus, 0, "unknown 8" }, { do_page_fault, sigsegv, segv_accerr, "level 1 access flag fault" }, { do_page_fault, sigsegv, segv_accerr, "level 2 access flag fault" }, { do_page_fault, sigsegv, segv_accerr, "level 3 access flag fault" }, { do_bad, sigbus, 0, "unknown 12" }, { do_page_fault, sigsegv, segv_accerr, "level 1 permission fault" }, { do_page_fault, sigsegv, segv_accerr, "level 2 permission fault" }, { do_page_fault, sigsegv, segv_accerr, "level 3 permission fault" }, ... };
从代码中可以看出:
do_translation_fault
,实际中do_translation_fault
最终也会调用到do_page_fault
;do_page_fault
;do_bad
,其中未列出来的部分还包括do_sea
等操作函数;
do_translation_fault
do_page_fault
do_page_fault
函数为页错误异常处理的核心函数,与体系结构相关,上图中的handle_mm_fault
函数为通用函数,也就是不管哪种处理器结构,最终都会调用到该函数。
handle_mm_fault
handle_mm_fault
用于处理用户空间的页错误异常:
do_page_fault
函数的流程图中也能看出来,当触发异常的虚拟地址属于某个vma
,并且拥有触发页错误异常的权限时,会调用到handle_mm_fault
函数,而handle_mm_fault
函数的主要逻辑是通过__handle_mm_fault
来实现的。流程如下图:
do_fault
do_fault
函数用于处理文件页异常,包括以下三种情况:
do_anonymous_page
匿名页的缺页异常处理调用本函数,在以下情况下会触发:
do_swap_page
如果访问swap页面
出错(页面不在内存中),则从swap cache
或swap文件
中读取该页面。
由于在4.14内核
版本中,do_swap_page
调用的很多函数都是空函数,无法进一步的了解,大体的流程如下图:
do_wp_page
do_wp_page
函数用于处理写时复制(copy on write
),会在以下两种情况处理:
page cache
中,并以只读模式创建映射,之后发生写访问后,触发cow
;关键的复制工作是由wp_page_copy
完成的:
如对本文有疑问, 点击进行留言回复!!
linux下文本编辑器vim的使用方法(复制、粘贴、替换、行号、撤销、多文件操作)
网友评论