首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
Linux 6.6内核内存管理深度解析(三):页表与缺页处理 — 虚拟地址怎么按需映射到物理页
📅 2026/10/15 19:33:30
✍️ 爱科研究院
👁 阅读 3,247
〇、全景页表 缺页 按需分配进程看到的是一整片连续的虚拟地址空间但实际内存是按需分配的——大部分地址只是承诺了访问到的那一瞬才真正映射物理页。实现这个机制靠两样东西多级页表虚拟地址 → 物理地址的映射硬件MMU逐级翻译缺页page fault访问一个还没映射或权限不够的地址MMU 翻译失败、触发缺页异常#PF内核补上映射、再重跑那条指令。命中missCPU 访问虚拟地址MMU 逐级翻译页表页表项不存在或无权限 → #PFdo_page_faultarch/x86/mm/fault.chandle_mm_faultmm/memory.c按 pte 状态分流handle_pte_fault拿到物理页直接访问匿名页do_anonymous_page 分配零页文件页do_fault 从文件读写保护do_wp_page 写时复制一句话主线虚拟地址靠多级页表PGD/P4D/PUD/PMD/PTE映射到物理页访问一个页表项不存在/权限不符的地址时MMU 触发缺页异常#PF内核do_page_fault→handle_mm_fault→handle_pte_fault根据 vma 判断是匿名页do_anonymous_page分配零页还是文件页do_fault从文件读补好页表项后重新执行——这就是按需分配进程地址空间大部分是承诺了但没实际映射的访问到才真正分配。一、5 级页表虚拟地址切成几段逐级翻译1.1 层级结构x86_64 支持4 级LA4848 位虚拟地址和5 级LA5757 位虚拟地址页表。5 级时虚拟地址切成 6 段逐级索引虚拟地址5 级 / LA57 | PGD | P4D | PUD | PMD | PTE | offset | | 9b | 9b | 9b | 9b | 9b | 12b |每一级都是 9 位 512 项正好一页shift 值定义在pgtable_64_types.h// arch/x86/include/asm/pgtable_64_types.h (v6.6, line 62 / 84 / 91)#defineP4D_SHIFT39#definePUD_SHIFT30#definePMD_SHIFT21// PAGE_SHIFT 12offset 占低 12 位5 级PGDIR_SHIFT pgdir_shift 48:56→ 虚拟地址 6 段4 级PGDIR_SHIFT 39:75P4D折叠成 1 项MAX_PTRS_PER_P4D 1:77等效 PGD/PUD/PMD/PTE 四级。翻译过程就是用每一段当前层的索引查表、拿到下一层表的基址最后 PTE 指向真正的物理页。顶层表指针存在每个进程的mm_struct里mm_struct.pgd。注意这是MMU 页表管 CPU 访存进程虚拟地址 → 物理地址和 IOMMU 页表管设备 DMA设备地址 → 物理地址是两套独立的页表别混见 IOMMU 系列。1.2 页表项的状态决定缺页行为每个 PTE 不只是物理页号还带一堆状态位present、write、dirty、accessed…。这些状态正是 §四 缺页分流判断的依据——比如present0说明这一页还没映射或缺页到 swap“write0说明只读写它就触发写时复制”。二、vma进程地址空间的登记簿页表是物理映射的视角但内核还需要逻辑归属的视角——这段地址是代码、堆、栈还是 mmap 的文件这就是vmavirtual memory area// include/linux/mm_types.h (v6.6, line 565)structvm_area_struct{// VMA covers [vm_start; vm_end) addresses within mmunsignedlongvm_start;// 起始地址unsignedlongvm_end;// 结束地址开区间// …constvm_flags_tvm_flags;// 权限/性质可读可写、匿名/文件…// …unsignedlongvm_pgoff;// 在 vm_file 里的页偏移structfile*vm_file;// 映射的文件匿名映射则为 NULL// …};一个进程的地址空间mm_structmm_types.h:673由一串 vma 串起来mm-mmap链表 / 红黑树。关键vm_file是否为空决定了这段地址是匿名还是文件映射——这正是缺页时走do_anonymous_page还是do_fault的分水岭见 §五。三、缺页入口硬件 #PF →do_page_fault访问非法地址时CPU 触发 #PF 异常经 IDT 进到// arch/x86/mm/fault.c (v6.6, line 1517)DEFINE_IDTENTRY_RAW_ERRORCODE(exc_page_fault){// …handle_page_fault(regs,error_code,address);// fault.c:1493}handle_page_fault按 faulting 地址在内核空间还是用户空间分流fault_in_kernel_spacefault.c:1502用户地址走do_user_addr_faultfault.c:1239。do_user_addr_fault的核心逻辑查 vmalock_mm_and_find_vmafault.c:1385找到覆盖这个地址的 vma地址不在任何 vma 里 → 非法访问 → 发SIGSEGV权限检查访问类型读/写/执行和vm_flags不符 →SIGSEGV保护违例调handle_mm_fault合法的缺页交给 mm 子系统补映射补好了 → 返回用户态重跑那条指令补不了 → 发信号终止进程SIGSEGV/SIGBUSOOM 则触发 OOM killer。handle_mm_faultmemory.c:5255→__handle_mm_faultmemory.c:5031逐级往下走页表PGD→P4D→PUD→PMD中间的中间表项不存在就现建直到PTE 层交给handle_pte_fault做最终分流。四、handle_pte_fault按 PTE 状态分流handle_pte_faultmemory.c:4947是缺页处理的核心分岔口——看 PTE 处于什么状态决定怎么办// mm/memory.c (v6.6, line 4947)staticvm_fault_thandle_pte_fault(structvm_fault*vmf){// … 定位/建立 PTE …if(!vmf-pte)returndo_pte_missing(vmf);// ① PTE 不存在 → 真正的缺页if(!pte_present(vmf-orig_pte))returndo_swap_page(vmf);// ② 页被换出到 swap → 换回来if(pte_protnone(vmf-orig_pte)vma_is_accessible(vmf-vma))returndo_numa_page(vmf);// ③ NUMA 迁移标记 → 迁回本节点// ④ PTE 存在写访问 只读 → 写时复制COWif(vmf-flags(FAULT_FLAG_WRITE|FAULT_FLAG_UNSHARE)){if(!pte_write(entry))returndo_wp_page(vmf);// …}// ⑤ 其他只是刷新 accessed/dirty 位直接返回}四种典型分支PTE 状态走哪个 handler含义pte_none不存在do_pte_missing真正的第一次访问按需分配/读入!pte_presentdo_swap_page页被 swap 出去了从 swap 换回pte_protnonedo_numa_pageNUMA 自动平衡按需把页迁到访问它的节点present 但!pte_write且写访问do_wp_page写时复制COW其中 ①do_pte_missing是最常见的缺页本义也是按需分配的核心——往下看。五、匿名页 vs 文件页do_pte_missing的分水岭do_pte_missingmemory.c:3667的判断极简就一个条件——这个 vma 是不是匿名的// mm/memory.c (v6.6, line 3667)staticvm_fault_tdo_pte_missing(structvm_fault*vmf){if(vma_is_anonymous(vmf-vma))returndo_anonymous_page(vmf);// 匿名分配零页elsereturndo_fault(vmf);// 文件从文件读}5.1 匿名页do_anonymous_page分配零页堆、栈、mmap(MAP_ANONYMOUS)都是匿名映射vm_file NULL。第一次访问时分配一个清零的物理页memory.c:4067// mm/memory.c (v6.6, line 4067)staticvm_fault_tdo_anonymous_page(structvm_fault*vmf){// 读访问直接用全局的零页所有进程共享不用真分配if(!(vmf-flagsFAULT_FLAG_WRITE)!mm_forbids_zeropage(vma-vm_mm)){entrypte_mkspecial(pfn_pte(my_zero_pfn(vmf-address),…));// …gotosetpte;}// 写访问真正分配一个清零页foliovma_alloc_zeroed_movable_folio(vma,vmf-address);// …}两个细节读匿名页直接用全局共享的零页省一次真分配写才真正从 buddy上一篇 #2分配一个清零页。这正是按需分配最直观的体现。5.2 文件页do_fault从文件读mmap(文件)的 vma 有vm_file。do_faultmemory.c:4678按是否共享 是否写再分三种// mm/memory.c (v6.6, line 4678节选/* 只读 → 直接从 page cache 读 */retdo_read_fault(vmf);// :4550/* 私有 写 → 先读进来再写时复制 */retdo_cow_fault(vmf);// :4583/* 共享 写 → 读进来直接写写穿回文件 */retdo_shared_fault(vmf);// :4627底层都走__do_faultmemory.c:4181→vma-vm_ops-fault()具体文件系统的回调把页从文件读进 page cache再填 PTE。这里体现了页缓存page cache“的角色文件页缺页本质是page cache 里没有这一页去磁盘读进来”。六、为什么这样设计Why 层6.1 为什么用多级页表而不是一张大平铺表48 位地址空间按 4KB 页算有 2^36 个页。若用一张平铺表每个进程要 2^36 × 8 字节 512GB 的表——荒谬。多级页表让稀疏地址空间只占实际用到的部分只有被访问的地址路径才会一层层分配中间页表。这是按需思想在页表结构上的体现。6.2 为什么缺页才分配按需分配而不是 mmap 时就分配因为进程申请的内存绝大部分申请了根本用不到或很久以后才用。如果malloc(1GB)立刻分配 1GB 物理页既慢又浪费。“承诺时只登记 vma访问时才分配物理页”把成本推迟到真正用的时候——这是虚拟内存最核心的设计。6.3 为什么把匿名/文件当分水岭而不是别的方式因为这是两种来源根本不同的内存匿名页没有来源凭空清零文件页有来源磁盘文件。缺页时前者造一个零页、后者从文件读进来处理逻辑完全不同。用vma-vm_file NULL这一个位就能分流简洁且正确。6.4 为什么读匿名页用共享零页而不分配大量程序会malloc一大块但只读不写或暂时只读。这些页内容全是 0让所有进程共享同一个物理零页省下海量内存一旦有进程要写再通过 COWdo_wp_page给它单独复制一份。读共享零页 写时复制是匿名页省内存的关键组合。七、与已有博客的交叉引用已有博客本篇覆盖的关系内存 #2 buddy本篇 §5.1do_anonymous_page写访问时vma_alloc_zeroed_movable_folio就是从 buddy 分配物理页——缺页是 buddy 的下游消费者内存 #1 物理内存初始化#1 建好了struct pagevmemmap和 buddy本篇的页表/缺页才能工作CPU #1 BSP 拉起#1 讲%cr3切页表内核页表本篇讲的是进程级页表 缺页IOMMU 系列本篇是 MMU 页表CPU 访存IOMMU 页表设备 DMA是另一套缺页逻辑也不同见 [[blog-iommu-expansion]]附本篇关键源码索引符号位置exc_page_fault/handle_page_fault/do_user_addr_faultarch/x86/mm/fault.c:1517/:1493/:1239PGDIR_SHIFT/P4D_SHIFT/PUD_SHIFT/PMD_SHIFTarch/x86/include/asm/pgtable_64_types.h:56/:62/:84/:91struct vm_area_structvm_start/vm_end/vm_flags/vm_fileinclude/linux/mm_types.h:565struct mm_struct进程地址空间含pgdinclude/linux/mm_types.h:673handle_mm_fault/__handle_mm_faultmm/memory.c:5255/:5031handle_pte_faultPTE 状态分流mm/memory.c:4947do_pte_missing匿名/文件分水岭mm/memory.c:3667do_anonymous_page匿名页mm/memory.c:4067do_fault/do_read_fault/do_cow_fault/do_shared_faultmm/memory.c:4678/:4550/:4583/:4627__do_fault→vm_ops-fault()mm/memory.c:4181do_wp_page写时复制mm/memory.c:3338do_numa_pagemm/memory.c:4739
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/10/15 19:33:30
fastjson 1.2.84 能在 JDK 7 上跑吗?实测:编译能过,一启动就 UnsupportedClassVersionError
2026/10/15 19:33:30
品牌被跟卖?eCommerce-Skills如何用AI技能抵御劫持、假货与违规定价
2026/10/15 19:33:30
商业模式画布为什么半年就没人看了:把九宫格当数据结构维护
2026/10/15 20:39:14
browser-logos 中的 Firefox v57-70 历史版 Logo 包:文件构成、安装方式与自动发布管线解析
2026/10/15 20:39:14
别再盲目调参!Cursor + Claude Code 联合编程的黄金工作流:把 settings 改到 TaoToken
2026/10/15 20:39:14
Python 和 PyCharm 下载安装过程 + 学习心得
2026/10/15 20:39:14
FilesEditor 源码剖析:Android 内部存储与外部存储的文件读写实战
2026/10/15 20:39:14
Ambari Agent中ComponentStatusExecutor原理与实战解析
2026/10/15 20:34:13
华为OBS Java SDK 3.20.3生产集成指南:高并发、大文件、断点续传
2026/10/15 0:02:59
AI Coding时代的柔性之美:从静态软件到自演化软件,软件研发正在重新进化
2026/10/15 0:02:59
亨得利客户服务中心官方网点分布一览:2026年全国多城门店地址与最新联系电话公布
2026/10/15 0:02:59
防关联浏览器哪个好?三款产品实测差异解析
2026/10/13 12:25:09
流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南
2026/10/14 18:35:19
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别
2026/10/14 14:52:59
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容
2026/10/15 1:07:55
我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
2026/10/15 5:07:59
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026/10/15 8:10:01
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)