Windows 内存管理概念详解

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_TABLEMMADDRESS_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) 全局统计:MmAvailablePagesMmPfnDatabase、LRU 链表
internal/mm.h(file:///d:/reactos/ntoskrnl/include/internal/mm.h) MMPFNMMPFNLISTMMPFNENTRY 结构定义

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) MiBalancerThreadMmTrimUserMemory、内存消费者注册/修剪

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) NtCreateSectionNtMapViewOfSectionMmCreateArm3SectionMiSegmentDelete(~3500行)
section.c(旧版)(file:///d:/reactos/ntoskrnl/mm/section.c) 旧版 Section 实现(部分仍使用)
mmtypes.h(file:///d:/reactos/sdk/include/ndk/mmtypes.h) CONTROL_AREASEGMENTSUBSECTION 结构定义

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/MiUnmapPageInHyperSpaceMiMapPagesInZeroSpace
miarm.h(file:///d:/reactos/ntoskrnl/mm/ARM3/miarm.h) MmHyperSpaceEndPTE_BASEPDE_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) MmProbeAndLockPagesMmMapLockedPagesSpecifyCacheMmAllocatePagesForMdlMmSizeOfMdl 等 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.cvirtual.c marea.c mmtypes.hmiarm.hmiavl.h
PFN Database pfnlist.c freelist.c internal/mm.h
工作集 wslist.cpp balance.c ---
Section section.c section.c mmtypes.h
Pool expool.cpool.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"。

相关推荐
caimouse14 天前
TCP/IP 协议驱动 (tcpip.sys) 分析
网络协议·tcp/ip·reactos
caimouse17 天前
MM 学习笔记 09:进程会话与工作集
笔记·学习·reactos
caimouse17 天前
MM 学习笔记 10:页面文件与系统加载器
笔记·学习·reactos
caimouse17 天前
WINE IE 与 wine_gecko 架构分析
架构·reactos
caimouse21 天前
Chrome V49 (49.0.2623.112) 在 ReactOS 上运行所需的系统依赖
chrome·reactos
caimouse21 天前
MM 学习笔记 01:miarm.h 核心头文件分析
笔记·学习·reactos
caimouse21 天前
Chrome V49 SxS 加载问题分析与修复
chrome·reactos
caimouse22 天前
mm学习笔记_04:VAD树算法与地址空间分配
笔记·学习·算法·reactos
caimouse22 天前
WINLOGON 初始化与作用分析
reactos