MM 学习笔记 10:页面文件与系统加载器
文件:
ntoskrnl/mm/pagefile.c+ntoskrnl/mm/rmap.c+ntoskrnl/mm/ARM3/sysldr.c+ntoskrnl/mm/ARM3/mdlsup.c+ntoskrnl/mm/ARM3/iosup.c日期:2026-07-19
1. 概述
这组文件涵盖内存管理的"外围"支持功能:
| 文件 | 行数 | 职责 |
|---|---|---|
pagefile.c |
~900 行 | 页面文件管理:交换槽位分配、磁盘 I/O |
rmap.c |
~470 行 | 反向映射:从物理页找到对应的虚拟地址 |
sysldr.c |
~3600 行 | 系统镜像加载器:PE 文件加载、导入表解析 |
mdlsup.c |
~2000 行 | MDL 管理:内存描述符列表、页面锁定 |
iosup.c |
~500 行 | I/O 空间映射:MmMapIoSpace / MmUnmapIoSpace |
2. pagefile.c --- 页面文件管理
位置: ntoskrnl/mm/pagefile.c
2.1 SWAPENTRY 结构
SWAPENTRY 编码了页面文件索引和页面偏移:
c
/*
* 编码/解码宏
*/
#define FILE_FROM_ENTRY(i) ((i) & 0x0f) // 低4位:页面文件索引(最多16个文件)
#define OFFSET_FROM_ENTRY(i) ((i) >> 11) // 高比特位:页面偏移
#define ENTRY_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 核心全局变量
c
PMMPAGING_FILE MmPagingFile[MAX_PAGING_FILES]; // 页面文件数组
ULONG MmNumberOfPagingFiles; // 页面文件数量
PFN_COUNT MiFreeSwapPages; // 空闲交换页面数
PFN_COUNT MiUsedSwapPages; // 已使用交换页面数
PFN_COUNT MiReservedSwapPages; // 已保留交换页面数
KGUARDED_MUTEX MmPageFileCreationLock; // 页面文件创建锁
BOOLEAN MmZeroPageFile; // 是否清零页面文件
// 页面文件最小/最大尺寸
#define MINIMUM_PAGEFILE_SIZE (256ULL * PAGE_SIZE) // 256页 = 1MB
#if defined(_M_IX86) && !defined(_X86PAE_)
#define MAXIMUM_PAGEFILE_SIZE ((1ULL * 1024 * 1024 - 1) * PAGE_SIZE) // ~4GB
#else
#define MAXIMUM_PAGEFILE_SIZE ((4ULL * 1024 * 1024 * 1024 - 1) * PAGE_SIZE) // ~16TB
#endif
// 提交比率控制
#define MM_PAGEFILE_COMMIT_RATIO (1) // 1:1 比率
#define MM_PAGEFILE_COMMIT_GRACE (256) // 256页的宽限
2.3 MmAllocSwapPage / MmFreeSwapPage 槽位管理
c
SWAPENTRY
NTAPI
MmAllocSwapPage(VOID)
位置: pagefile.c:320
MmAllocSwapPage
│
├─ 加锁 MmPageFileCreationLock
│
├─ 检查 MiFreeSwapPages > 0
│
├─ 遍历所有页面文件
│ └─ 查找有 FreeSpace 的文件
│ └─ RtlFindClearBitsAndSet(PageFile->Bitmap, 1, 0)
│
├─ 计算 SWAPENTRY
│ └─ entry = ENTRY_FROM_FILE_OFFSET(i, off)
│
├─ 更新统计
│ ├─ PageFile->FreeSpace--
│ ├─ PageFile->CurrentUsage++
│ ├─ MiFreeSwapPages--
│ └─ MiUsedSwapPages++
│
└─ 返回 SWAPENTRY
c
VOID
NTAPI
MmFreeSwapPage(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/O
c
NTSTATUS
NTAPI
MmWriteToSwapPage(SWAPENTRY SwapEntry, PFN_NUMBER Page)
位置: pagefile.c:147
MmWriteToSwapPage
│
├─ 解码 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 → 等待
c
NTSTATUS
NTAPI
MmReadFromSwapPage(SWAPENTRY SwapEntry, PFN_NUMBER Page)
位置: pagefile.c:204
MmReadFromSwapPage
│
└─ 调用 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 创建页面文件
c
NTSTATUS
NTAPI
NtCreatePagingFile(
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.c
3.1 概念
反向映射允许系统从一个物理页帧号(PFN)找到所有映射到它的虚拟地址。这对页面换出至关重要------当需要换出一个物理页面时,必须知道哪些进程映射了它,以便更新它们的 PTE。
数据结构:
物理页 (PFN)
│
└─ 反向映射链表 (MM_RMAP_ENTRY)
├─ [0] → (进程A, 虚拟地址VA1)
├─ [1] → (进程B, 虚拟地址VA2)
├─ [2] → (段映射, 段指针) ← RMAP_IS_SEGMENT 标记
└─ ....
3.2 初始化
c
CODE_SEG("INIT")
VOID
NTAPI
MmInitializeRmapList(VOID)
{
ExInitializeNPagedLookasideList(&RmapLookasideList,
NULL,
RmapListFree,
0,
sizeof(MM_RMAP_ENTRY),
TAG_RMAP,
50);
}
使用 Lookaside 列表缓存 RMAP 条目,避免频繁分配/释放。
3.3 MmInsertRmap / MmDeleteRmap 映射管理
c
NTSTATUS
NTAPI
MmInsertRmap(PEPROCESS Process, PVOID Address, PFN_NUMBER Page)
插入反向映射条目:
MmInsertRmap(Process, Address, Page)
└─ 从 Lookaside 列表分配 MM_RMAP_ENTRY
└─ entry->Process = Process
└─ entry->Address = Address
└─ 插入到 Page 的反向映射链表头部
c
NTSTATUS
NTAPI
MmDeleteRmap(PEPROCESS Process, PVOID Address, PFN_NUMBER Page)
删除反向映射条目:
MmDeleteRmap(Process, Address, Page)
└─ 遍历 Page 的反向映射链表
└─ 找到匹配 (Process, Address) 的条目
└─ 从链表移除
└─ 释放回 Lookaside 列表
3.4 MmPageOutPhysicalAddress 页面换出
c
NTSTATUS
NTAPI
MmPageOutPhysicalAddress(PFN_NUMBER Page)
位置: rmap.c:51
MmPageOutPhysicalAddress(Page)
│
├─ 获取反向映射条目的头
│ └─ entry = MmGetRmapListHeadPage(Page)
│
├─ 跳过段映射条目 (RMAP_IS_SEGMENT)
│
├─ 获取进程和虚拟地址
│ ├─ Process = entry->Process
│ └─ Address = entry->Address
│
├─ 获得进程的消亡保护
│ └─ ExAcquireRundownProtection(&Process->RundownProtect)
│
├─ 锁定地址空间
├─ 附加到进程(如果需要)
│
├─ 为页面分配交换槽位
│ └─ MmAllocSwapPage → swapentry
│
├─ 写入交换文件
│ └─ MmWriteToSwapPage(swapentry, Page)
│
├─ 更新 PTE 为页文件 PTE
│ └─ PTE 编码:Valid=0, PageFile=1, SwapEntry
│
├─ 释放物理页
│ └─ MmReleasePageMemoryConsumer(MC_USER, Page)
│
└─ 返回
4. sysldr.c --- 系统镜像加载器
位置: ntoskrnl/mm/ARM3/sysldr.c
4.1 概述
系统加载器负责将 PE 格式的内核模块(驱动、DLL)加载到系统空间。
c
// 核心全局变量
LIST_ENTRY PsLoadedModuleList; // 已加载模块链表
LIST_ENTRY MmLoadedUserImageList; // 已加载用户映像链表
KSPIN_LOCK PsLoadedModuleSpinLock; // 模块链表自旋锁
ERESOURCE PsLoadedModuleResource; // 模块资源锁
ULONG_PTR PsNtosImageBase; // NT 内核映像基址
KMUTANT MmSystemLoadLock; // 系统加载互斥体
4.2 MmLoadSystemImage PE 文件加载
c
NTSTATUS
NTAPI
MmLoadSystemImage(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
└─ MiLoadImageSection
4.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)
关键代码片段:
c
// 为驱动分配物理页
TempPte = ValidKernelPte;
while (PointerPte < LastPte)
{
ASSERT(PointerPte->u.Hard.Valid == 0);
MI_SET_USAGE(MI_USAGE_DRIVER_PAGE); // 标记为驱动页面
PageFrameIndex = MiRemoveAnyPage(MI_GET_NEXT_COLOR());
MiInitializePfn(PageFrameIndex, PointerPte, TRUE);
TempPte.u.Hard.PageFrameNumber = PageFrameIndex;
MI_WRITE_VALID_PTE(PointerPte, TempPte);
PointerPte++;
}
// 复制映像内容
RtlCopyMemory(DriverBase, Base, PteCount << PAGE_SHIFT);
4.4 MiResolveImageReferences 导入表解析
解析 PE 文件的导入表,将导入符号解析为实际地址:
MiResolveImageReferences(LdrEntry)
│
├─ 遍历导入描述符 (IMAGE_IMPORT_DESCRIPTOR)
│
├─ 对每个导入的 DLL:
│ ├─ 查找已加载的 DLL(在 PsLoadedModuleList 中搜索)
│ ├─ 如果未加载 → 先递归加载
│ └─ 解析每个导入函数
│
└─ 对每个导入函数:
├─ NameToOrdinal → 查找导出名称表
└─ MiSnapThunk → 修正 Thunk
4.5 MiSnapThunk Thunk 修正
修正导入地址表(IAT)中的每个条目:
MiSnapThunk(ImageBase, Thunk, ImportTable, Reason)
│
├─ 读取 Thunk 中的函数名或序号
├─ 通过 NameToOrdinal 或直接使用序号
├─ 在目标 DLL 的导出表中查找地址
└─ 将解析后的地址写回 Thunk
辅助函数 NameToOrdinal:
c
USHORT
NTAPI
NameToOrdinal(PCSTR ExportName, PVOID ImageBase,
ULONG NumberOfNames, PULONG NameTable, PUSHORT OrdinalTable)
{
// 在名称表上执行二分查找
Low = Mid = 0;
High = NumberOfNames - 1;
while (High >= Low)
{
Mid = (Low + High) >> 1;
Ret = strcmp(ExportName, (PCHAR)RVA(ImageBase, NameTable[Mid]));
if (Ret < 0) High = Mid - 1;
else if (Ret > 0) Low = Mid + 1;
else return OrdinalTable[Mid];
}
return -1; // 未找到
}
4.6 MiFreeInitializationCode 初始化代码释放
系统启动后,MiFreeInitializationCode 释放标记为 INIT 的代码段(.init 节),回收物理页面:
MiFreeInitializationCode
│
├─ 遍历已加载模块链表
│
├─ 对每个模块:
│ ├─ 查找 INIT 节
│ ├─ 释放物理页面(标记为空闲)
│ ├─ 释放系统 PTE
│ └─ 刷新 TLB
│
└─ 更新统计
5. mdlsup.c --- MDL 管理
位置: ntoskrnl/mm/ARM3/mdlsup.c
MDL(Memory Descriptor List)描述一段物理内存缓冲区,用于 I/O 操作。
5.1 MDL 结构
c
typedef struct _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 MmProbeAndLockPages
c
VOID
NTAPI
MmProbeAndLockPages(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_LOCKED
5.3 MmMapLockedPagesSpecifyCache
c
PVOID
NTAPI
MmMapLockedPagesSpecifyCache(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, ...)
│ ├─ 分配 VAD(MMVAD_LONG)
│ ├─ 查找空闲地址范围
│ ├─ 设置页表项(使用 PFN 数组)
│ └─ 设置缓存属性
│
└─ 设置 MDL_MAPPED_TO_SYSTEM_VA 标志
5.4 MmAllocatePagesForMdl / MmFreePagesFromMdl
c
PMDL
NTAPI
MmAllocatePagesForMdl(IN PHYSICAL_ADDRESS LowAddress,
IN PHYSICAL_ADDRESS HighAddress,
IN PHYSICAL_ADDRESS SkipBytes,
IN SIZE_T TotalBytes)
分配物理页并创建 MDL:
MmAllocatePagesForMdl
├─ 计算需要的页面数
├─ 分配 MDL 结构
├─ 分配物理页(从指定地址范围)
├─ 写入 PFN 数组
└─ 返回 MDL
c
VOID
NTAPI
MmFreePagesFromMdl(IN PMDL Mdl)
释放 MDL 中的物理页面:
MmFreePagesFromMdl
├─ 遍历 MDL 的 PFN 数组
├─ 对每个页面:
│ └─ MmReleasePageMemoryConsumer → 释放页面
└─ 清除标志
6. iosup.c --- I/O 空间映射
位置: ntoskrnl/mm/ARM3/iosup.c
6.1 MmMapIoSpace
c
PVOID
NTAPI
MmMapIoSpace(IN PHYSICAL_ADDRESS PhysicalAddress,
IN SIZE_T NumberOfBytes,
IN MEMORY_CACHING_TYPE CacheType)
位置: iosup.c:46
将物理地址空间映射到内核虚拟地址空间,用于访问设备内存(MMIO):
MmMapIoSpace(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 MmUnmapIoSpace
c
VOID
NTAPI
MmUnmapIoSpace(IN PVOID BaseAddress, IN SIZE_T NumberOfBytes)
取消 I/O 空间映射:
- 计算页面数和起始 PTE
- 清零 PTE
- 释放系统 PTE
- 刷新 TLB
6.3 平台缓存属性表
c
// 每种架构有自己的缓存属性
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"