当前位置: 首页 > java >正文

#Linux内存管理#缺页中断处理的核心函数是do_page_fault()的工作原理

深入剖析 Linux 内核 do_page_fault() 函数的工作原理

 

do_page_fault() 是 Linux 内核中处理缺页中断的核心函数,它是 ARMv7-A(及 x86 等)架构下虚拟内存管理的关键枢纽。这个函数负责响应硬件缺页异常,判断错误类型,并执行相应的内存管理操作。

 

函数原型与关键参数:

 

void do_page_fault(struct pt_regs *regs, unsigned int esr);

 

 regs: 指向进程在内核态保存的用户态寄存器上下文(包含触发缺页时的 PC、SP 等所有通用寄存器)

 

 esr (Exception Syndrome Register): ARMv7-A 中的 IFSR 寄存器值,包含故障的详细原因代码

 

处理流程详解(针对 ARMv7-A)

 

第一步:获取关键硬件信息 

 

unsigned long address = read_cp15_DFAR(); // 获取故障地址 (DFAR)

 

unsigned int fsr = read_cp15_IFSR(); // 获取故障状态 (IFSR)

 

 address: 导致缺页的虚拟地址 (来自 DFAR - Data Fault Address Register)

 

 fsr: 故障状态码 (来自 IFSR - Instruction Fault Status Register),包含:

  

  FSR[3:0]: 故障类型 (如 0x5=Translation fault, 0x7=Access flag fault)

  

  FSR[5:4]: 域 (Domain)

  

  FSR[6]: 写操作标识 (1=写操作触发)

  

  FSR[10]: 指令中止标记 (0=数据访问)

  

第二步:检查内核空间缺页

 

if (unlikely(address >= TASK_SIZE)) {

    // 在内核空间发生缺页

    if (!user_mode(regs)) {

        // 内核自身导致的缺页 (可能严重错误)

        vmalloc_fault(address); // 处理内核动态映射

        return;

    }

    // 用户进程错误访问内核空间

    bad_area(regs, address);

    return;

}

 

 内核态缺页: 处理 vmalloc 动态映射的延迟分配

 

 用户访问内核空间: 立即终止进程 (SIGSEGV)  

 

第三步:查找对应的 VMA (Virtual Memory Area)

 

vma = find_vma(mm, address);

if (unlikely(!vma)) {

    // 虚拟地址不存在于任何 VMA 中

    bad_area(regs, address);

    return;

}

if (likely(vma->vm_start <= address)) 

    goto good_area; // 成功落在 VMA 范围内

 

 遍历进程的 mm_struct->mmap 链表查找包含该地址的 VMA

 

 未找到说明是非法访问 (SIGSEGV) 

 

第四步:权限检查 (good_area)

 

good_area:

write = (fsr & FSR_WRITE) != 0;

exec = (fsr & FSR_EXEC) != 0;

 

// 检查 VMA 权限是否匹配

if (unlikely(access_error(fsr, vma))) {

    // 没有权限访问此区域 (如写只读区)

    bad_area_access_error(regs, fsr, address, vma);

    return;

}

 

关键检查项:

 

请求类型 VMA 权限要求 可能处理方式

READ VM_READ 正常处理

WRITE VM_WRITE 正常处理或 COW

EXECUTE VM_EXEC 正常处理或 SIGSEGV

WRITE+无VM_SHARED VM_WRITE COW 处理

 

 

第五步:区分多种缺页类型

 

switch (fsr & 0xF) { // 处理 FSR 的故障类型位

 

 

案例 1:首次访问的匿名页(最常见情况)

 

if (handle_mm_fault(vma, address, 

                   write ? FAULT_FLAG_WRITE : 0) &

                   VM_FAULT_ERROR) {

    // 处理分配失败

    oom_kill();

}

 

触发场景:malloc() 后首次写入内存

 

操作:调用 handle_mm_fault()

 

 分配清零的物理页 (通过 alloc_page())

 

 建立页表映射

 

 设置 PTE 的 PTE_VALID | PTE_USER | PTE_WRITE

 

案例 2:写时复制 (Copy-On-Write)

 

if (write && !(vma->vm_flags & VM_SHARED)) {

    if (!(vma->vm_flags & VM_WRITE)) {

        // 尝试写入私有只读页 (COW触发点)

        handle_cow_fault(vma, address);

    }

}

 

触发场景:写入共享库文本段或 fork() 后的父子共享页

 

操作:

 分配新物理页

 

 复制原始页内容 (copy_pte_range())

 

 将进程 PTE 改为指向新副本并标记可写 

 

案例 3:文件支持的页缺页

 

if (vma->vm_ops && vma->vm_ops->fault) {

    ret = vma->vm_ops->fault(vma, address, 

                        write ? FAULT_FLAG_WRITE : 0);

}

 

触发场景:首次访问 mmap 映射的文件区域

 

操作:

 调用文件系统的 filemap_fault()

 

 从磁盘读取数据到新页

 

 更新页表指向缓存页 

 

案例 4:换出页的换入

 

if (pte_swp_swapin_pte(orig_pte)) {

    swap_in_page(address, entry);

}

 

触发场景:访问已被换出到交换区的页面

 

操作:

 

 分配新物理页

 

 从 swap 分区读回数据

 

 重建页表项并清除 swap 标记 

 

案例 5:访问权限位缺失

 

if ((fsr & 0xF) == 0x7) { // Access Flag Fault

    handle_pte_fault(..., FAULT_FLAG_ACCESS);

}

 

ARMv7-A 特有:当 L2 PTE 存在但访问位(APX)未设置时触发

 

操作:仅设置 PTE 的访问位,不需分配物理页 

 

第六步:更新页表和刷新 TLB

 

flush_tlb_page(vma, address); // 使旧TLB项失效

 

关键操作:使用协处理器指令 (CP15)

 

 mcr p15, 0, <addr>, c8, c7, 1 (TLBIMVA - 使单个条目失效)

 

 mcr p15, 0, <domain>, c8, c7, 0 (TLBIALL - 全无效)

 

第七步:错误恢复路径

 

return; // 正常返回将重执行失败指令

 

bad_area:

    force_sig_fault(SIGSEGV, SEGV_MAPERR, ...);

    return;

    

oom: 

    pagefault_out_of_memory(); // 触发OOM killer

    return;

 

 非法访问:发送 SIGSEGV 信号终止进程

 

 内存不足:调用 OOM killer 选择牺牲进程 

 

ARMv7-A 的特有处理考量

 

 1.L1/L2 转换错误区分: 

 

  if (fsr & FSR_L1_PF) handle_l1_translation_fault();

  else handle_l2_translation_fault(); // 最常见的缺页情况 

  

 2.域访问控制处理:

 

  domain = (fsr >> 4) & 0x0F;

  if (domain != DOMAIN_USER) {

   // 内核域访问错误处理

  }

  

 3.精确的异常类型判断:

 

  FSR[3:0] 错误类型 处理方式

  0b0000 Alignment SIGBUS 信号

  0b0101 Section trans 一级页表错误处理

  0b0111 Page trans 二级页表错误 (主要缺页)

  0b1001 Domain section 域权限错误

  

性能关键路径优化

 

__do_page_fault():

    if (kmem_cache_page_fast_alloc()) // 快速路径分配

        goto fast_path;

        

    spin_lock(&mm->page_table_lock); // 慢速路径加锁

    // ...详细处理

    spin_unlock(&mm->page_table_lock);  

 

 

do_page_fault() 通过解析硬件状态码,结合进程内存描述,智能地处理了 10+ 种不同类型的页面错误。它不仅是物理内存分配的入口,更是实现写时复制、内存映射文件、交换机制等高级功能的统一网关。对缺页路径的极致优化直接决定了操作系统整体的内存访问性能。

 

http://www.xdnf.cn/news/16955.html

相关文章:

  • Vulnhub ELECTRICAL靶机复现(附提权)
  • RPG增容2.尝试使用MMC根据游戏难度自定义更改怪物属性(三)
  • (LeetCode 面试经典 150 题) 138. 随机链表的复制 (哈希表)
  • Kotlin单例模式懒汉模式:LazyThreadSafetyMode.SYNCHRONIZED(2)
  • 深度学习(鱼书)day09--与学习相关的技巧(前三节)
  • P10816 [EC Final 2020] Namomo Subsequence|普及+
  • 机器学习实战:KNN算法全解析 - 从原理到创新应用
  • 【LeetCode 热题 100】(三)滑动窗口
  • Windows下定位Mingw编译的Qt程序崩溃堆栈
  • Python编程基础与实践:Python模块与包入门实践
  • 滚珠花键在汽车制造中有哪些高要求?
  • 什么叫湖仓一体
  • 存储过程的介绍、基本语法、delimiter的使用
  • Effective C++ 条款18:让接口容易被正确使用,不易被误用
  • Qwen3 Embedding:新一代文本表征与排序模型
  • [硬件电路-123]:模拟电路 - 信号处理电路 - 常见的高速运放芯片、典型电路、电路实施注意事项
  • 高效游戏状态管理:使用双模式位运算与数学运算
  • 网络基础实操篇-05-路由基础-最佳实践
  • WinForm之NumericUpDown控件
  • linux ssh公钥移除办法
  • Day 29: 复习
  • 保证金率(Margin Ratio)
  • Mybatis学习之获取参数值(四)
  • 力扣面试150题--回文数
  • golang——viper库学习记录
  • AWS上部署Spring Boot应用的完整指南
  • 音视频学习(四十八):PCM和WAV
  • Linux网络-------4.传输层协议UDP/TCP-----原理
  • 深入 Go 底层原理(五):内存分配机制
  • 【笔试真题】2024秋招京东后端开发岗位-第一批笔试