背景
  • ​Read the fucking source code!​​ --By 鲁迅
  • ​A picture is worth a thousand words.​​ --By 高尔基

说明:

  1. Kernel版本:4.14
  2. ARM64处理器,Contex-A53,双核
  3. 使用工具:Source Insight 3.5, Visio
1. 概述

上篇文章分析到​​malloc/mmap​​函数中,内核实现只是在进程的地址空间建立好了​​vma​​区域,并没有实际的虚拟地址到物理地址的映射操作。这部分就是在​​Page Fault​​异常错误处理中实现的。

Linux内核中的​​Page Fault​​异常处理很复杂,涉及的细节也很多,​​malloc/mmap​​的物理内存映射只是它的一个子集功能,下图大概涵盖了出现​​Page Fault​​的情况:

(十四)Linux内存管理之page fault处理【转】_javascript

下边就开始来啃啃硬骨头吧。

2. Arm64处理

​Page Fault​​的异常处理,依赖于体系结构,因此有必要来介绍一下​​Arm64​​的处理。

代码主要参考:​​arch/arm64/kernel/entry.S​​。

(十四)Linux内存管理之page fault处理【转】_虚拟地址_02

Arm64在取指令或者访问数据时,需要把虚拟地址转换成物理地址,这个过程需要进行几种检查,在不满足的情况下都能造成异常:

  1. 地址的合法性,比如以39有效位地址为例,内核地址的高25位为全1,用户进程地址的高25位为全0;
  2. 地址的权限检查,这里边的权限位都位于页表条目中;

从上图中可以看到,最后都会调到​​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" },
...
};


从代码中可以看出:

  • 出现0/1/2/3级页表转换错误时,会调用​​do_translation_fault​​,实际中​​do_translation_fault​​最终也会调用到​​do_page_fault​​;
  • 出现1/2/3级页表访问权限的时候,会调用​​do_page_fault​​;
  • 其他的错误则调用​​do_bad​​,其中未列出来的部分还包括​​do_sea​​等操作函数;

​do_translation_fault​(十四)Linux内存管理之page fault处理【转】_异常处理_03

​do_page_fault​(十四)Linux内存管理之page fault处理【转】_页表_04

​do_page_fault​​函数为页错误异常处理的核心函数,与体系结构相关,上图中的​​handle_mm_fault​​函数为通用函数,也就是不管哪种处理器结构,最终都会调用到该函数。

3. ​​handle_mm_fault​

​handle_mm_fault​​用于处理用户空间的页错误异常:

  • 进程在用户模式下访问用户虚拟地址,触发页错误异常;
  • 进程在内核模式下访问用户虚拟地址,触发页错误异常;
    从​​do_page_fault​​函数的流程图中也能看出来,当触发异常的虚拟地址属于某个​​vma​​,并且拥有触发页错误异常的权限时,会调用到​​handle_mm_fault​​函数,而​​handle_mm_fault​​函数的主要逻辑是通过​​__handle_mm_fault​​来实现的。

流程如下图:

(十四)Linux内存管理之page fault处理【转】_异常处理_05

3.1 ​​do_fault​

​do_fault​​函数用于处理文件页异常,包括以下三种情况:

  1. 读文件页错误;
  2. 写私有文件页错误;
  3. 写共享文件页错误;

(十四)Linux内存管理之page fault处理【转】_虚拟地址_06

3.2 ​​do_anonymous_page​

匿名页的缺页异常处理调用本函数,在以下情况下会触发:

  1. malloc/mmap分配了进程地址空间区域,但是没有进行映射处理,在首次访问时触发;
  2. 用户栈不够的情况下,进行栈区的扩大处理;

(十四)Linux内存管理之page fault处理【转】_页表_07

3.3 ​​do_swap_page​

如果访问​​Swap页面​​出错(页面不在内存中),则从​​Swap cache​​或​​Swap文件​​中读取该页面。

由于在​​4.14内核​​版本中,​​do_swap_page​​调用的很多函数都是空函数,无法进一步的了解,大体的流程如下图:

(十四)Linux内存管理之page fault处理【转】_javascript_08

3.4 ​​do_wp_page​

​do_wp_page​​函数用于处理写时复制(​​copy on write​​),会在以下两种情况处理:

  1. 创建子进程时,父子进程会以只读方式共享私有的匿名页和文件页,当试图写的时候,触发页错误异常,从而复制物理页,并创建映射;
  2. 进程创建私有文件映射,读访问后触发异常,将文件页读入到​​page cache​​中,并以只读模式创建映射,之后发生写访问后,触发​​COW​​;

(十四)Linux内存管理之page fault处理【转】_错误异常_09

关键的复制工作是由​​wp_page_copy​​完成的:

(十四)Linux内存管理之page fault处理【转】_错误异常_10