mm学习笔记_04:VAD树算法与地址空间分配

mm学习笔记_04:VAD树算法与地址空间分配

基于 ReactOS ARM3 MM 源码(ntoskrnl/mm/ARM3/vadnode.c)


1. vadnode.c 概述

文件位置d:\reactos\ntoskrnl\mm\ARM3\vadnode.c

职责:实现 VAD(Virtual Address Descriptor,虚拟地址描述符)的 AVL 树算法,包括:

  • VAD 节点的查找、插入、删除
  • 空闲地址范围的搜索(Bottom-Up 和 Top-Down)
  • 基于地址的 Section 管理

核心数据结构 ------MM_AVL_TABLE

c 复制代码
typedef struct _MM_AVL_TABLE {
    MMADDRESS_NODE BalancedRoot;  // AVL 树根节点(内含右孩子为真正的根)
    ULONG_PTR NumberGenericTableElements; // 树中节点数
    PVOID NodeHint;               // 节点查找优化提示(最近访问的 VAD)
    ULONG DepthOfTree;            // 树深度
    UCHAR Unused;                 // 用于区分用户VAD树和内核内存区域
} MM_AVL_TABLE;

重要 :每个进程的 EPROCESS 中包含一个 VadRoot(类型为 MM_AVL_TABLE),即该进程所有 VAD 的 AVL 树。


2. 三种 VAD 结构

2.1 MMVAD_SHORT(32 字节)

c 复制代码
typedef struct _MMVAD_SHORT {
    MMADDRESS_NODE VadNode;       // AVL 树节点(含 StartingVpn, EndingVpn, 左右子指针, 父指针, 平衡因子)
    ULONG_PTR NumberGenericTableElements; // 已映射的页数
    MMVAD_FLAGS VadFlags;         // 16 位标志(PrivateMemory, MemCommit, Protection, VadType 等)
    MMVAD_FLAGS2 VadFlags2;       // 更多标志(LongVad, OneSecured 等)
    PMMADDRESS_NODE* NextVad;     // 未使用
} MMVAD_SHORT;

2.2 MMVAD(36 字节)

MMVAD_SHORT 基础上增加:

c 复制代码
typedef struct _MMVAD {
    MMVAD_SHORT Core;
    union {
        PVOID Pointer;
        PVOID ControlArea;        // 控制区指针(Section 映射时使用)
    } u2;
    PMMPTE FirstPrototypePte;     // 第一个原型 PTE
    PMMPTE LastContiguousPte;     // 最后一个连续 PTE
    PVOID DeleteStack;            // 调试用
} MMVAD;

2.3 MMVAD_LONG(44+ 字节)

MMVAD 基础上增加:

c 复制代码
typedef struct _MMVAD_LONG {
    MMVAD Core;
    union {
        struct {
            ULONG_PTR StartVpn;   // 安全区域的起始 VPN
            ULONG_PTR EndVpn;     // 安全区域的结束 VPN
        } Secured;
        ULONG_PTR LongFlags;
    } u3;
} MMVAD_LONG;

三者关系

复制代码
MMVAD_SHORT(32B):基础 VAD,用于普通私有内存
    │
    ▼
MMVAD(36B):增加 ControlArea 和 FirstPrototypePte,用于 Section 映射
    │
    ▼
MMVAD_LONG(44B+):增加 Secured 区域,用于 TEB/PEB 等安全 VAD

判断宏

c 复制代码
// 是否为长 VAD
Vad->u2.VadFlags2.LongVad

3. MiLocateVad------查找算法(hint 优化)

文件:vadnode.c:114

功能:在 VAD AVL 树中查找包含指定虚拟地址的 VAD 节点。

查找流程

  1. Hint 优化 :先从 Table->NodeHint 检查,如果目标地址落在 hint VAD 范围内直接返回(O(1) 最速路径)
  2. AVL 树搜索 :调用 RtlpFindAvlTableNodeOrParent 在 AVL 树中查找
  3. 更新 Hint:找到后将结果设为新的 hint,加速后续查找
c 复制代码
PMMVAD NTAPI MiLocateVad(IN PMM_AVL_TABLE Table, IN PVOID VirtualAddress)
{
    // Step 1: 尝试 hint
    FoundVad = (PMMVAD)Table->NodeHint;
    if (FoundVad) {
        Vpn = (ULONG_PTR)VirtualAddress >> PAGE_SHIFT;
        if ((Vpn >= FoundVad->StartingVpn) && (Vpn <= FoundVad->EndingVpn))
            return FoundVad;   // Hint 命中!
    }

    // Step 2: AVL 树搜索
    SearchResult = RtlpFindAvlTableNodeOrParent(Table, (PVOID)Vpn, &FoundVad);
    if (SearchResult != TableFoundNode) return NULL;

    // Step 3: 更新 hint(无锁更新,hint 只读不保证一致性)
    Table->NodeHint = FoundVad;
    return FoundVad;
}

配套函数

c 复制代码
// 直接在当前进程中查找
PMMVAD NTAPI MiLocateAddress(IN PVOID VirtualAddress)
{
    return MiLocateVad(&PsGetCurrentProcess()->VadRoot, VirtualAddress);
}

MiCheckForConflictingNodevadnode.c:155):

  • 遍历 AVL 树检查指定区间 [StartVpn, EndVpn] 是否与现有 VAD 冲突
  • 返回 TableFoundNode(冲突)/ TableInsertAsLeft(插入左侧)/ TableInsertAsRight(插入右侧)

4. MiInsertVad / MiRemoveNode------插入与删除

4.1 MiInsertVad

文件:vadnode.c:222

c 复制代码
VOID NTAPI MiInsertVad(IN PMMVAD Vad, IN PMM_AVL_TABLE VadRoot)
{
    // 1. 设为新的 hint
    VadRoot->NodeHint = Vad;

    // 2. 查找插入位置(确保不冲突)
    Result = RtlpFindAvlTableNodeOrParent(VadRoot, (PVOID)Vad->StartingVpn, &Parent);
    ASSERT(Result != TableFoundNode);  // 不应冲突

    // 3. AVL 树插入
    MiInsertNode(VadRoot, (PVOID)Vad, Parent, Result);
}

4.2 MiInsertVadEx------增强版插入

文件:vadnode.c:245

这是 NtAllocateVirtualMemory 调用的主要插入函数,支持地址自动选择:

流程

  1. 如果 *BaseAddress == 0(未指定基址):
    • 根据 MEM_TOP_DOWNVmTopDown 决定搜索方向
    • MiFindEmptyAddressRangeInTree(Bottom-Up)或 MiFindEmptyAddressRangeDownTree(Top-Down)
  2. 如果指定了基址:
    • MiCheckForConflictingNode 验证地址是否可用
  3. 设置 VAD 的 StartingVpnEndingVpn
  4. 如果 PrivateMemory && MemCommit!PrivateMemory && PAGE_WRITECOPY,设置 CommitCharge
  5. 锁定工作集 → 插入 AVL 树 → 解锁
  6. 更新进程的 VirtualSizePeakVirtualSize
c 复制代码
NTSTATUS NTAPI MiInsertVadEx(IN OUT PMMVAD Vad, IN ULONG_PTR *BaseAddress, ...)
{
    ViewSize = ALIGN_UP_BY(ViewSize, PAGE_SIZE);

    KeAcquireGuardedMutex(&CurrentProcess->AddressCreationLock);
    if (CurrentProcess->VmDeleted) return STATUS_PROCESS_IS_TERMINATING;

    if (*BaseAddress == 0) {
        // 选择搜索方向
        if ((AllocationType & MEM_TOP_DOWN) || CurrentProcess->VmTopDown)
            Result = MiFindEmptyAddressRangeDownTree(...);
        else
            Result = MiFindEmptyAddressRangeInTree(...);
        // 检查结果
        if (Result == TableFoundNode || EndingAddress > HighestAddress)
            return STATUS_NO_MEMORY;
    } else {
        // 验证给定地址
        Result = MiCheckForConflictingNode(...);
        if (Result == TableFoundNode) return STATUS_CONFLICTING_ADDRESSES;
    }

    Vad->StartingVpn = StartingAddress >> PAGE_SHIFT;
    Vad->EndingVpn = EndingAddress >> PAGE_SHIFT;

    // 插入 AVL 树
    MiLockProcessWorkingSetUnsafe(CurrentProcess, CurrentThread);
    MiInsertNode(&CurrentProcess->VadRoot, (PVOID)Vad, Parent, Result);
    MiUnlockProcessWorkingSetUnsafe(CurrentProcess, CurrentThread);

    CurrentProcess->VirtualSize += ViewSize;
    KeReleaseGuardedMutex(&CurrentProcess->AddressCreationLock);
    return STATUS_SUCCESS;
}

4.3 MiRemoveNode

文件:vadnode.c:403

c 复制代码
VOID NTAPI MiRemoveNode(IN PMMADDRESS_NODE Node, IN PMM_AVL_TABLE Table)
{
    // 调用 RTL AVL 树删除
    RtlpDeleteAvlTreeNode(Table, Node);
    Table->NumberGenericTableElements--;

    // 如果被删除的节点是 hint,更新 hint
    if (Table->NodeHint == Node) {
        if (!Table->NumberGenericTableElements)
            Table->NodeHint = NULL;
        else
            Table->NodeHint = Table->BalancedRoot.RightChild;
    }
}

5. MiFindEmptyAddressRangeInTree------Bottom-Up 搜索

文件:vadnode.c:494

功能 :从低地址向高地址搜索,找到第一个足够大的空闲地址范围。用于 MEM_BOTTOM_UP 分配(默认)。

搜索算法

复制代码
1. 计算起始 VPN (LowVpn),从 MM_LOWEST_USER_ADDRESS 开始
2. 如果 AVL 树为空,直接返回低位地址
3. 取最左节点(最小地址的 VAD)
4. 遍历所有 VAD 节点(从小到大):
   a. 检查当前节点起始地址与 LowVpn 之间是否有足够间隙
   b. 如果有间隙且足够大 → 返回该地址
   c. 否则将 LowVpn 移到当前节点结束地址 + 1
   d. 继续下一个节点
5. 如果遍历完所有 VAD 还没有,检查最高地址之前是否有足够空间

地址空间(低位 → 高位):
┌──────┐   ┌──────────┐   ┌──────────┐   ┌──────────┐
│ VAD1 │   │   VAD2   │   │   VAD3   │   │   VAD4   │
└──────┘   └──────────┘   └──────────┘   └──────────┘
     ↑                        ↑
     │                        └─ 找到空闲区域!
     └─ LowVpn 起始搜索位置
c 复制代码
TABLE_SEARCH_RESULT NTAPI MiFindEmptyAddressRangeInTree(
    SIZE_T Length, ULONG_PTR Alignment, PMM_AVL_TABLE Table,
    OUT PMMADDRESS_NODE *PreviousVad, OUT PULONG_PTR Base)
{
    PageCount = BYTES_TO_PAGES(Length);
    AlignmentVpn = Alignment >> PAGE_SHIFT;
    LowVpn = ALIGN_UP_BY(MM_LOWEST_USER_ADDRESS >> PAGE_SHIFT, AlignmentVpn);

    if (Table->NumberGenericTableElements == 0) {
        *Base = LowVpn << PAGE_SHIFT;
        return TableEmptyTree;
    }

    // 取最左节点
    Node = RtlRightChildAvl(&Table->BalancedRoot);
    while (RtlLeftChildAvl(Node)) Node = RtlLeftChildAvl(Node);

    PreviousNode = NULL;
    while (Node) {
        if (Node->StartingVpn >= LowVpn + PageCount) {
            *Base = LowVpn << PAGE_SHIFT;
            // 确定父节点方向
            return (RtlLeftChildAvl(Node) == NULL) ? TableInsertAsLeft : TableInsertAsRight;
        }
        if (Node->EndingVpn >= LowVpn)
            LowVpn = ALIGN_UP_BY(Node->EndingVpn + 1, AlignmentVpn);
        PreviousNode = Node;
        Node = MiGetNextNode(Node);
    }

    // 检查最高地址之上是否有空间
    HighestVpn = (MM_HIGHEST_VAD_ADDRESS + 1) / PAGE_SIZE;
    if (HighestVpn >= LowVpn + PageCount) {
        *Base = LowVpn << PAGE_SHIFT;
        return TableInsertAsRight;
    }
    return TableFoundNode; // 无足够空间
}

6. MiFindEmptyAddressRangeDownTree------Top-Down 搜索

文件:vadnode.c:591

功能 :从指定边界地址向低地址搜索,找到第一个足够大的空闲地址范围。用于 MEM_TOP_DOWN 分配。

搜索算法

复制代码
1. 计算最高边界 HighVpn = (BoundaryAddress + 1) >> PAGE_SHIFT
2. 如果 AVL 树为空,直接返回 BoundaryAddress - Length 对齐后地址
3. 从根节点开始,沿右孩子找到结束地址 >= HighVpn 的节点
4. 从该节点开始,逐个向前遍历(MiGetPreviousNode):
   a. 计算当前节点结束地址之上的空间大小
   b. 如果空间足够 → 从高地址向下分配
   c. 否则更新 HighVpn 为当前节点起始地址
   d. 继续前一个节点
5. 如果遍历完还没有,检查最低 VAD 之下的空间

地址空间(低位 → 高位):
┌──────┐   ┌──────────┐   ┌──────────┐   ┌──────────┐
│ VAD1 │   │   VAD2   │   │   VAD3   │   │   VAD4   │
└──────┘   └──────────┘   └──────────┘   └──────────┘
                              ↑              ↑
                              │              └─ BoundaryAddress
                              └─ 找到空闲区域(从高地址向下取)
c 复制代码
TABLE_SEARCH_RESULT NTAPI MiFindEmptyAddressRangeDownTree(
    SIZE_T Length, ULONG_PTR BoundaryAddress, ULONG_PTR Alignment,
    PMM_AVL_TABLE Table, OUT PULONG_PTR Base, OUT PMMADDRESS_NODE *Parent)
{
    HighVpn = (BoundaryAddress + 1) >> PAGE_SHIFT;

    // 从根开始,找到结束地址 >= HighVpn 的最右节点
    Node = RtlRightChildAvl(&Table->BalancedRoot);
    while ((Node->EndingVpn < HighVpn) &&
           ((Child = RtlRightChildAvl(Node)))) Node = Child;

    // 反向遍历
    while (Node) {
        LowVpn = ALIGN_UP_BY(Node->EndingVpn + 1, AlignmentVpn);
        if ((HighVpn > LowVpn) && ((HighVpn - LowVpn) >= PageCount)) {
            LowVpn = ALIGN_DOWN_BY(HighVpn - PageCount, AlignmentVpn);
            *Base = LowVpn << PAGE_SHIFT;
            return ...;
        }
        if (Node->StartingVpn < HighVpn) HighVpn = Node->StartingVpn;
        OldNode = Node;
        Node = MiGetPreviousNode(Node);
    }

    // 检查最低 VAD 之下
    LowVpn = ALIGN_UP_BY(MI_LOWEST_VAD_ADDRESS, Alignment) / PAGE_SIZE;
    if ((HighVpn > LowVpn) && ((HighVpn - LowVpn) >= PageCount)) { ... }
    return TableFoundNode;
}

7. MiInsertBasedSection------基于地址的 Section 管理

文件:vadnode.c:386

用途 :管理全局的 MmSectionBasedRoot AVL 树,记录已映射 Section 在地址空间中的位置。

特点

  • 使用全局表 MmSectionBasedRoot(非进程私有)
  • 插入时以 Section->Address 为节点,以 StartingVpn 为键
  • 通过 MmSectionBasedMutex 保护并发访问
c 复制代码
// MmSectionBasedRoot 是全局变量,不是每个进程一个
extern MM_AVL_TABLE MmSectionBasedRoot;

VOID NTAPI MiInsertBasedSection(IN PSECTION Section)
{
    ASSERT_LOCKED_FOR_WRITE(&MmSectionBasedRoot);
    Result = RtlpFindAvlTableNodeOrParent(&MmSectionBasedRoot,
                                          (PVOID)Section->Address.StartingVpn,
                                          &Parent);
    MiInsertNode(&MmSectionBasedRoot, &Section->Address, Parent, Result);
}

配套函数 MiFindEmptyAddressRangeDownBasedTreevadnode.c:711):

  • MmSectionBasedRoot 中从高地址向低地址搜索空闲范围
  • 模拟 Windows 2003 的 bug(当 NTDDI_VERSION < NTDDI_VISTA 时可能浪费最多 Alignment 字节)

附录:AVL 树辅助函数

函数 行号 功能
MiGetPreviousNode 423 获取 AVL 树中前一个节点(中序前驱)
MiGetNextNode 459 获取 AVL 树中后一个节点(中序后继)
MiInsertNode 207 底层 AVL 树节点插入
MiRemoveNode 403 底层 AVL 树节点删除
MiCheckSecuredVad 813 检查 Secured VAD 的权限修改是否合法
MiLocateAddress 147 在当前进程中按虚拟地址查找 VAD
MmReadWrite 25 32 字节权限表,按保护码查允许的访问类型
相关推荐
智者知已应修善业20 小时前
【使用D触发器74HC175实现0001 0011 0111 1111 1110 1100 1000的循环彩灯电路。】2025-4-28
驱动开发·经验分享·笔记·硬件架构·硬件工程
abcy07121320 小时前
flink窗口类型
开发语言·python·算法
叩码以求索1 天前
三维建模:SolidWorks 2025保姆级下载与安装教程指南
学习·数学建模
龙仔7251 天前
人大金仓KingbaseES V8 手动单库备份&恢复操作笔记
数据库·笔记·oracle·人大金仓
zhangrelay1 天前
消费电子原装锂电池服役8–20年且健康度>40%公开实测案例完整调研分析报告
笔记·学习
疯狂打码的少年1 天前
【软件工程】软件测试:黑盒测试(等价类/边界值/因果图)
笔记·软件工程
什巳1 天前
JAVA练习309- 二叉树的层序遍历
java·数据结构·算法·leetcode
tyqtyq221 天前
HarmonyOS AI 应用开发实战:简历项目经历改写系统
人工智能·学习·华为·生活·harmonyos
圣光SG1 天前
Java Web入门基础知识笔记
java·前端·笔记