Linux 6.6内核内存管理深度解析(三):页表与缺页处理 — 虚拟地址怎么按需映射到物理页
〇、全景页表 缺页 按需分配进程看到的是一整片连续的虚拟地址空间但实际内存是按需分配的——大部分地址只是承诺了访问到的那一瞬才真正映射物理页。实现这个机制靠两样东西多级页表虚拟地址 → 物理地址的映射硬件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

相关新闻

fastjson 1.2.84 能在 JDK 7 上跑吗?实测:编译能过,一启动就 UnsupportedClassVersionError

fastjson 1.2.84 能在 JDK 7 上跑吗?实测:编译能过,一启动就 UnsupportedClassVersionError

最近后台看到有人搜「fastjson1.2.84能在jdk1.7环境运行吗」。我拿一台 JDK 7 实际跑了一遍。 先说结论 JDK 8 及以上:没影响。 同一段代码在 JDK 8 上跑 1.2.83 和 1.2.84 都正常。JDK 7:1.2.84 起不来。 javac 只给 5 条警告、退出码是 0,所以构建不会失败;程序一运行就抛 …

2026/10/15 19:33:28 阅读更多 →
品牌被跟卖?eCommerce-Skills如何用AI技能抵御劫持、假货与违规定价

品牌被跟卖?eCommerce-Skills如何用AI技能抵御劫持、假货与违规定价

【免费下载链接】eCommerce-Skills E-commerce skills for AI agents — product research, marketing automation, supply chain optimization, and business analytics for online sellers across Amazon, Shopify, Etsy, TikTok Shop, and all platforms. 项目地址&#xff…

2026/10/15 19:33:28 阅读更多 →
商业模式画布为什么半年就没人看了:把九宫格当数据结构维护

商业模式画布为什么半年就没人看了:把九宫格当数据结构维护

做过一次商业模式画布的团队,多半都有同一个结局:工作坊当天讨论得热火朝天,便签贴满一墙,会后整理成一页 PPT,然后半年没人再打开。 复盘时通常归因到"执行力不行"或者"业务变化太快"。但真实原因…

2026/10/15 19:33:28 阅读更多 →

最新新闻

光储充换电站分时电价双层优化模型及Matlab实现

光储充换电站分时电价双层优化模型及Matlab实现

光储充换电站和分时电价互动这两件事,放在一起研究的论文和代码我见过不少,但真正能把“用户充电负荷”这个变量建模得当、又能在Matlab里跑通寻优的,确实不算多。这个标题对应的方向,简单说就是:站在运营商的视角&…

2026/10/16 4:47:24 阅读更多 →
Kafka与ZooKeeper的关系及bootstrap.servers配置详解:从旧架构到KRaft模式

Kafka与ZooKeeper的关系及bootstrap.servers配置详解:从旧架构到KRaft模式

1. 先讲清楚Kafka和ZooKeeper到底什么关系很多刚接触Kafka的人都会被一件事搞糊涂:Kafka不是号称"去ZooKeeper化"了吗?为什么配置里还有一个KAFKA_ZOOKEEPER_CONNECT?更离谱的是,生产者和消费者的配置里写的却是bootstr…

2026/10/16 4:47:24 阅读更多 →
公众号+题库小程序:从内容池到变现闭环的实操指南

公众号+题库小程序:从内容池到变现闭环的实操指南

“公众号是内容池,题库小程序是印钞机”,这句话我第一次听到时,是在和一个做考证培训的同行聊天。当时觉得夸张,但自己真把一个公众号和一个题库小程序跑起来之后,才发现这话糙理不糙。公众号负责沉淀内容和信任&#…

2026/10/16 4:47:24 阅读更多 →
把Claude API变成工程流程:本地自动化代码助手实践

把Claude API变成工程流程:本地自动化代码助手实践

如果你的工作流还停留在把代码复制到聊天对话框、再把结果复制回编辑器,那这个标题值得你仔细看完。我第一次在代码仓库里看到free-claude-code这个名字时,以为又是一个套壳的“AI 写代码”玩具。可把它拆开跑了一遍才发现,真正值钱的不是那几…

2026/10/16 4:47:24 阅读更多 →
Deepin安装oh-my-zsh完整指南:避坑与配置详解

Deepin安装oh-my-zsh完整指南:避坑与配置详解

说实话,在Deepin上安装oh-my-zsh这件事,我前前后后折腾过三次,这次是第四次,才算是把整个流程彻底捋顺了。Deepin本身是个很省心的Linux发行版,开箱即用,但对经常在终端里干活的人来说,默认的ba…

2026/10/16 4:47:24 阅读更多 →
酉空间核心解析:内积、埃尔米特矩阵与酉对角化

酉空间核心解析:内积、埃尔米特矩阵与酉对角化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 4:46:24 阅读更多 →

日新闻

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 0:00:05 阅读更多 →
信号与系统频域分析作业解题指南

信号与系统频域分析作业解题指南

1. 作业背景与核心要求解析信号与系统作为电子信息类专业的核心基础课,其作业设计往往直接关联课程的核心知识框架。2026年春季第六次作业的布置,通常对应着课程进展到频域分析的关键阶段。根据常规教学进度推测,本次作业可能涉及以下核心内容…

2026/10/16 0:00:05 阅读更多 →
Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

1. 从一个"四不像"的练手项目说起如果你已经看完了Django的官方教程前几章,跟着敲完了投票应用,大概会有一种"我好像会了,又好像什么都不会"的割裂感。教程里每个知识点都是拆开的——这一章讲模型,下一章讲视…

2026/10/16 0:00:05 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →