mm学习笔记_05:虚拟内存核心与系统调用
mm学习笔记_05:虚拟内存核心与系统调用
基于 ReactOS ARM3 MM 源码(ntoskrnl/mm/ARM3/virtual.c)
1. virtual.c 概述
文件位置:d:\reactos\ntoskrnl\mm\ARM3\virtual.c
职责:虚拟内存管理的核心,实现以下关键系统调用:
NtAllocateVirtualMemory— 分配虚拟内存NtFreeVirtualMemory— 释放虚拟内存NtProtectVirtualMemory— 修改内存保护属性MmGetPhysicalAddress— 虚拟地址转物理地址MmCopyVirtualMemory— 跨进程内存拷贝
2. NtAllocateVirtualMemory——完整路径
文件:virtual.c:4457
2.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(ZeroBits>MI_MAX_ZERO_BITS)returnSTATUS_INVALID_PARAMETER_3;// 2. AllocationType 合法性检查(必须包含 MEM_COMMIT / MEM_RESERVE / MEM_RESET)if(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((AllocationType&MEM_RESET)&&(AllocationType!=MEM_RESET))returnSTATUS_INVALID_PARAMETER_5;// 4. MEM_LARGE_PAGES 必须带 MEM_COMMITif((AllocationType&MEM_LARGE_PAGES)&&!(AllocationType&MEM_COMMIT))returnSTATUS_INVALID_PARAMETER_5;// 5. MEM_WRITE_WATCH 必须带 MEM_RESERVEif((AllocationType&MEM_WRITE_WATCH)&&!(AllocationType&MEM_RESERVE))returnSTATUS_INVALID_PARAMETER_5;// 6. MEM_PHYSICAL 必须带 MEM_RESERVE 且 Protect == PAGE_READWRITEif((AllocationType&MEM_PHYSICAL)&&...)...// 7. 保护属性验证ProtectionMask=MiMakeProtectionMask(Protect);if(ProtectionMask==MM_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(PBaseAddress>MM_HIGHEST_VAD_ADDRESS)returnSTATUS_INVALID_PARAMETER_2;if(!PRegionSize)returnSTATUS_INVALID_PARAMETER_4;2.3 步骤二:进程获取与附加
// 如果是当前进程,直接使用if(ProcessHandle==NtCurrentProcess())Process=CurrentProcess;else{// 否则以 PROCESS_VM_OPERATION 权限引用目标进程Status=ObReferenceObjectByHandle(ProcessHandle,PROCESS_VM_OPERATION,PsProcessType,...);// 如果目标进程不是当前进程,附加到目标进程地址空间if(CurrentProcess!=Process){KeStackAttachProcess(&Process->Pcb,&ApcState);Attached=TRUE;}}2.4 步骤三:MEM_RESERVE 主路径
当未指定基址或指定 MEM_RESERVE 时:
1. 配额计费 PsChargeProcessNonPagedPoolQuota(sizeof(MMVAD_LONG)) 2. 分配 VAD:ExAllocatePoolWithTag(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 分支] │ ├─ 配额计费 │ ├─ 分配 VAD(ExAllocatePoolWithTag) │ ├─ VAD 初始化 │ └─ MiInsertVadEx(自动搜索 + Avl 插入) │ ├─ [MEM_COMMIT 分支] │ ├─ MmLockAddressSpace │ ├─ 查找已有 VAD(MiCheckForConflictingNode) │ ├─ 验证 VAD 类型 │ ├─ [Section 内存] → 写原型 PTE → 更新段提交计数 │ ├─ [私有内存] → 更新 CommitCharge │ │ └─ 锁定工作集 │ │ └─ 遍历 PTE,确保 PDE 有效,写 DemandZero PTE │ └─ 解锁地址空间 │ └─ 返回 BaseAddress + STATUS_SUCCESS3. NtFreeVirtualMemory——完整路径
3.1 MEM_RELEASE 分支(完整 VAD 释放)
四种释放情况:
情况1:释放整个 VAD(BaseAddress == 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 的保护位 - 刷新 TLB(KeFlushCurrentTb) 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.Valid==0){// 无效 PTE:可能是 Transition 或 PageFile 状态if(TempPte.u.Soft.Transition){// Transition PTE → 从列表中拆除,递减引用MiUnlinkPageFromList(Pfn1);MiDecrementReferenceCount(Pfn1,PageFrameIndex);}// 忽略已换出(PageFile)的 PTE}else{// 有效 PTEPageFrameIndex=PFN_FROM_PTE(&TempPte);Pfn1=MiGetPfnEntry(PageFrameIndex);if(Pfn1->u3.e1.PrototypePte==1){// 原型 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(Va<=EndingAddress){// 逐级检查 PXE → PPE → PDE → PTE 的有效性// 跳过无效的大页表区域(AddressGap)// 对每个有效 PTE 调用 MiDeletePte// 递减页表引用计数,必要时删除 PDEVa+=PAGE_SIZE;}}关键特点:
- 支持多级页表(x86 PAE / x86-64)的地址间隙跳过
- 对 Section VAD 的间隙处理:通过
MI_GET_PROTOTYPE_PTE_FOR_VPN跳到下一个原型 PTE - 批量删除过程中保持 PFN 锁定
7. MiDecommitPages——页面取消提交
功能:将已提交的页面取消提交,释放物理内存但保留 VAD 结构。
1. 遍历指定地址范围的每个 PTE 2. 对每个 PTE: a. 如果 PTE 有效(Valid=1): - 获取 PFN - 标记 PFN 已删除(MI_SET_PFN_DELETED) - 递减 ShareCount - 擦除 PTE b. 如果 PTE 是 Transition 状态: - 从列表中拆除 PFN - 递减引用计数 c. 如果 PTE 是 DemandZero 状态: - 直接擦除 PTE 3. 更新进程/系统提交计数 4. 刷新 TLB8. MmGetPhysicalAddress——虚拟地址转物理地址
文件:virtual.c:5681
PHYSICAL_ADDRESS NTAPIMmGetPhysicalAddress(PVOID Address){// 1. 检查 PDE 是否有效(对大页 SPI 还检查 PPE/PXE)if(MiAddressToPde(Address)->u.Hard.Valid){// 2. 检查是否为大页(LargePage)TempPde=*MiAddressToPde(Address);if(TempPde.u.Hard.LargePage){// 大页偏移 = 地址低 22 位(4MB 大页)PhysicalAddress.QuadPart=(ULONG64)TempPde.u.Hard.PageFrameNumber<<PAGE_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.PageFrameNumber<<PAGE_SHIFT;PhysicalAddress.QuadPart+=((ULONG_PTR)Address&(PAGE_SIZE-1));returnPhysicalAddress;}}// 4. 无效地址,返回 0PhysicalAddress.QuadPart=0;returnPhysicalAddress;}9. MmCopyVirtualMemory——跨进程内存拷贝
文件:virtual.c:1269
两种策略,基于数据量选择:
9.1 大拷贝(> 512 字节):MDL 映射方式MiDoMappedCopy
BufferSize > 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 字节):池缓冲方式MiDoPoolCopy
BufferSize <= 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(BufferSize>MI_POOL_COPY_BYTES)Status=MiDoMappedCopy(SourceProcess,SourceAddress,...);elseStatus=MiDoPoolCopy(SourceProcess,SourceAddress,...);ExReleaseRundownProtection(&Process->RundownProtect);returnStatus;}附录:virtual.c 函数一览
| 函数 | 行号 | 功能 |
|---|---|---|
MiCalculatePageCommitment | 40 | 计算指定地址范围内的已提交页数 |
MiMakeSystemAddressValid | 181 | 确保系统页表地址有效(触发缺页) |
MiDeleteSystemPageableVm | 273 | 删除可分页系统内存 |
MiDeletePte | 367 | 删除单个 PTE |
MiDeleteVirtualAddresses | 528 | 批量删除 PTE |
MiDoMappedCopy | 793 | MDL 方式跨进程拷贝 |
MiDoPoolCopy | 1018 | 池缓冲方式跨进程拷贝 |
MmCopyVirtualMemory | 1269 | 跨进程内存拷贝入口 |
MiProtectVirtualMemory | 22 (extern) | 修改内存保护属性 |
MiGetPageProtection | 1359 | 获取页面的保护属性 |
NtAllocateVirtualMemory | 4457 | 虚拟内存分配系统调用 |
MmGetPhysicalAddress | 5681 | 虚拟地址→物理地址转换 |
