ntoskrnl\mm 源码学习与内存管理计划
📅 2026/7/19 23:38:52
👁️ 阅读次数
📝 编程学习
ntoskrnl\mm 源码学习与内存管理计划
概述
本计划针对 ReactOS 内核内存管理器(ntoskrnl\mm\)的源码学习。MM 子系统由旧版 MM(根目录 10 个文件)和ARM3(ARM3/子目录 30 个文件)两套实现组成,当前主力为 ARM3。整个目录包含约 49 个源文件 + 多个架构特定实现。
第一阶段:前置知识准备
1.1 硬件分页机制(预计 1-2 天)
在学习任何 MM 源码之前,必须先理解 CPU 的分页硬件机制。
| 主题 | 关键概念 | 参考材料 |
|---|---|---|
| x86 32位分页 | 页目录 (PD)、页表 (PT)、PTE 结构、4KB/4MB 页面 | Intel/AMD 手册 Vol 3 |
| PAE 分页 | 页目录指针表 (PDPT)、36位物理地址、64位 PTE | Intel/AMD 手册 Vol 3 |
| x86-64 分页 | PML4 → PDPT → PD → PT 四级页表 | AMD 手册 Vol 2 |
| ARM 分页 | 二级页表、段/大页/小页 | ARM 架构手册 |
| TLB 刷新 | INVEPT、INVVPID、CR3 切换 | Intel/AMD 手册 |
练习:手绘 x86 分页地址转换图(虚拟地址 → PDE → PTE → 物理页)。
1.2 Windows 内存管理概念(预计 2-3 天)
| 概念 | 说明 | MM 中对应 |
|---|---|---|
| 虚拟地址描述符 (VAD) | 描述进程地址空间中每个区域的 AVL 树节点 | vadnode.c,miarm.h |
| 页帧号数据库 (PFN DB) | 每个物理页一个条目,记录引用计数、状态、映射 | pfnlist.c,freelist.c |
| 工作集 (Working Set) | 进程当前在物理内存中的页面集合 | wslist.cpp,balance.c |
| 段对象 (Section) | 文件与虚拟内存之间的映射抽象 | section.c(ARM3),section.c(旧版) |
| 池 (Pool) | 内核堆分配器(分页/非分页) | expool.c,pool.c |
| 系统 PTE | 内核虚拟地址空间中的空闲页表项 | syspte.c |
| 超空间 (Hyperspace) | 临时映射物理页到内核空间的机制 | hypermap.c |
| MDL | 描述物理内存缓冲区的数据结构 | mdlsup.c |
推荐阅读:Windows Internals 7thPart 1 Chapter 9 “Memory Management”。
第二阶段:源码模块阅读(建议顺序)
2.1 核心头文件 — 建立全局认知
[miarm.h](file:///d:/reactos/ntoskrnl/mm/ARM3/miarm.h)
- 地位:整个 MM 子系统的唯一核心头文件,零外部依赖(无
#include) - 阅读重点:
- 保护常量:
MM_ZERO_ACCESS到MM_EXECUTE_WRITECOPY - PTE 标志位:
PTE_VALID、PTE_DIRTY、PTE_ACCESSED、PTE_PROTOTYPE、PTE_TRANSITION - PFN 状态:
ZeroedPageList、FreePageList、StandbyPageList、ModifiedPageList、BadPageList - 页表自映射区域:
PTE_BASE、PDE_BASE(x86 特有技巧) - 结构体:
MMPFN、MMPTE、MMVAD、MM_AVL_TABLE
- 保护常量:
- 目标:能闭眼画出 PFN 条目布局和 PTE 各字段含义
[miavl.h](file:///d:/reactos/ntoskrnl/mm/ARM3/miavl.h)
- 地位:VAD 树专用的 AVL 算法内联实现,仅被
vadnode.c包含 - 阅读重点:理解从 RTL 通用 AVL 到 MM 专用类型的宏映射
- 目标:理解平衡因子和父指针共用同一字段的优化
2.2 初始化子系统 — 理解启动流程
[ARM3/mminit.c](file:///d:/reactos/ntoskrnl/mm/ARM3/mminit.c)
- 函数:
MmArmInitSystem - 阅读重点:
- Phase0 初始化:扫描内存描述符 → 构建 PFN 数据库 → 映射 PFN DB → 构建分页池 → 初始化颜色表
- Phase1 初始化:已加载模块链表 → 大页面支持 → 特殊池 → 后台交换线程
- 非分页池大小计算算法
- 配合阅读:[mminit.c(旧版)](file:///d:/reactos/ntoskrnl/mm/mminit.c) 了解
MmInitSystem
[amd64/init.c](file:///d:/reactos/ntoskrnl/mm/amd64/init.c) / [i386/init.c](file:///d:/reactos/ntoskrnl/mm/ARM3/i386/init.c)
- 函数:
MiInitMachineDependent - 阅读重点:架构相关的模板 PTE/PDE 定义、页表层次映射
2.3 物理内存管理 — 页帧与分配器
[pfnlist.c](file:///d:/reactos/ntoskrnl/mm/ARM3/pfnlist.c)
- 核心函数:
MiRemoveAnyPage、MiRemoveZeroPage、MiInsertPageInList、MiInitializePfn - 阅读重点:
- 物理页的六种状态:Active / Transition / Standby / Modified / Free / Zeroed / Bad
- 按颜色分配算法 (Cache Color)
- 引用计数和共享计数的增减逻辑
- 流程图:绘制物理页生命周期状态机
[freelist.c](file:///d:/reactos/ntoskrnl/mm/freelist.c)
- 阅读重点:全局统计变量
MmAvailablePages、MmTotalCommittedPages、MmPfnDatabase
[zeropage.c](file:///d:/reactos/ntoskrnl/mm/ARM3/zeropage.c)
- 核心函数:
MiZeroPageThread - 阅读重点:后台零页线程的工作循环(从备用列表取页 → 置零 → 放入零页列表)
[contmem.c](file:///d:/reactos/ntoskrnl/mm/ARM3/contmem.c) / [largepag.c](file:///d:/reactos/ntoskrnl/mm/ARM3/largepag.c)
- 阅读重点:
MmAllocateContiguousMemorySpecifyCache的完整分配路径 - 理解:大页面的分配条件和对性能的影响
2.4 虚拟内存管理 — VAD 与系统调用
[vadnode.c](file:///d:/reactos/ntoskrnl/mm/ARM3/vadnode.c)
- 核心函数:
MiLocateVad、MiInsertVad、MiRemoveNode、MiFindEmptyAddressRangeInTree - 阅读重点:在 AVL 树中查找包含指定地址的 VAD 的算法
- 练习:手写
MiLocateVad的二叉树搜索过程
[virtual.c](file:///d:/reactos/ntoskrnl/mm/ARM3/virtual.c)
- 核心函数:
NtAllocateVirtualMemory、NtFreeVirtualMemory、NtProtectVirtualMemory、NtQueryVirtualMemory - 阅读重点(按顺序):
NtAllocateVirtualMemory完整路径:参数验证 → 锁定工作集锁 → 找空闲区间 → 插 VAD → 提交页面NtFreeVirtualMemory路径:释放 VAD → 删除 PTE → 释放物理页 → 更新 PFNMmGetPhysicalAddress:虚拟地址 → 物理地址转换MiDeletePte和MiDeleteVirtualAddresses:反向操作的底层逻辑
- 数据流:画出 NtAllocateVirtualMemory 的完整函数调用链
[syspte.c](file:///d:/reactos/ntoskrnl/mm/ARM3/syspte.c)
- 核心函数:
MiReserveSystemPtes、MiReleaseSystemPtes - 阅读重点:系统 PTE 的位图分配算法
2.5 缺页异常处理 — 核心性能路径
[pagfault.c](file:///d:/reactos/ntoskrnl/mm/ARM3/pagfault.c)
- 核心函数:
MmArmAccessFault→MiDispatchFault - 四种故障类型(按重要性排序):
MiResolveDemandZeroFault— 请求零页:分配 → 置零 → 映射MiResolvePageFileFault— 页文件换回:解析 PTE 交换条目 → 磁盘 I/O 读回MiResolveTransitionFault— 转换故障:备用/已修改列表中的页重新激活MiResolveProtoPteFault— 原型 PTE 故障:共享页的写时复制 (COW)
- 阅读重点:
- 错误码分析(PF 错误码各位含义)
MiCheckForUserStackOverflow— 用户栈自动扩展MiAccessCheck— 访问权限验证
- 配合阅读:[i386/page.c](file:///d:/reactos/ntoskrnl/mm/i386/page.c) 中页表操作底层函数
2.6 池分配器 — 内核堆
[expool.c](file:///d:/reactos/ntoskrnl/mm/ARM3/expool.c) + [pool.c](file:///d:/reactos/ntoskrnl/mm/ARM3/pool.c)
- 核心函数:
ExAllocatePoolWithTag、ExFreePoolWithTag - 阅读重点:
- 三种分配粒度:小(POOL_DESCRIPTOR 列表)、中(位图管理页)、大(整页)
- 池头部结构和标签跟踪机制
- 热标签缓存 (Hot Tags)
- 配额管理(
MmRaisePoolQuota/MmReturnPoolQuota) - 非分页池初始化流程
- 配合阅读:[special.c](file:///d:/reactos/ntoskrnl/mm/ARM3/special.c) 了解 Driver Verifier 特殊池的填充模式检测
2.7 段对象与文件映射
[section.c(ARM3)](file:///d:/reactos/ntoskrnl/mm/ARM3/section.c)
- 核心函数:
NtCreateSection、NtMapViewOfSection - 阅读重点:
- 数据文件映射(
MiCreateDataFileMap)vs 分页文件映射(MiCreatePagingFileMap) - 控制区 (Control Area) → 段 (Segment) → 原型 PTE (Prototype PTE) 的层次关系
- 系统空间映射(
MiMapViewInSystemSpace)
- 数据文件映射(
- 配合阅读:[mdlsup.c](file:///d:/reactos/ntoskrnl/mm/ARM3/mdlsup.c) 了解 MDL 创建和页面锁定
2.8 进程与会话
[procsup.c](file:///d:/reactos/ntoskrnl/mm/ARM3/procsup.c)
- 核心函数:
MmCreateProcessAddressSpace、MmCleanProcessAddressSpace、MmDeleteProcessAddressSpace - 阅读重点:进程地址空间创建的完整步骤
- 配合阅读:[i386/procsup.c](file:///d:/reactos/ntoskrnl/mm/ARM3/i386/procsup.c) / [amd64/procsup.c](file:///d:/reactos/ntoskrnl/mm/amd64/procsup.c) 理解架构差异
[session.c](file:///d:/reactos/ntoskrnl/mm/ARM3/session.c)
- 阅读重点:
MmSessionCreate/MmSessionDelete、Session ID 位图管理
2.9 工作集与内存平衡
[wslist.cpp](file:///d:/reactos/ntoskrnl/mm/ARM3/wslist.cpp)
- 核心函数:
MmWorkingSetManager、MiInsertInWorkingSetList、MiRemoveFromWorkingSetList - 注意:这是 MM 目录中唯一的 C++ 文件
[balance.c](file:///d:/reactos/ntoskrnl/mm/balance.c)
- 核心函数:
MiBalancerThread、MmTrimUserMemory、MmRebalanceMemoryConsumers - 阅读重点:页面回收的完整路径:
- 消费者注册和优先级
- Trim 回调调用
- 解除映射 → 写交换文件 → 放入备用列表
2.10 页面文件与反向映射
[pagefile.c](file:///d:/reactos/ntoskrnl/mm/pagefile.c)(旧版,未适配 ARM3)
- 核心函数:
MmWriteToSwapPage、MmReadFromSwapPage、MmAllocSwapPage、MmFreeSwapPage - 阅读重点:交换槽位分配和磁盘 I/O 发起的完整路径
[rmap.c](file:///d:/reactos/ntoskrnl/mm/rmap.c)(旧版)
- 阅读重点:反向映射如何支持页面换出
2.11 系统镜像加载
[sysldr.c](file:///d:/reactos/ntoskrnl/mm/ARM3/sysldr.c)
- 核心函数:
MmLoadSystemImage、MmUnloadSystemImage - 阅读重点:
- PE 文件的加载过程
- 导入表解析(
MiResolveImageReferences) - Thunk 修正(
MiSnapThunk) - 驱动程序可分页性控制(
MmPageEntireDriver/MmResetDriverPaging)
第三阶段:深入理解与验证练习
3.1 代码阅读练习
| 练习 | 目标文件 | 要求 |
|---|---|---|
| 画出页表自映射结构 | i386/procsup.c | 解释 PD[1023]→PD、PTE_BASE 寻址原理 |
| 追踪页分配路径 | pfnlist.c→virtual.c | 从NtAllocateVirtualMemory到物理页的完整链 |
| 画出 PFN 状态机 | pfnlist.c+freelist.c | 包含所有 6 种状态的转换条件和触发函数 |
| 分析写时复制 | pagfault.c→MiResolveProtoPteFault | 写出 COW 的触发条件和页面分配逻辑 |
| 池分配链路 | expool.c→pool.c→pfnlist.c | 从ExAllocatePoolWithTag到物理页的完整链 |
| 缺页异常完整路径 | pagfault.c | 画出 CPU 异常 → 内核分发 → 各子处理器的分派树 |
| 工作集修剪过程 | balance.c+wslist.cpp | 从内存压力触发到页面回收的完整路径 |
3.2 编译调试练习
| 练习 | 操作 | 验证方法 |
|---|---|---|
| 添加 DPRINT1 日志 | 在关键路径入口添加日志 | 查看串口输出确认调用路径 |
| 触发缺页异常 | 运行测试程序访问未提交内存 | 串口观察MiDispatchFault日志 |
| 监控池分配 | 在ExAllocatePoolWithTag添加跟踪 | 查看分配模式和标签 |
| 触发页面回收 | 运行大内存消耗程序 | 观察MiBalancerThread活动 |
3.3 阅读路线图
┌─────────────────────────────────────────────────────────────────┐ │ 推荐阅读顺序 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 1. miarm.h + miavl.h ← 头文件基础(1天) │ │ ↓ │ │ 2. mminit.c ← 初始化流程(1天) │ │ ↓ │ │ 3. pfnlist.c + freelist.c ← 物理页管理(1-2天) │ │ ↓ │ │ 4. vadnode.c ← VAD 树算法(1天) │ │ ↓ │ │ 5. virtual.c ← 虚拟内存核心(2天) │ │ ↓ │ │ 6. pagfault.c + i386/page.c ← 缺页处理(2天) │ │ ↓ │ │ 7. expool.c + pool.c ← 池分配器(1-2天) │ │ ↓ │ │ 8. section.c(ARM3) ← 段对象(2天) │ │ ↓ │ │ 9. procsup.c + session.c ← 进程/会话(1天) │ │ ↓ │ │ 10. wslist.cpp + balance.c ← 工作集(1-2天) │ │ ↓ │ │ 11. pagefile.c + rmap.c ← 页文件/反向映射(1天) │ │ ↓ │ │ 12. sysldr.c ← 系统加载器(2天) │ │ ↓ │ │ 13. mdlsup.c + iosup.c ← MDL/I/O 支持(1天) │ │ ↓ │ │ 14. 架构文件(i386/amd64等) ← 架构差异(1天) │ │ │ │ 总计:约 17-22 天 │ └─────────────────────────────────────────────────────────────────┘第四阶段:推荐学习资料
书籍
- Windows Internals, 7th Edition— Part 1, Chapter 9 “Memory Management”
- 这是理解 Windows 内存管理架构的最佳参考
- Understanding the Linux Kernel, 3rd Edition— Chapter 8 “Memory Management”
- 与 Linux 对比学习有助于加深理解
- Intel 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A
- 分页机制硬件细节
在线资源
- ReactOS 官方 Wiki — Memory Manager
- Windows内核原理与实现(潘爱民) — 中文经典
- WRK (Windows Research Kernel) 源码 — 对照参考
ReactOS 内部文档
doc/ARM3内存管理器分析.md— 已有的 ARM3 分析文档doc/MM实现分析.md— 旧版 MM 的实现分析doc/ntoskrnl内存管理器MM分析.md— 综合分析(已有)
第五阶段:学习验证清单
每节完成后自测
- 能画出 x86 两级分页地址转换图
- 能解释 PAE 和非 PAE 的区别
- 能写出 x86-64 四级页表的各级名称
- 能画出 PFN 条目结构和页面状态机
- 能描述 VAD AVL 树的搜索算法
- 能写出
NtAllocateVirtualMemory的完整调用链 - 能解释四种缺页故障类型及处理方式
- 能描述池分配器的小/中/大三级分配策略
- 能画出 Section 对象 → 控制区 → 段 → 原型 PTE 的层次
- 能解释页表自映射技巧的原理
- 能描述页面回收的完整路径(平衡器 → 修剪 → 换出)
- 能解释逆向映射的作用和工作原理
- 能描述 PE 镜像加载过程的关键步骤
代码修改练习
- 在某个关键路径上添加 DPRINT1 并验证串口输出
- 找到一处
UNIMPLEMENTED或存根函数并实现它 - 给某个复杂函数添加中文注释解释
编程学习
技术分享
实战经验