Windows 内存管理概念详解
概述
本文详细解释 Windows 内核(及 ReactOS)中 8 个核心内存管理概念。每个概念包含:定义 、数据结构 、操作流程 、对应的 ReactOS 源文件 以及与其他模块的交互关系。
1. 虚拟地址描述符(VAD)
1.1 定义
VAD(Virtual Address Descriptor)是描述进程虚拟地址空间中一个连续区域的 AVL 树节点。每个 VAD 记录了一段虚拟地址范围的起始/结束页号、保护属性、后备存储(文件/Section)等信息。所有 VAD 按地址排序组织成 AVL 树,挂载在 EPROCESS.VadRoot。
1.2 三种 VAD 类型
MMVAD_SHORT --- 最简单的 VAD,用于私有内存(VirtualAlloc 分配):
c
// sdk/include/ndk/mmtypes.h
typedef struct _MMVAD_SHORT {
MMADDRESS_NODE VadNode; // AVL 树节点(含 StartingVpn/EndingVpn)
ULONG_PTR StartingVpn; // 起始虚拟页号
ULONG_PTR EndingVpn; // 结束虚拟页号
union {
ULONG_PTR LongFlags;
MMVAD_FLAGS VadFlags; // VAD 标志(PrivateMemory/NoChange/...)
} u;
} MMVAD_SHORT, *PMMVAD_SHORT;
MMVAD --- 标准 VAD,增加 Section 映射支持:
c
typedef struct _MMVAD {
MMADDRESS_NODE VadNode;
ULONG_PTR StartingVpn;
ULONG_PTR EndingVpn;
union { ULONG_PTR LongFlags; MMVAD_FLAGS VadFlags; } u;
PCONTROL_AREA ControlArea; // Section 控制区
PMMPTE FirstPrototypePte; // 原型 PTE 基址
PMMPTE LastContiguousPte;
union { ULONG LongFlags2; MMVAD_FLAGS2 VadFlags2; } u2;
} MMVAD, *PMMVAD;
MMVAD_LONG --- 完整 VAD,含文件映射的 Subsection 偏移:
c
typedef struct _MMVAD_LONG {
MMVAD Core; // 继承 MMVAD
ULONG_PTR FirstPrototypePte;
ULONG_PTR LastContiguousPte;
union {
ULONG LongFlags2;
MMVAD_FLAGS2 VadFlags2;
} u2;
PSUBSECTION Subsection; // 子段指针(文件映像特有)
} MMVAD_LONG, *PMMVAD_LONG;
1.3 关键标志(MMVAD_FLAGS)
c
// sdk/include/ndk/mmtypes.h
typedef struct _MMVAD_FLAGS {
ULONG Lock:1; // 锁定(不可换出)
ULONG Locked:1;
ULONG PrivateMemory:1; // 私有内存(非 Section 映射)
ULONG NoChange:1; // 禁止改变保护属性
ULONG ImageMap:1; // 映像文件映射
ULONG PhysicalMapping:1; // 物理内存映射
ULONG LargePages:1; // 大页面
ULONG DeleteInProgress:1;
ULONG PageFile:3; // 页面文件编号
ULONG Protection:5; // 保护掩码(MM_* 常量)
ULONG CopyOnWrite:1;
// ...
};
1.4 VAD 树操作
c
// ntoskrnl/mm/ARM3/vadnode.c
// 查找 --- O(log N),hint 加速
PMMVAD MiLocateVad(PMM_AVL_TABLE Table, PVOID Address)
{
// 1. 检查 NodeHint(最后访问的 VAD)
if (Table->NodeHint && VPN in [hint->Start, hint->End])
return hint;
// 2. AVL 树搜索
return RtlpFindAvlTableNodeOrParent(Table, Address);
}
// 插入 --- O(log N),自动平衡
VOID MiInsertVad(PMMVAD Vad, PMM_AVL_TABLE Table)
{
Table->NodeHint = Vad; // hint 总是指向最后插入的 VAD
RtlpInsertAvlTreeNode(Table, &Vad->VadNode);
}
// 删除 --- O(log N),自动平衡
VOID MiRemoveNode(PMMADDRESS_NODE Node, PMM_AVL_TABLE Table)
{
RtlpDeleteAvlTreeNode(Table, Node);
if (被删除的节点是 NodeHint) 更新 hint;
}
1.5 源文件对应
| 文件 | 功能 |
|---|---|
| vadnode.c(file:///d:/reactos/ntoskrnl/mm/ARM3/vadnode.c) | VAD AVL 树查找/插入/删除、空闲地址范围搜索 |
| miarm.h(file:///d:/reactos/ntoskrnl/mm/ARM3/miarm.h) | MM_AVL_TABLE、MMADDRESS_NODE 类型、VAD 相关宏 |
| mmtypes.h(file:///d:/reactos/sdk/include/ndk/mmtypes.h) | MMVAD/MMVAD_SHORT/MMVAD_LONG 结构体定义 |
| miavl.h(file:///d:/reactos/ntoskrnl/mm/ARM3/miavl.h) | AVL 树内联算法(父指针/平衡因子共用字段优化) |
2. 页帧号数据库(PFN Database)
2.1 定义
PFN Database(Page Frame Number Database)是系统中每个物理页面的元数据数组。系统中有 NumberOfPhysicalPages 个物理页,就有 NumberOfPhysicalPages 个连续的 MMPFN 条目。PFN 数据库本身位于非分页池中,在系统启动的 Phase0 构建。
2.2 MMPFN 结构
c
// ntoskrnl/include/internal/mm.h:389
typedef struct _MMPFN
{
union {
PFN_NUMBER Flink; // 链表前向指针
ULONG WsIndex; // 工作集索引
PKEVENT Event; // I/O 完成事件(页面读取时使用)
NTSTATUS ReadStatus; // 页面读取状态
SINGLE_LIST_ENTRY NextStackPfn; // 内核栈 PFN 链表
SWAPENTRY SwapEntry; // 交换条目编码
} u1;
PMMPTE PteAddress; // 指向映射此页的 PTE
union {
PFN_NUMBER Blink; // 链表后向指针
ULONG_PTR ShareCount; // 共享计数
} u2;
union {
struct {
USHORT ReferenceCount; // 引用计数
MMPFNENTRY e1; // PFN 标志
};
struct { USHORT ReferenceCount; USHORT ShortFlags; } e2;
} u3;
union {
MMPTE OriginalPte; // 原始 PTE(页面被换出时保存的内存)
LONG AweReferenceCount; // AWE 引用计数
PMM_RMAP_ENTRY RmapListHead; // 反向映射链表
};
union {
ULONG_PTR EntireFrame;
struct {
ULONG_PTR PteFrame:MI_PTE_FRAME_BITS; // 页表的 PFN
ULONG_PTR InPageError:1;
ULONG_PTR VerifierAllocation:1;
ULONG_PTR AweAllocation:1;
ULONG_PTR Priority:3;
ULONG_PTR MustBeCached:1;
};
} u4;
} MMPFN;
2.3 PFN 状态位(MMPFNENTRY)
c
typedef struct _MMPFNENTRY
{
USHORT Modified:1; // 页面已被修改
USHORT ReadInProgress:1; // 页读取 I/O 进行中
USHORT WriteInProgress:1; // 页写入 I/O 进行中
USHORT PrototypePte:1; // 原型 PTE 引用
USHORT PageColor:4; // 缓存颜色(Cache Color)
USHORT PageLocation:3; // 页面所在列表(8种)
USHORT RemovalRequested:1;
USHORT CacheAttribute:2; // 缓存属性
USHORT Rom:1; // ROM 页面
USHORT ParityError:1; // 奇偶校验错误
} MMPFNENTRY;
2.4 页面列表(PageLocation)
c
typedef enum _MMLISTS {
ZeroedPageList = 0, // 已清零页面
FreePageList = 1, // 空闲页面(未清零)
StandbyPageList = 2, // 备用页面(内容有效,但未被引用)
ModifiedPageList = 3, // 已修改页面(需写回磁盘)
ModifiedNoWritePageList = 4, // 已修改但不写回
BadPageList = 5, // 坏页
ActiveAndValid = 6, // 活跃页面(被一个或多个进程使用)
TransitionPage = 7 // 转换中
} MMLISTS;
2.5 页面状态转换
Free (空闲) ──分配──→ Active (活跃)
Active ──解除映射─→ Transition (转换中)
↓
引用计数归零
↓
┌─────────┴─────────┐
│ Modified=1 │ Modified=0
▼ ▼
ModifiedPageList StandbyPageList
│ ├──清零线程──→ ZeroedPageList
│ 写回线程 │ │
▼ ↓ ↓
StandbyPageList Free Free
2.6 核心操作
c
// ntoskrnl/mm/ARM3/pfnlist.c
// 按颜色分配任意页面(优先空闲页,再零页)
PFN_NUMBER MiRemoveAnyPage(ULONG Color)
{
// 1. 优先颜色空闲链表 → 2. 颜色零链表
// 3. 全局空闲链表 → 4. 全局零链表
// 返回 PFN 并更新 MmAvailablePages
}
// 按颜色分配零页(优先零页,再空闲页+手动清零)
PFN_NUMBER MiRemoveZeroPage(ULONG Color)
{
// 1. 颜色零链表 → 2. 全局零链表
// 3. 颜色空闲链表(手动清零)→ 4. 全局空闲链表(手动清零)
}
// 将页面插入空闲链表(维护颜色链表)
VOID MiInsertPageInFreeList(PFN_NUMBER PageFrameIndex);
// 将页面插入指定链表
VOID MiInsertPageInList(PMMPFNLIST ListHead, PFN_NUMBER PageFrameIndex);
// 初始化 PFN 条目
VOID MiInitializePfn(PFN_NUMBER Page, PMMPTE PointerPte, BOOLEAN Modified);
2.7 源文件对应
| 文件 | 功能 |
|---|---|
| pfnlist.c(file:///d:/reactos/ntoskrnl/mm/ARM3/pfnlist.c) | PFN 列表操作:分配/释放/初始化/引用计数管理 |
| freelist.c(file:///d:/reactos/ntoskrnl/mm/freelist.c) | 全局统计:MmAvailablePages、MmPfnDatabase、LRU 链表 |
| internal/mm.h(file:///d:/reactos/ntoskrnl/include/internal/mm.h) | MMPFN、MMPFNLIST、MMPFNENTRY 结构定义 |
3. 工作集(Working Set)
3.1 定义
工作集是进程当前驻留在物理内存中的页面集合。操作系统为每个进程维护一个工作集列表(Working Set List, WSL),记录哪些物理页属于该进程。工作集管理器(Working Set Manager)在内存压力下修剪各个进程的工作集。
3.2 工作集列表结构
工作集列表是一个 MMWSLE(Working Set List Entry)数组:
c
typedef struct _MMWSLE {
ULONG_PTR VirtualAddress; // 虚拟地址(含标志位)
// 编码:低12位为标志(原型PTE位、锁定位等)
// 高位为虚拟地址的页号部分
union { ULONG_PTR Long; } u;
} MMWSLE, *PMMWSLE;
3.3 核心操作
c
// ntoskrnl/mm/ARM3/wslist.cpp(唯一C++文件)
// 插入页面到工作集列表
VOID MiInsertInWorkingSetList(PMMWSLE WsList, PMMPTE Pte,
PVOID VirtualAddress, PMMPTE PointerPte,
ULONG_PTR PteLong)
{
// 获取空闲槽位索引(必要时增长数组)
ULONG Index = GetFreeWsleIndex(WsList);
// 写入 WSLE 条目
WsList->Wsles[Index].u.Long = (ULONG_PTR)VirtualAddress | Flags;
WsList->LastInitializedWsle = max(WsList->LastInitializedWsle, Index);
}
// 从工作集列表移除
VOID MiRemoveFromWorkingSetList(PMMWSLE WsList, PMMPTE PointerPte,
PVOID VirtualAddress);
// 工作集管理器(内核线程主循环)
BOOLEAN MmWorkingSetManager(VOID)
{
// 响应页面错误和高优先级事件
// 遍历所有进程的工作集,修剪超额页面
}
3.4 内存平衡器
c
// ntoskrnl/mm/balance.c
// 平衡器线程主循环
MiBalancerThread()
{
while (TRUE) {
KeWaitForSingleObject(MiBalancerEvent); // 等待触发
for (每个内存消费者 c)
MiTrimMemoryConsumer(c); // 修剪内存
}
}
// 修剪单消费者:高优先级→页面换出;低优先级→仅清除 Accessed 位
MiTrimMemoryConsumer(Consumer)
{
Consumer->TrimCallback(); // 调用消费者注册的回调
}
// 用户内存修剪:遍历 LRU 页面链表
MmTrimUserMemory(VOID)
{
for (LRU 链表中的每个页面) {
if (高优先级) MmPageOutPhysicalAddress(); // 换出到分页文件
else 清除 PTE_ACCESSED; // 仅清除访问位
}
}
3.5 源文件对应
| 文件 | 功能 |
|---|---|
| wslist.cpp(file:///d:/reactos/ntoskrnl/mm/ARM3/wslist.cpp) | 工作集列表插入/移除/初始化、MmWorkingSetManager(唯一 C++ 文件) |
| balance.c(file:///d:/reactos/ntoskrnl/mm/balance.c) | MiBalancerThread、MmTrimUserMemory、内存消费者注册/修剪 |
4. 段对象(Section)
4.1 定义
Section 对象是 Windows 中实现内存映射文件 的核心抽象。它将一个文件(或分页文件支持的匿名内存)映射到进程的虚拟地址空间。Section 对象由 NtCreateSection 创建,由 NtMapViewOfSection 映射到进程空间。
4.2 三层数据结构
SECTION 对象
│
├── CONTROL_AREA(控制区)
│ ├── Segment → 指向 SEGMENT
│ ├── FilePointer → 文件对象指针(NULL=分页文件映射)
│ ├── Flags.File/Image/Rom
│ ├── NumberOfMappedViews
│ └── NumberOfSectionReferences
│
├── SUBSECTION(子段)
│ ├── SubsectionBase → 原型 PTE 数组基址
│ ├── PtesInSubsection → 本子段的 PTE 数量
│ ├── StartingSector → 文件中的起始扇区
│ └── NextSubsection → 下一个子段
│
└── SEGMENT(段)
├── PrototypePte → 原型 PTE 数组
├── TotalNumberOfPtes
├── SegmentPteTemplate
└── SizeOfSegment
4.3 三种映射类型
| 类型 | FilePointer | 后备存储 | 页面错误来源 |
|---|---|---|---|
| 数据文件映射 | 非 NULL | 磁盘文件 | IoPageRead 从文件读回 |
| 分页文件映射 | NULL | 分页文件(pagefile.sys) |
MiResolvePageFileFault 从分页文件读回 |
| 映像映射 | 非 NULL | PE 可执行文件 | 类似数据文件,但含映像特有元数据 |
4.4 核心操作
c
// ntoskrnl/mm/ARM3/section.c
// 创建 Section 对象
NTSTATUS NtCreateSection(PHANDLE SectionHandle, ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PLARGE_INTEGER MaximumSize, ULONG SectionPageProtection,
ULONG AllocationAttributes, HANDLE FileHandle)
{
// 1. 参数校验(SEC_COMMIT/SEC_RESERVE/SEC_IMAGE)
// 2. 获取/创建文件对象
// 3. 调用 MmCreateArm3Section 创建 CONTROL_AREA+SEGMENT+SUBSECTION
}
// 映射 Section 视图到进程地址空间
NTSTATUS NtMapViewOfSection(HANDLE SectionHandle, HANDLE ProcessHandle,
PVOID *BaseAddress, ULONG_PTR ZeroBits,
SIZE_T CommitSize, PLARGE_INTEGER SectionOffset,
PSIZE_T ViewSize, SECTION_INHERIT InheritDisposition,
ULONG AllocationType, ULONG Win32Protect)
{
// 1. 参数校验(对齐、权限)
// 2. 在目标进程中创建 VAD(指向 Section 的原型 PTE)
// 3. 更新 CONTROL_AREA 引用计数
}
4.5 源文件对应
| 文件 | 功能 |
|---|---|
| section.c(ARM3)(file:///d:/reactos/ntoskrnl/mm/ARM3/section.c) | NtCreateSection、NtMapViewOfSection、MmCreateArm3Section、MiSegmentDelete(~3500行) |
| section.c(旧版)(file:///d:/reactos/ntoskrnl/mm/section.c) | 旧版 Section 实现(部分仍使用) |
| mmtypes.h(file:///d:/reactos/sdk/include/ndk/mmtypes.h) | CONTROL_AREA、SEGMENT、SUBSECTION 结构定义 |
5. 池(Pool)
5.1 定义
池(Pool)是 Windows 内核的堆分配器,提供两种主要类型:
- 非分页池(NonPaged Pool):物理内存常驻、永远不会被换出,用于中断处理程序和 DMA 缓冲区
- 分页池(Paged Pool):可以换出到磁盘,用于大部分内核分配
5.2 POOL_DESCRIPTOR 结构
c
// ntoskrnl/mm/ARM3/miarm.h
typedef struct _POOL_DESCRIPTOR
{
POOL_TYPE PoolType; // 分页池 / 非分页池
ULONG PoolIndex;
ULONG RunningAllocs; // 累计分配次数
ULONG RunningDeAllocs; // 累计释放次数
ULONG TotalPages; // 总页数
ULONG TotalBigPages; // 大页面分配数
ULONG Threshold; // 大页面阈值
PVOID LockAddress;
PVOID PendingFrees; // 待释放链表
LONG PendingFreeDepth;
SIZE_T TotalBytes; // 总字节数
LIST_ENTRY ListHeads[POOL_LISTS_PER_PAGE]; // 空闲块列表(256个桶)
} POOL_DESCRIPTOR, *PPOOL_DESCRIPTOR;
关键设计:
ListHeads[256]:256 个空闲链表,按块大小(8/16 字节粒度)组织- 分配时从对应大小桶开始查找,未找到则向下搜索更大的桶
Threshold:超过此大小的分配走"大页"路径(分配整页)
5.3 POOL_HEADER 结构
每个分配块前有一个 8 字节头部:
c
// 32位: PreviousSize:9 | PoolIndex:7 | BlockSize:9 | PoolType:7
// 64位: PreviousSize:8 | PoolIndex:8 | BlockSize:8 | PoolType:8
PreviousSize: 前一个块大小(以 POOL_BLOCK_SIZE=16 为单位)
PoolIndex: 所属池索引
BlockSize: 当前块大小
PoolType: 池类型(Free=0, Allocated=奇数)
5.4 分配路径
ExAllocatePoolWithTag(PoolType, Size, Tag)
│
├─ 特殊池检查(Driver Verifier 模式)
│
├─ 大分配(> POOL_MAX_ALLOC ≈ 4080 字节)
│ └─ MiAllocatePoolPages() → 分配整页
│
└─ 小/中分配
├─ Lookaside 缓存快速路径(per-CPU SList)
├─ 空闲列表遍历(对应大小→向上搜索)
├─ 碎片分割(大块切分为小块)
└─ 写入 POOL_HEADER 和标签跟踪
5.5 释放路径
ExFreePoolWithTag(Ptr, Tag)
│
├─ 特殊池检查
├─ 大分配 → MiFreePoolPages() 释放整页
└─ 普通释放 → 前后向合并空闲块以减少碎片
→ 插入对应大小的空闲链表
5.6 标签跟踪系统
每个分配带有一个 4 字节标签(如 'ldaV' 表示 VAD 分配),存储在哈希表 PoolTrackTable 中:
c
// expool.c
ExpInsertPoolTracker(Tag, Allocs, Bytes); // 分配时增加计数
ExpRemovePoolTracker(Tag, Allocs, Bytes); // 释放时减少计数
ExGetPoolTagInfo() → 返回所有标签的统计(给 NtQuerySystemInformation)
初始化时预填充 64 个常用标签(ExpSeedHotTags),如 ' oI'(Io)、'PldM'(Mdlp)、'LooP'(Pool)等。
5.7 源文件对应
| 文件 | 功能 |
|---|---|
| expool.c(file:///d:/reactos/ntoskrnl/mm/ARM3/expool.c) | ExAllocatePoolWithTag/ExFreePoolWithTag、标签跟踪、热标签缓存、配额管理 |
| pool.c(file:///d:/reactos/ntoskrnl/mm/ARM3/pool.c) | MiAllocatePoolPages/MiFreePoolPages、非分页池初始化、池页面保护 |
| special.c(file:///d:/reactos/ntoskrnl/mm/ARM3/special.c) | Driver Verifier 特殊池(填充模式检测) |
| ncache.c(file:///d:/reactos/ntoskrnl/mm/ARM3/ncache.c) | MmAllocateNonCachedMemory/MmFreeNonCachedMemory |
6. 系统 PTE(System PTE)
6.1 定义
系统 PTE 是内核虚拟地址空间中预留的页表项池,用于内核内部的动态映射。系统 PTE 由 MiReserveSystemPtes 分配,由 MiReleaseSystemPtes 释放。它们被用于:
- 池分配器的页面映射(分页池/非分页池)
- I/O 空间映射(
MmMapIoSpace) - 超空间临时映射
- 大页面分配
- 其他需要临时虚拟地址的内核操作
6.2 PTE 池类型
c
typedef enum _MI_PTE_POOL_TYPE {
LowestPoolType = 0,
PagedPoolPte = 0, // 分页池 PTE
NonPagedPoolPte = 1, // 非分页池 PTE
SessionPte = 2, // 会话空间 PTE
SystemPte = 3, // 系统 PTE(通用)
MaximumPtePoolTypes = 4
} MI_PTE_POOL_TYPE;
6.3 全局范围
c
// ntoskrnl/mm/ARM3/miarm.h 中声明
extern PMMPTE MmSystemPtesStart[MaximumPtePoolTypes]; // 各类型池的起始
extern PMMPTE MmSystemPtesEnd[MaximumPtePoolTypes]; // 各类型池的结束
extern PMMPTE MmFirstReservedMappingPte, MmLastReservedMappingPte;
extern PMMPTE MiFirstReservedZeroingPte;
6.4 分配算法
c
// ntoskrnl/mm/ARM3/syspte.c
// 预留系统 PTE
PMMPTE MiReserveSystemPtes(ULONG NumberOfPtes, MI_PTE_POOL_TYPE PoolType)
{
// 1. 在对应池类型的位图中查找空闲连续 PTE 块
// 2. 标记为已分配
// 3. 更新 MmTotalFreeSystemPtes[PoolType]
// 4. 返回起始 PTE 地址
}
// 释放系统 PTE
VOID MiReleaseSystemPtes(PMMPTE StartingPte, ULONG NumberOfPtes,
MI_PTE_POOL_TYPE PoolType)
{
// 1. 清零 PTE
// 2. 在位图中标记为空闲
// 3. 更新空闲计数
}
6.5 源文件对应
| 文件 | 功能 |
|---|---|
| syspte.c(file:///d:/reactos/ntoskrnl/mm/ARM3/syspte.c) | 系统 PTE 分配/释放/初始化、对齐分配 |
7. 超空间(Hyperspace)
7.1 定义
超空间(Hyperspace)是内核虚拟地址空间中的一个预留区域,用于临时映射任意物理页面。内存管理器使用超空间来:
- 访问和初始化新分配的物理页(清空、写入 PFN 数据)
- 临时访问其他进程的页表
- 执行跨进程的内存操作
7.2 核心操作
c
// ntoskrnl/mm/ARM3/hypermap.c
// 在超空间中映射一个物理页
PMMPTE MiMapPageInHyperSpace(PFN_NUMBER PageFrameIndex)
{
// 1. 预留一个超空间 PTE
// 2. 写入硬件 PTE(PageFrameIndex → 虚拟地址)
// 3. 刷新 TLB
// 4. 返回虚拟地址
}
// 取消超空间映射
VOID MiUnmapPageInHyperSpace(PMMPTE PointerPte)
{
// 1. 清零 PTE
// 2. 刷新 TLB
// 3. 释放超空间 PTE
}
// 在 ZeroSpace 中映射页面
PVOID MiMapPagesInZeroSpace(PFN_NUMBER PageFrameIndex, ULONG NumberOfPages,
BOOLEAN UseLargePages);
VOID MiUnmapPagesInZeroSpace(PVOID Address, ULONG NumberOfPages);
7.3 超空间 PDE 位置
x86 架构下,超空间 PDE 位于页目录的特定位置:
PD[1022] → 超空间 PDE(用于临时物理页映射)
PD[1023] → 页目录自映射(PTE_BASE)
x86-64 下超空间通过 PML4 的特定条目实现。
7.4 源文件对应
| 文件 | 功能 |
|---|---|
| hypermap.c(file:///d:/reactos/ntoskrnl/mm/ARM3/hypermap.c) | MiMapPageInHyperSpace/MiUnmapPageInHyperSpace、MiMapPagesInZeroSpace |
| miarm.h(file:///d:/reactos/ntoskrnl/mm/ARM3/miarm.h) | MmHyperSpaceEnd、PTE_BASE、PDE_BASE |
8. MDL(Memory Descriptor List)
8.1 定义
MDL(Memory Descriptor List)是描述一组物理页缓冲区的标准内核数据结构。它主要用于:
- DMA 操作:驱动程序将用户缓冲区锁定,获取物理页列表供 DMA
- 跨进程内存访问:将用户缓冲区映射到内核空间
- I/O 操作:为文件系统缓存提供物理页描述
8.2 MDL 结构
c
typedef struct _MDL {
struct _MDL *Next; // 链表
CSHORT Size; // MDL 总大小
CSHORT MdlFlags; // 标志(已锁定/已映射/等)
struct _EPROCESS *Process; // 所属进程
PVOID MappedSystemVa; // 系统空间映射地址
PVOID StartVa; // 起始虚拟地址(页对齐)
ULONG ByteCount; // 字节数
ULONG ByteOffset; // 页内偏移
// PFN 数组紧跟结构体(变长)
PFN_NUMBER Pfns[1]; // 物理页帧号数组
} MDL, *PMDL;
8.3 核心操作
c
// ntoskrnl/mm/ARM3/mdlsup.c
// 创建 MDL
PMDL MmCreateMdl(PVOID Base, SIZE_T Length);
// 计算 MDL 所需内存大小(含 PFN 数组空间)
USHORT MmSizeOfMdl(PVOID Base, SIZE_T Length);
// 探测并锁定用户页面(页面被锁定在物理内存中,防止换出)
PMDL MmProbeAndLockPages(PMDL Mdl, KPROCESSOR_MODE AccessMode,
LOCK_OPERATION Operation)
{
// 1. 遍历用户虚拟地址范围内的每一页
// 2. MiRemoveAnyPage() 确保页面在内存中
// 3. 递增 PFN 引用计数(锁定)
// 4. 填充 MDL 的 PFN 数组
// 5. 设置 MDL_MDL_SOURCE_IS_NONPAGED_POOL 等标志
}
// 以指定缓存属性映射锁定页到系统地址空间
PVOID MmMapLockedPagesSpecifyCache(PMDL Mdl, KPROCESSOR_MODE AccessMode,
CACHE_TYPE CacheType, PVOID BaseAddress,
ULONG BugCheckOnFailure, ULONG Priority)
{
// 1. 预留系统 PTE
// 2. 为每个 PFN 写入硬件 PTE(考虑缓存属性:NonCached/Cached/WriteCombined)
// 3. 返回内核虚拟地址
}
// 分配物理页并构建 MDL(支持地址范围约束)
PMDL MmAllocatePagesForMdl(PHYSICAL_ADDRESS LowAddress,
PHYSICAL_ADDRESS HighAddress,
PHYSICAL_ADDRESS SkipBytes, SIZE_T TotalBytes);
// 释放 MDL 中的物理页
VOID MmFreePagesFromMdl(PMDL Mdl);
// 解除页面锁定
VOID MmUnlockPages(PMDL Mdl);
// 解除系统空间映射
VOID MmUnmapLockedPages(PVOID MappedAddress, PMDL Mdl);
8.4 使用示例
驱动程序接收用户缓冲区指针 → MmProbeAndLockPages(Mdl, UserMode, IoWrite)
→ 页面被锁定在物理内存中
→ Mdl->Pfns[] 包含所有物理页帧号
→ MmMapLockedPagesSpecifyCache(Mdl, KernelMode, Cached)
→ 驱动程序通过系统虚拟地址访问
→ 操作完成后:MmUnmapLockedPages → MmUnlockPages
8.5 源文件对应
| 文件 | 功能 |
|---|---|
| mdlsup.c(file:///d:/reactos/ntoskrnl/mm/ARM3/mdlsup.c) | MmProbeAndLockPages、MmMapLockedPagesSpecifyCache、MmAllocatePagesForMdl、MmSizeOfMdl 等 22 个函数 |
| wdm.h(file:///d:/reactos/sdk/include/ddk/wdm.h) | MDL 结构体定义、MdlFlags 枚举 |
9. 概念关系图
进程虚拟地址空间
│
┌──────────┴──────────┐
▼ ▼
┌──────────┐ ┌──────────────┐
│ VAD │◄─────────│ Section │
│ (AVL树) │ 映射到 │ (文件/匿名) │
└────┬─────┘ └──────┬───────┘
│ │
│ 页表(PTE) │ 原型 PTE
▼ ▼
┌──────────────────────────────────────┐
│ PFN Database (MMPFN) │
│ (每个物理页一个条目) │
└────────┬─────────┬──────────┬─────────┘
│ │ │
┌────────┴┐ ┌────┴────┐ ┌──┴────────┐
│ 工作集 │ │页面列表 │ │ 反向映射 │
│ (WSL) │ │(8种状态) │ │ (RMAP) │
└──────────┘ └─────────┘ └───────────┘
内存分配器
┌──────────────────────────────┐
│ 池分配器 (Pool) │
│ ├─ 非分页池 (NonPaged Pool) │
│ ├─ 分页池 (Paged Pool) │
│ └─ 特殊池 (Special Pool) │
└──────────┬───────────────────┘
│
▼
┌──────────────────────────────┐
│ 系统 PTE + 超空间 │
│ (临时映射机制) │
└──────────────────────────────┘
10. 源文件汇总
| 概念 | ARM3 主文件 | 旧版文件 | 头文件 |
|---|---|---|---|
| VAD | vadnode.c、virtual.c |
marea.c |
mmtypes.h、miarm.h、miavl.h |
| PFN Database | pfnlist.c |
freelist.c |
internal/mm.h |
| 工作集 | wslist.cpp |
balance.c |
--- |
| Section | section.c |
section.c |
mmtypes.h |
| Pool | expool.c、pool.c |
--- | miarm.h |
| 系统 PTE | syspte.c |
--- | miarm.h |
| 超空间 | hypermap.c |
--- | miarm.h |
| MDL | mdlsup.c |
--- | wdm.h |
推荐阅读 :Windows Internals, 7th Edition Part 1 Chapter 9 "Memory Management"。