MM 学习笔记 10:页面文件与系统加载器
MM 学习笔记 10页面文件与系统加载器文件ntoskrnl/mm/pagefile.cntoskrnl/mm/rmap.cntoskrnl/mm/ARM3/sysldr.cntoskrnl/mm/ARM3/mdlsup.cntoskrnl/mm/ARM3/iosup.c日期2026-07-191. 概述这组文件涵盖内存管理的外围支持功能文件行数职责pagefile.c~900 行页面文件管理交换槽位分配、磁盘 I/Ormap.c~470 行反向映射从物理页找到对应的虚拟地址sysldr.c~3600 行系统镜像加载器PE 文件加载、导入表解析mdlsup.c~2000 行MDL 管理内存描述符列表、页面锁定iosup.c~500 行I/O 空间映射MmMapIoSpace / MmUnmapIoSpace2. pagefile.c — 页面文件管理位置ntoskrnl/mm/pagefile.c2.1 SWAPENTRY 结构SWAPENTRY 编码了页面文件索引和页面偏移/* * 编码/解码宏 */#defineFILE_FROM_ENTRY(i)((i)0x0f)// 低4位页面文件索引最多16个文件#defineOFFSET_FROM_ENTRY(i)((i)11)// 高比特位页面偏移#defineENTRY_FROM_FILE_OFFSET(i,j)((i)|((j)11)|0x400)// 编码SWAPENTRY 编码布局31 11|10 4|3 0 ┌────────────────┬──────────┬────────┐ │ 页面偏移 │ 保留/标志 │ 文件索引 │ │ (21 bits) │ (7 bits) │ (4 bits)│ └────────────────┴──────────┴────────┘最大支持 16 个页面文件MAX_PAGING_FILES 16每个文件支持约 2M 个页面21 位偏移0x400标志位表示该条目有效2.2 核心全局变量PMMPAGING_FILE MmPagingFile[MAX_PAGING_FILES];// 页面文件数组ULONG MmNumberOfPagingFiles;// 页面文件数量PFN_COUNT MiFreeSwapPages;// 空闲交换页面数PFN_COUNT MiUsedSwapPages;// 已使用交换页面数PFN_COUNT MiReservedSwapPages;// 已保留交换页面数KGUARDED_MUTEX MmPageFileCreationLock;// 页面文件创建锁BOOLEAN MmZeroPageFile;// 是否清零页面文件// 页面文件最小/最大尺寸#defineMINIMUM_PAGEFILE_SIZE(256ULL*PAGE_SIZE)// 256页 1MB#ifdefined(_M_IX86)!defined(_X86PAE_)#defineMAXIMUM_PAGEFILE_SIZE((1ULL*1024*1024-1)*PAGE_SIZE)// ~4GB#else#defineMAXIMUM_PAGEFILE_SIZE((4ULL*1024*1024*1024-1)*PAGE_SIZE)// ~16TB#endif// 提交比率控制#defineMM_PAGEFILE_COMMIT_RATIO(1)// 1:1 比率#defineMM_PAGEFILE_COMMIT_GRACE(256)// 256页的宽限2.3 MmAllocSwapPage / MmFreeSwapPage 槽位管理SWAPENTRY NTAPIMmAllocSwapPage(VOID)位置pagefile.c:320MmAllocSwapPage │ ├─ 加锁 MmPageFileCreationLock │ ├─ 检查 MiFreeSwapPages 0 │ ├─ 遍历所有页面文件 │ └─ 查找有 FreeSpace 的文件 │ └─ RtlFindClearBitsAndSet(PageFile-Bitmap, 1, 0) │ ├─ 计算 SWAPENTRY │ └─ entry ENTRY_FROM_FILE_OFFSET(i, off) │ ├─ 更新统计 │ ├─ PageFile-FreeSpace-- │ ├─ PageFile-CurrentUsage │ ├─ MiFreeSwapPages-- │ └─ MiUsedSwapPages │ └─ 返回 SWAPENTRYVOID NTAPIMmFreeSwapPage(SWAPENTRY Entry)MmFreeSwapPage │ ├─ i FILE_FROM_ENTRY(Entry) ├─ off OFFSET_FROM_ENTRY(Entry) - 1 │ ├─ RtlClearBit(PagingFile-Bitmap, off 5) │ └─ 更新统计 ├─ PagingFile-FreeSpace ├─ PagingFile-CurrentUsage-- ├─ MiFreeSwapPages └─ MiUsedSwapPages--2.4 MmWriteToSwapPage / MmReadFromSwapPage 磁盘 I/ONTSTATUS NTAPIMmWriteToSwapPage(SWAPENTRY SwapEntry,PFN_NUMBER Page)位置pagefile.c:147MmWriteToSwapPage │ ├─ 解码 SWAPENTRY │ ├─ i FILE_FROM_ENTRY(SwapEntry) │ └─ offset OFFSET_FROM_ENTRY(SwapEntry) - 1 │ ├─ 构建 MDL指向要写出的物理页 │ ├─ MmInitializeMdl(Mdl, NULL, PAGE_SIZE) │ └─ MmBuildMdlFromPages(Mdl, Page) │ ├─ 计算文件偏移 │ └─ file_offset.QuadPart offset * PAGE_SIZE │ └─ 发起同步写操作 └─ IoSynchronousPageWrite(FileObject, Mdl, file_offset, Event, Iosb) └─ 如果 STATUS_PENDING → 等待NTSTATUS NTAPIMmReadFromSwapPage(SWAPENTRY SwapEntry,PFN_NUMBER Page)位置pagefile.c:204MmReadFromSwapPage │ └─ 调用 MiReadPageFile(Page, FileIndex, PageFileOffset) │ ├─ 构建 MDL ├─ 计算文件偏移 └─ 发起同步读操作 └─ IoPageRead(FileObject, Mdl, file_offset, Event, Iosb)2.5 页面文件 I/O 流程图缺页异常 (Page Fault) │ ▼ MiResolvePageFileFault │ ├─ 解析 SWAPENTRY 从 PTE │ ├─ 分配物理页 │ └─ MmReadFromSwapPage(swapentry, page) │ ▼ MiReadPageFile │ ├─ 构建 MDL ├─ 计算偏移 └─ IoPageRead (同步) └─ → 页面内容加载完成 工作集修剪 (Trim) │ ▼ 页面换出 (Page Out) │ ├─ MmAllocSwapPage → 分配槽位 │ └─ MmWriteToSwapPage(swapentry, page) │ ├─ 构建 MDL └─ IoSynchronousPageWrite (同步) └─ → 页面内容写入交换文件2.6 NtCreatePagingFile 创建页面文件NTSTATUS NTAPINtCreatePagingFile(IN PUNICODE_STRING PageFileName,IN PLARGE_INTEGER MinimumSize,IN PLARGE_INTEGER MaximumSize,IN ULONG Priority)创建一个新的页面文件验证文件名和大小参数检查是否超过MAX_PAGING_FILES限制打开或创建文件分配MMPAGING_FILE结构初始化位图管理空闲槽位添加到MmPagingFile[]数组3. rmap.c — 反向映射位置ntoskrnl/mm/rmap.c3.1 概念反向映射允许系统从一个物理页帧号PFN找到所有映射到它的虚拟地址。这对页面换出至关重要——当需要换出一个物理页面时必须知道哪些进程映射了它以便更新它们的 PTE。数据结构物理页 (PFN) │ └─ 反向映射链表 (MM_RMAP_ENTRY) ├─ [0] → (进程A, 虚拟地址VA1) ├─ [1] → (进程B, 虚拟地址VA2) ├─ [2] → (段映射, 段指针) ← RMAP_IS_SEGMENT 标记 └─ ....3.2 初始化CODE_SEG(INIT)VOID NTAPIMmInitializeRmapList(VOID){ExInitializeNPagedLookasideList(RmapLookasideList,NULL,RmapListFree,0,sizeof(MM_RMAP_ENTRY),TAG_RMAP,50);}使用 Lookaside 列表缓存 RMAP 条目避免频繁分配/释放。3.3 MmInsertRmap / MmDeleteRmap 映射管理NTSTATUS NTAPIMmInsertRmap(PEPROCESS Process,PVOID Address,PFN_NUMBER Page)插入反向映射条目MmInsertRmap(Process, Address, Page) └─ 从 Lookaside 列表分配 MM_RMAP_ENTRY └─ entry-Process Process └─ entry-Address Address └─ 插入到 Page 的反向映射链表头部NTSTATUS NTAPIMmDeleteRmap(PEPROCESS Process,PVOID Address,PFN_NUMBER Page)删除反向映射条目MmDeleteRmap(Process, Address, Page) └─ 遍历 Page 的反向映射链表 └─ 找到匹配 (Process, Address) 的条目 └─ 从链表移除 └─ 释放回 Lookaside 列表3.4 MmPageOutPhysicalAddress 页面换出NTSTATUS NTAPIMmPageOutPhysicalAddress(PFN_NUMBER Page)位置rmap.c:51MmPageOutPhysicalAddress(Page) │ ├─ 获取反向映射条目的头 │ └─ entry MmGetRmapListHeadPage(Page) │ ├─ 跳过段映射条目 (RMAP_IS_SEGMENT) │ ├─ 获取进程和虚拟地址 │ ├─ Process entry-Process │ └─ Address entry-Address │ ├─ 获得进程的消亡保护 │ └─ ExAcquireRundownProtection(Process-RundownProtect) │ ├─ 锁定地址空间 ├─ 附加到进程如果需要 │ ├─ 为页面分配交换槽位 │ └─ MmAllocSwapPage → swapentry │ ├─ 写入交换文件 │ └─ MmWriteToSwapPage(swapentry, Page) │ ├─ 更新 PTE 为页文件 PTE │ └─ PTE 编码Valid0, PageFile1, SwapEntry │ ├─ 释放物理页 │ └─ MmReleasePageMemoryConsumer(MC_USER, Page) │ └─ 返回4. sysldr.c — 系统镜像加载器位置ntoskrnl/mm/ARM3/sysldr.c4.1 概述系统加载器负责将 PE 格式的内核模块驱动、DLL加载到系统空间。// 核心全局变量LIST_ENTRY PsLoadedModuleList;// 已加载模块链表LIST_ENTRY MmLoadedUserImageList;// 已加载用户映像链表KSPIN_LOCK PsLoadedModuleSpinLock;// 模块链表自旋锁ERESOURCE PsLoadedModuleResource;// 模块资源锁ULONG_PTR PsNtosImageBase;// NT 内核映像基址KMUTANT MmSystemLoadLock;// 系统加载互斥体4.2 MmLoadSystemImage PE 文件加载NTSTATUS NTAPIMmLoadSystemImage(IN PUNICODE_STRING FileName,IN PUNICODE_STRING NamePrefix OPTIONAL,IN BOOLEAN SessionLoad,IN BOOLEAN LoadSymbols,OUT PVOID*ImageBase,OUT PLDR_DATA_TABLE_ENTRY*LdrEntry)主要调用链MmLoadSystemImage │ ├─ 打开文件 │ └─ IoCreateFile / ObReferenceObjectByHandle │ ├─ 创建 Section │ └─ MmCreateSection → MmCreateArm3Section │ └─ 加载镜像 Section └─ MiLoadImageSection4.3 MiLoadImageSection 核心加载逻辑位置sysldr.c:78这是驱动加载的核心函数MiLoadImageSection(SectionPtr, ImageBase, FileName, SessionLoad, LdrEntry) │ ├─ 附加到系统进程 (KeStackAttachProcess) │ ├─ 映射 Section 视图 │ └─ MmMapViewOfSection(Section, SystemProcess, ...) │ ├─ 保留系统 PTE用于驱动驻留 │ └─ MiReserveSystemPtes(PteCount, SystemPteSpace) │ ├─ 分配物理页循环 │ └─ for each PTE: │ ├─ PageFrameIndex MiRemoveAnyPage(color) │ ├─ MiInitializePfn(PageFrameIndex, PointerPte, TRUE) │ └─ MI_WRITE_VALID_PTE(PointerPte, TempPte) │ ├─ 复制镜像内容 │ └─ RtlCopyMemory(DriverBase, Base, PteCount PAGE_SHIFT) │ └─ 取消映射临时视图 └─ MmUnmapViewOfSection(Process, Base)关键代码片段// 为驱动分配物理页TempPteValidKernelPte;while(PointerPteLastPte){ASSERT(PointerPte-u.Hard.Valid0);MI_SET_USAGE(MI_USAGE_DRIVER_PAGE);// 标记为驱动页面PageFrameIndexMiRemoveAnyPage(MI_GET_NEXT_COLOR());MiInitializePfn(PageFrameIndex,PointerPte,TRUE);TempPte.u.Hard.PageFrameNumberPageFrameIndex;MI_WRITE_VALID_PTE(PointerPte,TempPte);PointerPte;}// 复制映像内容RtlCopyMemory(DriverBase,Base,PteCountPAGE_SHIFT);4.4 MiResolveImageReferences 导入表解析解析 PE 文件的导入表将导入符号解析为实际地址MiResolveImageReferences(LdrEntry) │ ├─ 遍历导入描述符 (IMAGE_IMPORT_DESCRIPTOR) │ ├─ 对每个导入的 DLL │ ├─ 查找已加载的 DLL在 PsLoadedModuleList 中搜索 │ ├─ 如果未加载 → 先递归加载 │ └─ 解析每个导入函数 │ └─ 对每个导入函数 ├─ NameToOrdinal → 查找导出名称表 └─ MiSnapThunk → 修正 Thunk4.5 MiSnapThunk Thunk 修正修正导入地址表IAT中的每个条目MiSnapThunk(ImageBase, Thunk, ImportTable, Reason) │ ├─ 读取 Thunk 中的函数名或序号 ├─ 通过 NameToOrdinal 或直接使用序号 ├─ 在目标 DLL 的导出表中查找地址 └─ 将解析后的地址写回 Thunk辅助函数NameToOrdinalUSHORT NTAPINameToOrdinal(PCSTR ExportName,PVOID ImageBase,ULONG NumberOfNames,PULONG NameTable,PUSHORT OrdinalTable){// 在名称表上执行二分查找LowMid0;HighNumberOfNames-1;while(HighLow){Mid(LowHigh)1;Retstrcmp(ExportName,(PCHAR)RVA(ImageBase,NameTable[Mid]));if(Ret0)HighMid-1;elseif(Ret0)LowMid1;elsereturnOrdinalTable[Mid];}return-1;// 未找到}4.6 MiFreeInitializationCode 初始化代码释放系统启动后MiFreeInitializationCode释放标记为INIT的代码段.init节回收物理页面MiFreeInitializationCode │ ├─ 遍历已加载模块链表 │ ├─ 对每个模块 │ ├─ 查找 INIT 节 │ ├─ 释放物理页面标记为空闲 │ ├─ 释放系统 PTE │ └─ 刷新 TLB │ └─ 更新统计5. mdlsup.c — MDL 管理位置ntoskrnl/mm/ARM3/mdlsup.cMDLMemory Descriptor List描述一段物理内存缓冲区用于 I/O 操作。5.1 MDL 结构typedefstruct_MDL{struct_MDL*Next;// 链表CSHORT Size;// MDL 总大小CSHORT MdlFlags;// 标志struct_EPROCESS*Process;// 所属进程PVOID MappedSystemVa;// 系统空间映射地址PVOID StartVa;// 起始虚拟地址ULONG ByteOffset;// 页内偏移ULONG ByteCount;// 字节数// 后面紧跟 PFN 数组}MDL,*PMDL;5.2 MmProbeAndLockPagesVOID NTAPIMmProbeAndLockPages(IN PMDL Mdl,IN KPROCESSOR_MODE AccessMode,IN LOCK_OPERATION Operation)核心流程MmProbeAndLockPages(Mdl, AccessMode, Operation) │ ├─ 锁定地址空间 │ └─ MmLockAddressSpace │ ├─ 遍历 MDL 覆盖的页面 │ │ │ ├─ ProbeAndReadAddress用户模式地址探测 │ │ │ ├─ MiAddressToPte → 获取 PTE │ │ │ ├─ 解析 PTE │ │ ├─ 如果无效 → 触发缺页异常MmAccessFault │ │ ├─ 如果转换中 → MiResolveTransitionFault │ │ └─ 有效 → 使用页面 │ │ │ ├─ 增加 PFN 引用计数 │ │ └─ Pfn-ReferenceCount │ │ │ └─ 将 PFN 写入 MDL 的 PFN 数组 │ └─ MdlPages[i] PageFrameIndex │ └─ 设置 MDL 标志 └─ Mdl-MdlFlags | MDL_PAGES_LOCKED5.3 MmMapLockedPagesSpecifyCachePVOID NTAPIMmMapLockedPagesSpecifyCache(IN PMDL Mdl,IN KPROCESSOR_MODE AccessMode,IN MEMORY_CACHING_TYPE CacheType,IN PVOID BaseAddress OPTIONAL,IN BOOLEAN BugCheckOnFailure,IN ULONG Priority)将已锁定的页面映射到系统空间MmMapLockedPagesSpecifyCache(Mdl, ...) │ ├─ 如果是内核模式访问 → 直接计算地址 │ └─ 返回 Mdl-StartVa Mdl-ByteOffset │ ├─ 用户模式访问 → 需要 VAD │ └─ MiMapLockedPagesInUserSpace(Mdl, ...) │ ├─ 分配 VADMMVAD_LONG │ ├─ 查找空闲地址范围 │ ├─ 设置页表项使用 PFN 数组 │ └─ 设置缓存属性 │ └─ 设置 MDL_MAPPED_TO_SYSTEM_VA 标志5.4 MmAllocatePagesForMdl / MmFreePagesFromMdlPMDL NTAPIMmAllocatePagesForMdl(IN PHYSICAL_ADDRESS LowAddress,IN PHYSICAL_ADDRESS HighAddress,IN PHYSICAL_ADDRESS SkipBytes,IN SIZE_T TotalBytes)分配物理页并创建 MDLMmAllocatePagesForMdl ├─ 计算需要的页面数 ├─ 分配 MDL 结构 ├─ 分配物理页从指定地址范围 ├─ 写入 PFN 数组 └─ 返回 MDLVOID NTAPIMmFreePagesFromMdl(IN PMDL Mdl)释放 MDL 中的物理页面MmFreePagesFromMdl ├─ 遍历 MDL 的 PFN 数组 ├─ 对每个页面 │ └─ MmReleasePageMemoryConsumer → 释放页面 └─ 清除标志6. iosup.c — I/O 空间映射位置ntoskrnl/mm/ARM3/iosup.c6.1 MmMapIoSpacePVOID NTAPIMmMapIoSpace(IN PHYSICAL_ADDRESS PhysicalAddress,IN SIZE_T NumberOfBytes,IN MEMORY_CACHING_TYPE CacheType)位置iosup.c:46将物理地址空间映射到内核虚拟地址空间用于访问设备内存MMIOMmMapIoSpace(PhysicalAddress, NumberOfBytes, CacheType) │ ├─ 1. 参数验证 │ ├─ NumberOfBytes 必须大于 0 │ └─ CacheType 必须在有效范围内 │ ├─ 2. 计算页面数 │ └─ PageCount ADDRESS_AND_SIZE_TO_SPAN_PAGES(...) │ ├─ 3. 计算 PFN │ └─ Pfn PhysicalAddress.QuadPart PAGE_SHIFT │ ├─ 4. 获取缓存属性 │ └─ CacheAttribute MiPlatformCacheAttributes[isIoMapping][CacheType] │ ├─ 5. 保留系统 PTE │ └─ PointerPte MiReserveSystemPtes(PageCount, SystemPteSpace) │ ├─ 6. 设置页表循环 │ └─ for each page: │ ├─ TempPte IoPteTemplate (写入合并/非缓存等) │ └─ MI_WRITE_VALID_PTE(PointerPte, TempPte) │ └─ 7. 返回虚拟地址 └─ BaseAddress MiPteToAddress(PointerPte)6.2 MmUnmapIoSpaceVOID NTAPIMmUnmapIoSpace(IN PVOID BaseAddress,IN SIZE_T NumberOfBytes)取消 I/O 空间映射计算页面数和起始 PTE清零 PTE释放系统 PTE刷新 TLB6.3 平台缓存属性表// 每种架构有自己的缓存属性MI_PFN_CACHE_ATTRIBUTE MiPlatformCacheAttributes[2][MmMaximumCacheType]{// RAM 映射{MiNonCached,MiCached,MiWriteCombined,MiCached,MiNonCached,MiWriteCombined},// 设备内存映射{MiNonCached,MiCached,MiWriteCombined,MiCached,MiNonCached,MiWriteCombined},};7. 整体数据流┌─────────────────┐ │ 系统加载器 │ │ sysldr.c │ │ MmLoadSystemImage│ └────────┬────────┘ │ 加载驱动/内核模块 ▼ ┌─────────────────┐ │ Section 创建 │ │ section.c │ └────────┬────────┘ │ 创建映射 ▼ ┌─────────────────┐ ┌─────────────────┐ │ 页面文件管理 │◄─────│ 缺页异常处理 │ │ pagefile.c │ │ pagfault.c │ │ MmWriteToSwap │──────│ MiResolve*Fault│ │ MmReadFromSwap │ └─────────────────┘ └────────┬────────┘ │ │ │ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ │ 反向映射 │◄─────│ 工作集修剪 │ │ rmap.c │ │ balance.c │ │ MmPageOut │──────│ MmTrimUserMemory│ │ PhysicalAddress│ └─────────────────┘ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ MDL 管理 │ │ mdlsup.c │ │ MmProbeAndLock │ │ MmMapLockedPages│ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ I/O 空间映射 │ │ iosup.c │ │ MmMapIoSpace │ │ MmUnmapIoSpace │ └─────────────────┘参考链接[pagefile.c — 页面文件管理](file:///d:/reactos/ntoskrnl/mm/pagefile.c)[rmap.c — 反向映射](file:///d:/reactos/ntoskrnl/mm/rmap.c)[sysldr.c — 系统镜像加载器](file:///d:/reactos/ntoskrnl/mm/ARM3/sysldr.c)[mdlsup.c — MDL 管理](file:///d:/reactos/ntoskrnl/mm/ARM3/mdlsup.c)[iosup.c — I/O 空间映射](file:///d:/reactos/ntoskrnl/mm/ARM3/iosup.c)[Windows Internals 7th Part 1, Chapter 10 “Memory Management”]