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

mm学习笔记_06:缺页异常处理

mm学习笔记_06:缺页异常处理

基于 ReactOS ARM3 MM 源码(ntoskrnl/mm/ARM3/pagfault.c)


1. pagfault.c 概述

文件位置d:\reactos\ntoskrnl\mm\ARM3\pagfault.c

职责:处理所有类型的缺页异常(Page Fault),包括:

  • 请求零页故障(Demand Zero Fault)
  • 分页文件故障(Page File Fault)
  • 转换故障(Transition Fault)
  • 原型 PTE 故障(Proto PTE Fault / Copy on Write)
  • 用户栈自动扩展(Stack Overflow)
  • 访问权限检查(Access Check)

入口点MmArmAccessFault(由 CPU 缺页中断KiTrap0E调用)


2. MmArmAccessFault——缺页异常入口完整路径

文件:pagfault.c:1698

CPU 检测到缺页(CR2 = 故障地址,错误码在栈上) │ ▼ KiTrap0E (x86 汇编中断处理程序) │ ▼ MmArmAccessFault(FaultCode, Address, Mode, TrapInformation)

2.1 第一阶段:高 IRQL 故障处理

MmArmAccessFault(ULONG FaultCode,PVOID Address,...){OldIrql=KeGetCurrentIrql();if(OldIrql>APC_LEVEL){// 高 IRQL(> APC_LEVEL)下的缺页处理// 只允许系统页表(paged pool 页表)的缺页if(MI_IS_SYSTEM_PAGE_TABLE_ADDRESS(Address))MiSynchronizeSystemPde(...);// 检查 PXE/PPE/PDE/PTE 是否有效if(任何一级页表无效){// 无效 → 返回错误,触发 KERNEL_DATA_INPAGE_ERROR 或 ATTEMPTED_WRITE_TO_READONLY_MEMORYreturnSTATUS_IN_PAGE_ERROR|0x10000000;}// 检查是否写只读页if(MI_IS_WRITE_ACCESS(FaultCode)&&!PTE_READWRITE)KeBugCheck(ATTEMPTED_WRITE_TO_READONLY_MEMORY);returnSTATUS_SUCCESS;// 无实际错误的缺页}

关键点:高 IRQL 下不允许复杂的缺页处理,只能处理页表本身的缺页。

2.2 第二阶段:内核地址空间故障

if(Address>=MmSystemRangeStart){// 内核地址空间缺页// 不允许用户态触发内核缺页if(Mode==UserMode)returnSTATUS_ACCESS_VIOLATION;// 检查 PXE/PPE/PDE 是否有效if(任何一级无效)KeBugCheck(PAGE_FAULT_IN_NONPAGED_AREA);// 如果 PTE 有效,检查写只读页或 NX 违规if(TempPte.u.Hard.Valid){if(写只读)KeBugCheck(ATTEMPTED_WRITE_TO_READONLY_MEMORY);if(执行不可执行)KeBugCheck(ATTEMPTED_EXECUTE_OF_NOEXECUTE_MEMORY);returnSTATUS_SUCCESS;}// 获取工作集(系统缓存或会话空间)WorkingSet=&MmSystemCacheWs;// 或 MmSessionSpace->...// 锁定工作集 → 重新检查 PTE → 分派故障MiLockWorkingSet(CurrentThread,WorkingSet);if(TempPte.u.Hard.Valid){/* 已解决 */}else{// 处理原型 PTE 或分派故障Status=MiDispatchFault(FaultCode,Address,PointerPte,ProtoPte,FALSE,CurrentProcess,...);}MiUnlockWorkingSet(CurrentThread,WorkingSet);if(Status==STATUS_NO_MEMORY){MmRebalanceMemoryConsumersAndWait();gotoRetryKernel;}returnStatus;}

2.3 第三阶段:用户地址空间故障(UserFault)

UserFault: CurrentProcess = PsGetCurrentProcess(); MiLockProcessWorkingSet(CurrentProcess, CurrentThread); // 1. 检查各级页表(PXE → PPE → PDE → PTE)有效性 // 对无效的页表项,通过 MiResolveDemandZeroFault 按需分配页表页 // 2. 读取 PTE TempPte = *PointerPte; // 3. 如果 PTE 有效(Valid=1): if (TempPte.u.Hard.Valid) { if (写操作) { if (COW) { // → 分配新页 → 复制内容 → 写时复制(COW) } if (只读) { return STATUS_ACCESS_VIOLATION; } } return STATUS_SUCCESS; // 其他线程已解决此故障 } // 4. 如果 PTE 是 Demand Zero 格式 if (是 DemandZero 格式) { return MiResolveDemandZeroFault(Address, PointerPte, ...); } // 5. 如果 PTE == 0(从未映射) if (TempPte.u.Long == 0) { // 查找 VAD(MiCheckVirtualAddress) // 如果是 Guard Page → 调用 MiCheckForUserStackOverflow(栈扩展) // 否则 → 分配零页 } // 6. 对原型 PTE 等复杂情况,调用 MiDispatchFault Status = MiDispatchFault(FaultCode, Address, PointerPte, ProtoPte, ...); ExitUser: MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread); if (Status == STATUS_NO_MEMORY) { MmRebalanceMemoryConsumersAndWait(); goto UserFault; // 重试 } return Status;

完整流程图

MmArmAccessFault(FaultCode, Address, Mode, TrapInfo) │ ├─ [高 IRQL (> APC_LEVEL)] │ ├─ 检查页表有效性 │ ├─ 同步系统 PDE │ └─ 返回 STATUS_SUCCESS 或 BugCheck │ ├─ [内核地址 (Address >= MmSystemRangeStart)] │ ├─ 检查用户态违规 │ ├─ 检查 PDE/PPE/PXE 有效性 │ ├─ 锁定系统工作集 │ ├─ 重新检查 PTE │ ├─ MiDispatchFault → 故障分派 │ └─ 解锁工作集 │ └─ [用户地址 (UserFault 标签)] ├─ MiLockProcessWorkingSet ├─ 检查/分配页表页(PDE/PPE/PXE) ├─ [PTE 有效] → COW 处理 / 访问违规 ├─ [DemandZero PTE] → MiResolveDemandZeroFault ├─ [PTE == 0] → 查找 VAD → GuardPage? 栈扩展 / 分配零页 ├─ [其他] → MiDispatchFault └─ MiUnlockProcessWorkingSet

3. MiDispatchFault——分派逻辑

文件:pagfault.c:1338

功能:根据 PTE 内容判断故障类型,分派到对应的解析函数。

PTE 状态判断顺序

NTSTATUS NTAPIMiDispatchFault(...){TempPte=*PointerPte;// 顺序1:有原型 PTE 吗?if(PointerProtoPte){// 锁定 PFN 数据库,检查原型 PTE 的状态// → MiResolveProtoPteFault}// 顺序2:Transition PTE?if(TempPte.u.Soft.Transition){// → MiResolveTransitionFault}// 顺序3:页面在分页文件中?if(TempPte.u.Soft.PageFileHigh!=0){// → MiResolvePageFileFault}// 顺序4:只能是 Demand Zero PTEASSERT(TempPte.u.Hard.Valid==0);ASSERT(TempPte.u.Soft.Prototype==0);ASSERT(TempPte.u.Soft.Transition==0);ASSERT(TempPte.u.Soft.PageFileHigh==0);ASSERT(TempPte.u.Long!=0);// → MiResolveDemandZeroFault}

判断流程图

读取 PTE │ ├─ 有原型 PTE? → MiResolveProtoPteFault │ ├─ Transition 位=1? → MiResolveTransitionFault │ ├─ PageFileHigh ≠ 0? → MiResolvePageFileFault │ └─ 默认(软件 PTE, 非零)→ MiResolveDemandZeroFault

4. MiResolveDemandZeroFault——请求零页故障

文件:pagfault.c:599

触发条件:访问的页面已被预留但从未提交/访问,PTE 为软件格式(Demand Zero)。

处理流程

MiResolveDemandZeroFault(Address, PointerPte, Protection, Process, OldIrql) │ ├─ 确定是否需要零页(NeedZero) │ ├─ 用户模式进程 → NeedZero = TRUE(需要已清零页) │ └─ 内核/会话 → 根据 OldIrql 判断 │ ├─ 锁定 PFN 数据库(如尚未锁定) │ ├─ 取页策略: │ ├─ 有颜色 → MiRemoveZeroPageSafe(Color) 优先取零页 │ │ └─ 失败 → MiRemoveAnyPage(Color) + NeedZero = TRUE │ └─ 无颜色 → MI_GET_NEXT_COLOR() + MiRemoveAnyPage / MiRemoveZeroPage │ ├─ 初始化 PFN:MiInitializePfn(PageFrameNumber, PointerPte, TRUE) │ ├─ 递增 Demand Zero 计数:MmDemandZeroCount++ │ ├─ 释放 PFN 锁 │ ├─ 如需清零:MiZeroPfn(PageFrameNumber)(通过超空间映射清零) │ ├─ 构建硬件 PTE: │ ├─ 用户地址 → MI_MAKE_HARDWARE_PTE_USER(含用户标志位) │ └─ 内核地址 → MI_MAKE_HARDWARE_PTE │ ├─ 写脏位(如页面可写) │ └─ MI_WRITE_VALID_PTE(PointerPte, TempPte)

数据结构变化

分配前: 分配后: ┌─────────┐ ┌─────────┐ │ PointerPte → 软件 PTE │ PointerPte → 硬件 PTE(Valid=1, PFN=X) │ (Valid=0, Protection=XX)│ └─────────┘ └─────────┘ PFN X: PageLocation: Free/Zeroed → ActiveAndValid ReferenceCount: 0 → 1 ShareCount: 0 → 1 PteAddress: → PointerPte OriginalPte: → 保存原 PTE

5. MiResolvePageFileFault——分页文件故障

文件:pagfault.c:900

触发条件:访问的页面已被换出到分页文件(PTE 中记录了分页文件号和偏移量)。

PTE 格式(分页文件状态):

// PageFileHigh = 分页文件中的偏移量(高位)// PageFileLow = 分页文件索引号(低位)// Protection = 保护属性TempPte.u.Soft.PageFileHigh → 偏移量 TempPte.u.Soft.PageFileLow → 文件索引 TempPte.u.Soft.Protection → 保护属性

处理流程

MiResolvePageFileFault(StoreInstruction, FaultingAddress, PointerPte, Process, OldIrql) │ ├─ 解析 PTE 中的交换条目:PageFileIndex, PageFileOffset, Protection │ ├─ 取一空闲物理页:MiRemoveAnyPage(Color) │ ├─ 初始化 PFN:MiInitializePfn(Page, PointerPte, StoreInstruction) │ ├─ 设置 ReadInProgress = 1(标记 I/O 进行中) │ ├─ 写 Transition PTE(标记页面正在 I/O,保护原内容) │ MI_MAKE_TRANSITION_PTE(&TempPte, Page, Protection) │ MI_WRITE_INVALID_PTE(PointerPte, TempPte) │ ├─ 释放 PFN 锁 → 发起读盘 I/O │ Status = MiReadPageFile(Page, PageFileIndex, PageFileOffset) │ ├─ 重新锁定 PFN 锁 │ ├─ 检查 I/O 结果 │ ├─ 成功:构建硬件 PTE,写为有效 │ ├─ 失败:标记 InPageError │ └─ 唤醒等待者(若有事件) │ └─ 返回 Status

关键点

  • I/O 期间 PTE 被写为 Transition 格式,其他线程访问同一地址时会等待
  • 使用Pfn1->u1.Event事件对象实现等待队列链

6. MiResolveTransitionFault——转换故障

文件:pagfault.c:992

触发条件:PTE 的 Transition 位 = 1(页面物理上存在但已被暂时解除映射,位于备用/已修改列表中)。

PTE 格式(Transition 状态):

// Valid=0, Transition=1, Prototype=0// PageFrameNumber = 物理页帧号// Protection = 保护属性TempPte.u.Trans.PageFrameNumber → PFN TempPte.u.Trans.Protection → 保护属性 TempPte.u.Trans.Transition →1

处理流程

MiResolveTransitionFault(StoreInstruction, FaultingAddress, PointerPte, Process, OldIrql) │ ├─ 获取 PFN 和 PFN 条目 │ ├─ 递增转换故障计数:MmTransitionCount++ │ ├─ 检查页面是否正在进行 I/O(ReadInProgress / WriteInProgress) │ ├─ 是 → 设置 InPageBlock 事件,让调用者等待 │ └─ 否 → 继续处理 │ ├─ 检查页面位置: │ ├─ ActiveAndValid → 已在使用中(仅做检查) │ └─ 其他列表 → MiUnlinkPageFromList + 引用计数递增 │ ├─ 设置 ShareCount++、PageLocation = ActiveAndValid │ ├─ 构建最终 PTE: │ ├─ 从原 PTE 取 PFN 和属性位 │ ├─ 从 MmProtectToPteMask 取硬件保护位 │ └─ 根据 Modified 位设置 Dirty/Clean │ └─ MI_WRITE_VALID_PTE(PointerPte, TempPte)

关键点

  • Transition 故障是"软故障"(soft fault),不需要磁盘 I/O
  • 页面已在备用/已修改列表中,只是被临时解除映射
  • 这是最快的故障类型之一(页内容仍在物理内存中)

7. MiResolveProtoPteFault——原型 PTE 故障

文件:pagfault.c:1124

触发条件:进程的 PTE 指向一个原型 PTE(Prototype PTE),即该页面被多个进程共享(Section 映射)。

处理流程

MiResolveProtoPteFault(StoreInstruction, Address, PointerPte, PointerProtoPte, OutPfn, ..., Process, OldIrql) │ ├─ 读取原型 PTE(共享的底层 PTE) │ ├─ [原型 PTE 有效(Valid=1)]: │ ├─ 递增 PFN 的 ShareCount │ ├─ 递增转换计数 │ └─ MiCompleteProtoPteFault(构建进程私有 PTE,指向共享物理页) │ ├─ [原型 PTE 为零(未提交的 Section)]: │ └─ STATUS_ACCESS_VIOLATION │ ├─ [原型 PTE 为 Transition]: │ └─ MiResolveTransitionFault(先在原型层面恢复页) │ ├─ [原型 PTE 为 Demand Zero]: │ └─ MiResolveDemandZeroFault(先在原型层面分配页) │ └─ [写时复制 COW]: ├─ 先按读操作解析原型 PTE ├─ 分配新物理页 ├─ MiCopyPfn(复制原页内容到新页) ├─ MiDeletePte(删除指向共享页的 PTE) ├─ MiInitializePfn(初始化新页) ├─ 构建 PTE(保护属性去掉 WRITECOPY,加上 READWRITE) └─ MI_WRITE_VALID_PTE

写时复制(COW)详细流程

进程 A 写入共享页 → 缺页 │ ├─ 原型 PTE 有效(指向物理页 X,标记为 COW) │ ├─ 先按读操作完成故障(建立只读映射) │ ├─ 分配新物理页 Y │ ├─ MiCopyPfn(Y, X) → 复制内容 │ ├─ 删除原 PTE → 递减共享计数 │ ├─ MiInitializePfn(Y, PointerPte, TRUE) │ ├─ 构建新 PTE:去掉 WRITECOPY → READWRITE │ └─ 写有效 PTE,指向新物理页 Y

COW 后的状态

COW 前: COW 后: 进程 A PTE → [Proto] 进程 A PTE → 物理页 Y(私有, RW) 进程 B PTE → [Proto] 进程 B PTE → 物理页 X(共享, RO) │ (实际未改变) └→ 物理页 X(共享, RO)

8. MiCheckForUserStackOverflow——用户栈自动扩展

文件:pagfault.c:34

触发条件:访问用户栈的 Guard Page(保护页),导致 STATUS_GUARD_PAGE_VIOLATION。

处理流程

MiCheckForUserStackOverflow(Address, TrapInformation) │ ├─ 检查线程状态(已附加进程? ACP已禁? → 不允许栈扩展) │ ├─ 读取 TEB 中的栈信息: │ StackBase = Teb->NtTib.StackBase(栈基址) │ DeallocationStack = Teb->DeallocationStack(栈底) │ GuaranteedSize = Teb->GuaranteedStackBytes(保证大小) │ ├─ 计算下一个栈地址: │ NextStackAddress = PAGE_ALIGN(Address) - PAGE_SIZE │ ├─ 如果快到栈底了(距 DeallocationStack 不足一页): │ ├─ 在 DeallocationStack 之上提交一页 │ ├─ 更新 StackLimit │ └─ 返回 STATUS_STACK_OVERFLOW │ ├─ 否则(正常扩展): │ ├─ 更新 StackLimit │ ├─ 在下一页提交带 PAGE_GUARD 的内存 │ │ ZwAllocateVirtualMemory(..., MEM_COMMIT, PAGE_READWRITE | PAGE_GUARD) │ └─ 返回 STATUS_PAGE_FAULT_GUARD_PAGE

栈扩展示意图

栈方向(高地址 → 低地址) StackBase (高地址) ←── 栈顶部 ... ┌───────────────────────┐ │ 已提交的栈空间 │ ├───────────────────────┤ │ Guard Page(当前) │ ← 本次触发了缺页 ├───────────────────────┤ ← StackLimit 更新位置 │ 新提交的 Guard Page │ ← 新保护页 ├───────────────────────┤ │ 未提交的栈空间 │ └───────────────────────┘ DeallocationStack (低地址) ←── 栈底部

9. MiAccessCheck / MiIsAccessAllowed——权限检查

9.1 MiIsAccessAllowed

文件:pagfault.c:142

FORCEINLINE BOOLEANMiIsAccessAllowed(ULONG ProtectionMask,BOOLEAN Write,BOOLEAN Execute){// 32 字节查表法staticconstUCHAR AccessAllowedMask[2][2]={// 读访问{_BYTE_MASK(0,1,1,1,1,1,1,1),// 只读_BYTE_MASK(0,0,1,1,0,0,1,1)},// 可执行读// 写访问{_BYTE_MASK(0,0,0,0,1,1,1,1),// 写_BYTE_MASK(0,0,0,0,0,0,1,1)},// 可执行写};ProtectionMask&=MM_PROTECT_ACCESS;return(AccessAllowedMask[Write][Execute]>>ProtectionMask)&1;}

权限表(基于保护码低 3 位查表):

保护码只读可执行读可执行写
0 (NOACCESS)
1 (READONLY)
2 (EXECUTE)
3 (EXECUTE_READ)
4 (READWRITE)
5 (WRITECOPY)✓(COW)✓(COW)
6 (EXECUTE_READWRITE)
7 (EXECUTE_WRITECOPY)✓(COW)✓(COW)

9.2 MiAccessCheck

文件:pagfault.c:172

NTSTATUS NTAPIMiAccessCheck(PMMPTE PointerPte,BOOLEAN StoreInstruction,KPROCESSOR_MODE PreviousMode,ULONG_PTR ProtectionMask,PVOID TrapFrame,BOOLEAN LockHeld){// 1. 检查用户态访问内核地址 → STATUS_ACCESS_VIOLATIONif((PreviousMode==UserMode)&&(PointerPte>MiHighestUserPte))returnSTATUS_ACCESS_VIOLATION;// 2. 如果 PTE 有效:if(TempPte.u.Hard.Valid){// 写操作时,检查可写性或 COWif(StoreInstruction){if(MI_IS_PAGE_WRITEABLE(&TempPte)||MI_IS_PAGE_COPY_ON_WRITE(&TempPte))returnSTATUS_SUCCESS;returnSTATUS_ACCESS_VIOLATION;}returnSTATUS_SUCCESS;// 读操作总是允许}// 3. 如果 PTE 无效,查权限表if(!MiIsAccessAllowed(ProtectionMask,StoreInstruction,FALSE))returnSTATUS_ACCESS_VIOLATION;// 4. 检查 Guard Pageif((ProtectionMask&MM_PROTECT_SPECIAL)==MM_GUARDPAGE){// 移除 Guard Page 位TempPte.u.Soft.Protection=ProtectionMask&~MM_GUARDPAGE;MI_WRITE_INVALID_PTE(PointerPte,TempPte);returnSTATUS_GUARD_PAGE_VIOLATION;}returnSTATUS_SUCCESS;}

附录:pagfault.c 函数一览

函数行号功能
MiCheckForUserStackOverflow34用户栈自动扩展处理
MiIsAccessAllowed142查表法权限判断
MiAccessCheck172访问权限验证(含 Guard Page 检测)
MiCheckVirtualAddress240检查虚拟地址是否有效(查找 VAD)
MiCheckPdeForPagedPool421确保分页池页表有效
MiCheckPdeForSessionSpace341确保会话空间页表有效
MiZeroPfn487通过系统 PTE 清零一个物理页
MiCopyPfn533在两个物理页之间复制内容
MiResolveDemandZeroFault599请求零页故障处理
MiCompleteProtoPteFault775完成原型 PTE 故障构建
MiResolvePageFileFault900分页文件故障处理(磁盘 I/O)
MiResolveTransitionFault992转换故障处理(备用/修改列表取回)
MiResolveProtoPteFault1124原型 PTE 故障处理(含 COW)
MiDispatchFault1338故障分派主逻辑
MmArmAccessFault1698缺页异常入口函数
MmGetExecuteOptions2651获取 NX 执行选项
MmSetExecuteOptions2693设置 NX 执行选项
http://www.jsqmd.com/news/1243936/

相关文章:

  • 2026年7月亲身到店探访沈阳亨得利**名表服务中心|全新电话和详细网点地址 - 亨得利官方博客
  • 上海如韵 CN3300 4.5-32V/40W PFM升压型多节电池充电控制器 SOT23-6 技术解析
  • Quarkus 优缺点深度剖析:来自 Spring Boot 开发者的视角
  • 3个步骤实现OpenClaw AI记忆助理:memory-lancedb-pro生产级部署指南
  • 一线观察:长期体验后,热门厨房空调厂家名声背后的真实情况
  • 两万块的闪测仪是消费陷阱?从二次元成本看一键闪测仪的真实价值
  • 重磅公告|亨得利2026年7月**服務熱線升級|香港統一**售後通道、地址核驗 - 亨得利官方
  • 2026年7月最新宇舶温州大悦春风里维修保养服务电话 - 亨得利钟表维修中心
  • 2026北京企业财税行业GEO优化公司新**丨生成式引擎优化服务商头部**及本地选型深度解析 - 企业新闻快传
  • 「嵌入式->ROS2」为什么是MicroROS?关于选择的考虑。
  • Linus Torvalds谈开源技术:Linux、Git、Rust与AI,开源世界如何应对新技术浪潮?
  • 终极指南:如何快速上手OpenHGNN异构图神经网络工具包
  • 三步构建B站会员购自动化购票系统:biliTickerBuy技术解析与实战指南
  • 2026年7月劳力士杭州官方服务网点地址及客服热线权威发布 - 劳力士官方服务中心
  • 亲身到店探访石家庄亨得利**名表服务中心|维修地址及24小时电话(2026年7月更新) - 亨得利官方
  • 出海AIToken中国平台崛起,客易云从“产品输出”迈向“Token化智能输出”
  • Browser-Use:企业级AI浏览器自动化框架的技术架构与应用实践
  • 权威发布:天梭唐山官方2026年7月最新网点地址与全国统一热线电话,售后客服全天候服务 - 天梭服务中心
  • 拆解爆款视频文案怎么做?2026大马工具箱文案提取教程 - 科技大爆炸
  • 全明星阵容聚焦灵巧手!T-Rex如何解决触觉添加难题,推动具身智能发展?
  • PY32 定时器中断回调不执行的解决办法
  • 北京产业园哪家能挂靠注册地址:【博亚信诚】地址稳定 - MXyuyu
  • 2026年7月 口碑好的 济南保安公司加盟平台、保安公司挂靠平台口碑**全面梳理 - 起跑123
  • 熵权法结合层次分析法组合权重➕模糊综合评价根据体育教师胜任力作为案例
  • 自动Cookie刷新功能:Roblox Account Manager让账号永不过期
  • 别再手动开机启动!Systemd 一键搞定所有后台服务
  • extended_text核心功能详解:从特殊文本到自定义背景,一篇搞定所有高级用法
  • 武汉女生适合读什么中职专业 护理 / 高铁 / 美术设计稳定好就业 - 湖北升学规划
  • 深圳长途搬家公司:长途搬家+家电打包避坑指南,2026年跨省搬迁注意事项 - szxybj
  • 2026年保定赵掌柜回收黄金首饰,金条金币铂金回收,竞秀区向阳南大街489号赵掌柜二奢按实时金价结算到手不扣费(185-3117-2838) - 赵掌柜二奢