不同的架构都会有自己的缺页异常处理机制,Intel X86 也有自己的缺页异常处理机制,缺页异常占用 14# 异常向量。引起缺页异常的原因很多,当发生缺页异常时,硬件会将异常原因存储在指定位置,并服务于缺页异常处理函数。另外硬件还会将发生缺页的地址存储在 CR2 寄存器里,因此缺页异常利用这些信息进行异常处理.
在 Intel X86 架构上以前缺页(页面故障)的原因包括以上几种,硬件上使用一个 32 位寄存器进行维护,每个 Bit 的含义如下:
- BIT0 PRESENT: 置位时表示因为违法页级权限保护,例如进程没有满足 Protection Key 设定的权限导致缺页; 清零时表示因为虚拟内存不在系统物理地址空间,这里分为两种情况,第一种情况是还没有为虚拟内存分配对应的物理内存引起的 #PF, 另外一种情况是已经为虚拟内存分配对应的物理内存,只是物理内存被交换到 SWAP Space 上.
- BIT1 Write/Read: 置位时表示读操作引起的缺页,例如发生缺页时进程正在读虚拟内存; 清零时表示写操作引起的缺页,例如发生缺页时正在写虚拟内核,或者对写保护的虚拟内存执行写操作.
- BIT2 User/Super: 置位表示用户模式访问引起的缺页; 清零表示内核模式访问引起的缺页.
- BIT3 RSVD: 置位表示页表中 RSVD 的标志位被置位引起的.
- BIT4 INSTR: 置位表示因为访问的指令对应的物理内存不存在引起的缺页.
- BIT5 PK: 置位表示违法了 Protection Key 制定的权限引起的缺页
- BIT6 SS: 置位表示访问 Shadow-Stack 引起的缺页
- BIT15 SGX: 违法了 SGX 制定的权限引起的缺页
Intel X86 架构在发生缺页异常时,会将异常的虚拟地址(线性地址)存储到 CR2 寄存器,那么缺页异常处理函数可以从 CR2 寄存器中获得虚拟地址. 寄存器根据架构具有不同的长度,在 i386 架构下是 CR2 寄存器是 32 位寄存器,而在 X86-64 架构下 CR2 则是 64 位寄存器.
asm_exc_page_fault 是 Intel X86 架构缺页异常处理函数的起点,最终调用到 “arch/x86/mm/fault.c” 文件的 exc_page_fault 函数,因此开发者可以使用 bs_debug 工具从该函数跟踪内存在缺页异常处理函数的流动.
Intel X86 架构缺页异常处理流程如上图,起点为 exc_page_fault, 可以看到缺页异常不仅与 ERROR CODE 有关,还与虚拟内存 VMA 有莫大的关系,例如 VMA 采用的文件映射与匿名映射的路径就存在很大的差异,又如 VMA 采用 THP 和 HugeTLB 的缺页路径也差异很大,为了能比较全面的了解缺页异常处理函数,接下来会从 ERROR CODE 与映射方式角度入手进行分析,以此全面深入了解缺页.