深入学习Linux内存管理-缺页异常
- 互联网
- 2025-09-14 02:33:02

1.什么是缺页异常
在Linux虚拟内存管理中,缺页异常(Page Fault) 是CPU在访问虚拟地址时发现对应物理页未就绪时触发的中断。根据触发原因,缺页异常分为两类:
次要缺页(Minor Fault):物理页已存在(如缓存或共享内存),只需建立映射。 主要缺页(Major Fault):需要分配物理页或从磁盘加载数据(如匿名页首次访问或文件页未缓存)。 匿名页面(Anonymous Page) 是指不与任何文件关联的内存页(如进程堆、栈或mmap(MAP_ANONYMOUS)分配的内存),其生命周期与进程绑定,可能被交换到磁盘(Swap)。
当进程访问一个虚拟内存地址时,若该地址对应的物理页尚未分配、权限不足或已被换出到磁盘,CPU 会触发缺页异常。此时,内核接管异常处理流程,完成内存分配、权限修复或数据加载后,进程才能继续执行。
2.缺页异常的内核处理流程Linux 内核的缺页异常处理入口为 do_page_fault(x86 架构),其核心逻辑如下:
1. 硬件触发异常:CPU 将触发异常的虚拟地址存入 CR2 寄存器。进入内核态,保存现场并调用缺页处理函数。
2. 异常原因检查 内核通过以下步骤判断异常原因:
// 伪代码逻辑 if (地址超出进程虚拟空间范围) { 触发 SIGSEGV 信号(段错误); } else if (访问权限不足) { 检查是否可修复(如 COW); } else { 进入物理页分配流程; }3. 物理页分配与映射 内核调用 handle_mm_fault,根据虚拟内存区域(VMA)类型处理:
匿名页(Anonymous Page):分配物理页并填充零(Zero Page)。
文件映射页(File-backed Page):从文件系统读取数据到物理页。
Swap 页:从 Swap 分区加载数据到物理页。
4. 关键数据结构 VMA(vm_area_struct):描述进程虚拟内存区域(如堆、栈、文件映射)。页表项(PTE):存储虚拟地址到物理地址的映射关系及权限标志。 反向映射(Reverse Mapping):加速物理页的回收和 Swap 操作。
2.缺页异常的核心函数分析 1.函数do_page_fault 1.函数定义(以 x86 架构为例) // arch/x86/mm/fault.c void __kprobes do_page_fault(struct pt_regs *regs, unsigned long error_code) { unsigned long address = read_cr2(); // 获取触发异常的虚拟地址 struct vm_area_struct *vma; struct task_struct *tsk; struct mm_struct *mm; int fault; tsk = current; mm = tsk->mm; // 检查异常是否发生在内核态(如内核模块访问非法地址) if (unlikely(fault_in_kernel_space(address))) { if (vmalloc_fault(address) >= 0) // 处理 vmalloc 异常 return; // 触发内核 oops 或 panic bad_area_nosemaphore(regs, error_code, address); return; } // 检查进程是否处于中断上下文或未分配内存 if (unlikely(!mm || pagefault_disabled())) { bad_area_nosemaphore(regs, error_code, address); return; } // 查找虚拟地址对应的 VMA(虚拟内存区域) vma = find_vma(mm, address); if (unlikely(!vma)) { bad_area(regs, error_code, address); return; } // 检查地址是否在 VMA 的合法范围内 if (likely(vma->vm_start <= address && address < vma->vm_end)) { // 调用 handle_mm_fault 处理具体缺页逻辑 fault = handle_mm_fault(vma, address, flags); if (unlikely(fault & VM_FAULT_ERROR)) { // 处理错误(如权限不足) __bad_area(regs, error_code, address, vma, fault); return; } // 成功处理缺页,返回用户态 return; } // 地址不在 VMA 范围内,触发段错误(SIGSEGV) bad_area(regs, error_code, address); } 2.do_page_fault 核心流程1. 硬件触发异常 CPU 将触发异常的虚拟地址存入 CR2 寄存器(x86 特性)。 保存寄存器状态到 pt_regs 结构体,进入内核态。 2. 异常地址合法性检查 内核空间地址:检查是否由 vmalloc 区域访问引发,尝试修复映射。 用户空间地址:检查进程的 mm_struct 是否存在,确认地址是否合法。 3. 查找虚拟内存区域(VMA) 通过 find_vma 函数在进程的 VMA 红黑树中查找包含 address 的 VMA。 VMA 描述了进程虚拟地址空间的属性(如权限、文件映射、堆栈等)。 4. 权限与类型检查 检查访问权限(读/写/执行)是否与 VMA 的 vm_flags 匹配。 根据错误码 error_code 判断异常原因: 写操作触发:error_code & PF_WRITE 用户态触发:error_code & PF_USER 5. 调用 handle_mm_fault fault = handle_mm_fault(vma, address, flags); handle_mm_fault 进一步调用 handle_pte_fault,根据页表项(PTE)状态处理: PTE 不存在:分配物理页(匿名页或文件映射页)。 PTE 存在但权限不足:处理写时复制(COW)或权限升级。 6. 错误处理 若 handle_mm_fault 返回错误(如 VM_FAULT_OOM),向进程发送 SIGSEGV 信号。
2.函数handle_mm_fault // mm/memory.c int handle_mm_fault(struct vm_area_struct *vma, unsigned long address, unsigned int flags) { pgd_t *pgd; // 页全局目录项 p4d_t *p4d; // 四级页目录项(x86 五级分页时为 p4d,否则映射到 pgd) pud_t *pud; // 页上级目录项 pmd_t *pmd; // 页中间目录项 pte_t *pte; // 页表项 // 逐级查找页表项 pgd = pgd_offset(vma->vm_mm, address); p4d = p4d_alloc(mm, pgd, address); pud = pud_alloc(mm, p4d, address); pmd = pmd_alloc(mm, pud, address); pte = pte_offset_map(pmd, address); // 处理 PTE 状态 return handle_pte_fault(vma, address, pte, pmd, flags); } 3.函数handle_pte_fault // mm/memory.c static int handle_pte_fault(struct vm_fault *vmf) { pte_t entry; if (!vmf->pte) { // PTE 不存在 if (vma_is_anonymous(vmf->vma)) return do_anonymous_page(vmf); // 分配匿名页 else return do_fault(vmf); // 文件映射页 } entry = *vmf->pte; if (!pte_present(entry)) { // PTE 存在但页不在内存 if (pte_none(entry)) return do_swap_page(vmf); // 从 Swap 分区加载页 } if (pte_protnone(entry)) // 权限不足(如 COW) return do_numa_page(vmf); if (flags & FAULT_FLAG_WRITE) { if (!pte_write(entry)) // 写操作触发 COW return do_wp_page(vmf); } // 其他错误处理 return VM_FAULT_SIGBUS; } 3.缺页异常的分类 1.缺页异常的基本分类Linux内核将缺页异常分为两大类:Major Fault**(主要缺页)和Minor Fault(次要缺页)。 两者的核心区别在于是否涉及磁盘I/O操*:
深入学习Linux内存管理-缺页异常由讯客互联互联网栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“深入学习Linux内存管理-缺页异常”