mm学习笔记_05:虚拟内存核心与系统调用 mm学习笔记_05虚拟内存核心与系统调用基于 ReactOS ARM3 MM 源码ntoskrnl/mm/ARM3/virtual.c1. virtual.c 概述文件位置d:\reactos\ntoskrnl\mm\ARM3\virtual.c职责虚拟内存管理的核心实现以下关键系统调用NtAllocateVirtualMemory— 分配虚拟内存NtFreeVirtualMemory— 释放虚拟内存NtProtectVirtualMemory— 修改内存保护属性MmGetPhysicalAddress— 虚拟地址转物理地址MmCopyVirtualMemory— 跨进程内存拷贝2. NtAllocateVirtualMemory——完整路径文件virtual.c:44572.1 函数签名NTSTATUS NTAPINtAllocateVirtualMemory(IN HANDLE ProcessHandle,IN OUT PVOID*BaseAddress,IN ULONG_PTR ZeroBits,IN OUT PSIZE_T RegionSize,IN ULONG AllocationType,IN ULONG Protect);2.2 步骤一参数验证// 1. ZeroBits 检查不能超过最大值if(ZeroBitsMI_MAX_ZERO_BITS)returnSTATUS_INVALID_PARAMETER_3;// 2. AllocationType 合法性检查必须包含 MEM_COMMIT / MEM_RESERVE / MEM_RESETif(AllocationType~(MEM_COMMIT|MEM_RESERVE|MEM_RESET|...))returnSTATUS_INVALID_PARAMETER_5;if(!(AllocationType(MEM_COMMIT|MEM_RESERVE|MEM_RESET)))returnSTATUS_INVALID_PARAMETER_5;// 3. MEM_RESET 独占检查if((AllocationTypeMEM_RESET)(AllocationType!MEM_RESET))returnSTATUS_INVALID_PARAMETER_5;// 4. MEM_LARGE_PAGES 必须带 MEM_COMMITif((AllocationTypeMEM_LARGE_PAGES)!(AllocationTypeMEM_COMMIT))returnSTATUS_INVALID_PARAMETER_5;// 5. MEM_WRITE_WATCH 必须带 MEM_RESERVEif((AllocationTypeMEM_WRITE_WATCH)!(AllocationTypeMEM_RESERVE))returnSTATUS_INVALID_PARAMETER_5;// 6. MEM_PHYSICAL 必须带 MEM_RESERVE 且 Protect PAGE_READWRITEif((AllocationTypeMEM_PHYSICAL)...)...// 7. 保护属性验证ProtectionMaskMiMakeProtectionMask(Protect);if(ProtectionMaskMM_INVALID_PROTECTION)returnSTATUS_INVALID_PAGE_PROTECTION;// 8. 用户态参数捕获SEH 保护_SEH2_TRY{if(PreviousMode!KernelMode){ProbeForWritePointer(UBaseAddress);ProbeForWriteSize_t(URegionSize);}PBaseAddress*UBaseAddress;PRegionSize*URegionSize;}_SEH2_EXCEPT(...);// 9. 地址范围检查if(PBaseAddressMM_HIGHEST_VAD_ADDRESS)returnSTATUS_INVALID_PARAMETER_2;if(!PRegionSize)returnSTATUS_INVALID_PARAMETER_4;2.3 步骤二进程获取与附加// 如果是当前进程直接使用if(ProcessHandleNtCurrentProcess())ProcessCurrentProcess;else{// 否则以 PROCESS_VM_OPERATION 权限引用目标进程StatusObReferenceObjectByHandle(ProcessHandle,PROCESS_VM_OPERATION,PsProcessType,...);// 如果目标进程不是当前进程附加到目标进程地址空间if(CurrentProcess!Process){KeStackAttachProcess(Process-Pcb,ApcState);AttachedTRUE;}}2.4 步骤三MEM_RESERVE 主路径当未指定基址或指定 MEM_RESERVE 时1. 配额计费 PsChargeProcessNonPagedPoolQuota(sizeof(MMVAD_LONG)) 2. 分配 VADExAllocatePoolWithTag(NonPagedPool, sizeof(MMVAD_LONG), SdaV) 3. 初始化 VAD - Vad-u.VadFlags.PrivateMemory 1 - Vad-u.VadFlags.Protection ProtectionMask - if (AllocationType MEM_COMMIT) Vad-u.VadFlags.MemCommit 1 4. 插入 VAD自动查找空闲地址 Status MiInsertVadEx(Vad, StartingAddress, PRegionSize, HighestAddress, MM_VIRTMEM_GRANULARITY, AllocationType) 5. 写回用户参数SEH 保护 *URegionSize PRegionSize; *UBaseAddress (PVOID)StartingAddress;2.5 步骤四MEM_COMMIT 主路径当指定已有地址即对已预留区进行提交1. 锁定地址空间MmLockAddressSpace(AddressSpace) 2. 检查进程是否将终止 3. 查找冲突/匹配 VAD Result MiCheckForConflictingNode(StartVpn, EndVpn, Process-VadRoot, ...) 4. 验证 VAD 类型不能是 VadAwe/VadDevicePhysicalMemory/VadLargePages 5. 验证地址范围在 VAD 内 6. 如果是非私有内存Section 映射 - 获取原型 PTE - 锁定 MmSectionCommitMutex - 遍历 PTE 写入模板 PTE - 更新 NumberOfCommittedPages 7. 如果是私有内存 - 更新 CommitCharge 和 Process-CommitCharge - MiLockProcessWorkingSetUnsafe - 遍历每个 PTE - MiMakePdeExistAndMakeValid确保页表存在 - 写入 DemandZero 格式的 PTE保护属性 软件 PTE 标记 - MiUnlockProcessWorkingSetUnsafe完整流程图NtAllocateVirtualMemory │ ├─ 参数验证 │ ├─ ZeroBits 范围检查 │ ├─ AllocationType 组合检查 │ ├─ Protect 合法性检查 │ ├─ 用户态参数 Probe │ └─ 地址范围检查 │ ├─ 进程获取与附加KeStackAttachProcess │ ├─ [MEM_RESERVE 分支] │ ├─ 配额计费 │ ├─ 分配 VADExAllocatePoolWithTag │ ├─ VAD 初始化 │ └─ MiInsertVadEx自动搜索 Avl 插入 │ ├─ [MEM_COMMIT 分支] │ ├─ MmLockAddressSpace │ ├─ 查找已有 VADMiCheckForConflictingNode │ ├─ 验证 VAD 类型 │ ├─ [Section 内存] → 写原型 PTE → 更新段提交计数 │ ├─ [私有内存] → 更新 CommitCharge │ │ └─ 锁定工作集 │ │ └─ 遍历 PTE确保 PDE 有效写 DemandZero PTE │ └─ 解锁地址空间 │ └─ 返回 BaseAddress STATUS_SUCCESS3. NtFreeVirtualMemory——完整路径3.1 MEM_RELEASE 分支完整 VAD 释放四种释放情况情况1释放整个 VADBaseAddress VAD.StartingVpn, Size VAD 全范围 ─ 直接删除 VAD释放所有物理页 情况2释放 VAD 头部部分BaseAddress VAD.StartingVpn, Size VAD 全范围 ─ 调整 VAD.StartingVpn 原起始地址 Size ─ 释放对应 PTE 情况3释放 VAD 尾部部分BaseAddress VAD.StartingVpn, 释放到结束 ─ 调整 VAD.EndingVpn BaseAddress - 1 ─ 释放对应 PTE 情况4释放 VAD 中间部分释放中间一块两边保留 ─ 需要创建新 VAD 保存剩余后半部分 ─ 原 VAD 缩小到前半部分释放前 ┌──────────────────────────────┐ │ 原始 VAD │ └──────────────────────────────┘ 情况1 情况2 ┌──────────────────┐ ┌─────┐ ┌──────────┐ │ 全部释放 → 删除 │ │释放 │ │ 保留 │ └──────────────────┘ └─────┘ └──────────┘ Base StartingVpn 调整 情况3 情况4 ┌──────────┐ ┌─────┐ ┌─────┐ ┌──────┐ ┌─────┐ │ 保留 │ │释放 │ │释放 │ │ 保留1│ │保留2│ └──────────┘ └─────┘ └─────┘ └──────┘ └─────┘ (需要新建 VAD)3.2 MEM_DECOMMIT 分支取消提交// 1. 锁定地址空间// 2. 查找 VAD确认地址范围有效// 3. 调用 MiDecommitPages 取消提交// - 遍历指定范围的 PTE// - 对每个有效 PTE → 释放物理页、更新 PFN// - 将 PTE 写为 decommit 状态// 4. 更新进程 CommitCharge4. NtProtectVirtualMemory 路径通过MiProtectVirtualMemory实现1. 验证参数保护属性合法性 2. 锁定地址空间 3. 查找覆盖整个范围的 VAD 4. 调用 MiCheckSecuredVad 检查安全 VAD 限制 5. 锁定工作集 6. 遍历范围内的每个 PTE a. 如果 PTE 有效 - 修改硬件 PTE 的保护位 - 刷新 TLBKeFlushCurrentTb b. 如果 PTE 无效 - 修改软件 PTE 的保护位 c. 写时复制COW处理 - 如果新保护为只读但原为可写清除脏位 7. 更新 VAD 的保护属性 8. 解锁5. MiDeletePte——底层 PTE 删除逻辑文件virtual.c:369功能删除单个 PTE释放其引用的物理页。VOID NTAPIMiDeletePte(IN PMMPTE PointerPte,IN PVOID VirtualAddress,IN PEPROCESS CurrentProcess,IN PMMPTE PrototypePte){TempPte*PointerPte;if(TempPte.u.Hard.Valid0){// 无效 PTE可能是 Transition 或 PageFile 状态if(TempPte.u.Soft.Transition){// Transition PTE → 从列表中拆除递减引用MiUnlinkPageFromList(Pfn1);MiDecrementReferenceCount(Pfn1,PageFrameIndex);}// 忽略已换出PageFile的 PTE}else{// 有效 PTEPageFrameIndexPFN_FROM_PTE(TempPte);Pfn1MiGetPfnEntry(PageFrameIndex);if(Pfn1-u3.e1.PrototypePte1){// 原型 PTE → 递减页表共享计数和 PFN 共享计数MiDecrementShareCount(PageTablePfn,...);MiDecrementShareCount(Pfn1,PageFrameIndex);}else{// 普通 PTE → 标记删除递减共享计数MI_SET_PFN_DELETED(Pfn1);MiDecrementShareCount(Pfn1,PageFrameIndex);}// 擦除 PTEMI_ERASE_PTE(PointerPte);}KeFlushCurrentTb();}6. MiDeleteVirtualAddresses——批量 PTE 删除文件virtual.c:530功能批量删除指定虚拟地址范围内的所有 PTE。VOID NTAPIMiDeleteVirtualAddresses(IN ULONG_PTR Va,IN ULONG_PTR EndingAddress,IN OPTIONAL PMMVAD Vad){while(VaEndingAddress){// 逐级检查 PXE → PPE → PDE → PTE 的有效性// 跳过无效的大页表区域AddressGap// 对每个有效 PTE 调用 MiDeletePte// 递减页表引用计数必要时删除 PDEVaPAGE_SIZE;}}关键特点支持多级页表x86 PAE / x86-64的地址间隙跳过对 Section VAD 的间隙处理通过MI_GET_PROTOTYPE_PTE_FOR_VPN跳到下一个原型 PTE批量删除过程中保持 PFN 锁定7. MiDecommitPages——页面取消提交功能将已提交的页面取消提交释放物理内存但保留 VAD 结构。1. 遍历指定地址范围的每个 PTE 2. 对每个 PTE a. 如果 PTE 有效Valid1 - 获取 PFN - 标记 PFN 已删除MI_SET_PFN_DELETED - 递减 ShareCount - 擦除 PTE b. 如果 PTE 是 Transition 状态 - 从列表中拆除 PFN - 递减引用计数 c. 如果 PTE 是 DemandZero 状态 - 直接擦除 PTE 3. 更新进程/系统提交计数 4. 刷新 TLB8. MmGetPhysicalAddress——虚拟地址转物理地址文件virtual.c:5681PHYSICAL_ADDRESS NTAPIMmGetPhysicalAddress(PVOID Address){// 1. 检查 PDE 是否有效对大页 SPI 还检查 PPE/PXEif(MiAddressToPde(Address)-u.Hard.Valid){// 2. 检查是否为大页LargePageTempPde*MiAddressToPde(Address);if(TempPde.u.Hard.LargePage){// 大页偏移 地址低 22 位4MB 大页PhysicalAddress.QuadPart(ULONG64)TempPde.u.Hard.PageFrameNumberPAGE_SHIFT;PhysicalAddress.QuadPart((ULONG_PTR)Address(PAGE_SIZE*PTE_PER_PAGE-1));returnPhysicalAddress;}// 3. 检查 PTE 是否有效TempPte*MiAddressToPte(Address);if(TempPte.u.Hard.Valid){// 物理地址 PFN 12 页内偏移PhysicalAddress.QuadPart(ULONG64)TempPte.u.Hard.PageFrameNumberPAGE_SHIFT;PhysicalAddress.QuadPart((ULONG_PTR)Address(PAGE_SIZE-1));returnPhysicalAddress;}}// 4. 无效地址返回 0PhysicalAddress.QuadPart0;returnPhysicalAddress;}9. MmCopyVirtualMemory——跨进程内存拷贝文件virtual.c:1269两种策略基于数据量选择9.1 大拷贝 512 字节MDL 映射方式MiDoMappedCopyBufferSize MI_POOL_COPY_BYTES (512) → MiDoMappedCopy 流程 1. 循环拷贝每次最大 14 页MI_MAPPED_COPY_PAGES * PAGE_SIZE 56KB 2. 附加到源进程 → ProbeForRead → MmProbeAndLockPages → MmMapLockedPagesSpecifyCache 3. 分离源进程 4. 附加到目标进程 → ProbeForWrite → RtlCopyMemory 5. 分离目标进程 6. MmUnmapLockedPages → MmUnlockPages 7. 继续下一批数据9.2 小拷贝≤ 512 字节池缓冲方式MiDoPoolCopyBufferSize MI_POOL_COPY_BYTES (512) → MiDoPoolCopy 流程 1. 如果 BufferSize 512使用栈上缓冲StackBuffer否则分配 NonPagedPool 2. 附加到源进程 → ProbeForRead → RtlCopyMemory(池, 源) 3. 分离源进程 4. 附加到目标进程 → ProbeForWrite → RtlCopyMemory(目标, 池) 5. 分离目标进程 6. 重复直到所有数据拷贝完毕入口函数NTSTATUS NTAPIMmCopyVirtualMemory(PEPROCESS SourceProcess,PVOID SourceAddress,PEPROCESS TargetProcess,PVOID TargetAddress,SIZE_T BufferSize,KPROCESSOR_MODE PreviousMode,PSIZE_T ReturnSize){if(!BufferSize)returnSTATUS_SUCCESS;// 获取目标进程的 rundown protectionif(!ExAcquireRundownProtection(Process-RundownProtect))returnSTATUS_PROCESS_IS_TERMINATING;if(BufferSizeMI_POOL_COPY_BYTES)StatusMiDoMappedCopy(SourceProcess,SourceAddress,...);elseStatusMiDoPoolCopy(SourceProcess,SourceAddress,...);ExReleaseRundownProtection(Process-RundownProtect);returnStatus;}附录virtual.c 函数一览函数行号功能MiCalculatePageCommitment40计算指定地址范围内的已提交页数MiMakeSystemAddressValid181确保系统页表地址有效触发缺页MiDeleteSystemPageableVm273删除可分页系统内存MiDeletePte367删除单个 PTEMiDeleteVirtualAddresses528批量删除 PTEMiDoMappedCopy793MDL 方式跨进程拷贝MiDoPoolCopy1018池缓冲方式跨进程拷贝MmCopyVirtualMemory1269跨进程内存拷贝入口MiProtectVirtualMemory22 (extern)修改内存保护属性MiGetPageProtection1359获取页面的保护属性NtAllocateVirtualMemory4457虚拟内存分配系统调用MmGetPhysicalAddress5681虚拟地址→物理地址转换