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"。

相关推荐
caimouse20 小时前
MM 学习笔记 01:miarm.h 核心头文件分析
笔记·学习·reactos
caimouse1 天前
Chrome V49 SxS 加载问题分析与修复
chrome·reactos
caimouse1 天前
mm学习笔记_04:VAD树算法与地址空间分配
笔记·学习·算法·reactos
caimouse2 天前
WINLOGON 初始化与作用分析
reactos
caimouse2 天前
KPRCB 结构分析与调度流程
reactos
caimouse2 天前
ReactOS.exe 安装程序分析
reactos
caimouse3 天前
Phase1Initialization 函数分析
单片机·嵌入式硬件·reactos
caimouse5 天前
ReactOS bootcd.iso 引导到桌面显示过程分析
reactos
caimouse5 天前
ReactOS BootCD 光盘布局完整深度分析
单片机·reactos