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 字节权限表,按保护码查允许的访问类型
相关推荐
从零开始的嵌入式之旅4 小时前
day33
linux·c语言·经验分享·笔记·嵌入式硬件
AI服务老曹4 小时前
车牌识别算法接入AI视频分析平台的流程和误报优化
人工智能·算法·音视频
神威难绷泪5 小时前
Linux应用软件编程:线程分离属性 互斥机制 同步机制 死锁
linux·开发语言·算法·线程
小星星闪亮登场5 小时前
图论--最小生成树(内含二分图)
数据结构·算法·图论·迭代加深·图搜索算法
一条破秋裤5 小时前
STM32 学习笔记:STM32 简介与学习路线
笔记·stm32·学习
ZC跨境爬虫5 小时前
LeetCode 219. 存在重复元素 II(滑动窗口 + 哈希表详解)
算法·leetcode·散列表
念何架构之路5 小时前
Gin辅助模块与学习路径
学习·gin
吞下星星的少年·-·5 小时前
The 2025 ICPC Asia East Continent Online Contest (I) A题(模拟+贪心)
算法·贪心·模拟
具身AGI6 小时前
基座模型架构之争,物理AI 人类学习路线 的双脑答案
人工智能·学习·架构
浔溺6 小时前
al+大数据每日学习笔记27
笔记·学习