AMD rocr-libhsakmt分析系列3-1: manageable_aperture_t 技术分析

1. 概述

前文:rocr-libhsakmt分析系列3-1: Apertures总结了所有的apertures,那么这些apertures是如何管理的呢?本文的内容就是讲解apertures的管理实现的核心结构体:manageable_aperture_t
manageable_aperture_t 是 libhsakmt 内存管理系统的核心数据结构,它将简单的地址范围定义(aperture_t)扩展为功能完备的内存管理器。本文档深入分析该结构体在 aperture 实现中的作用、设计原理和应用场景。

2. 结构体定义与成员分析

2.1 完整定义

struct manageable_aperture {
    void *base;                              // 起始地址
    void *limit;                            // 结束地址
    uint64_t align;                         // 内存对齐要求
    uint32_t guard_pages;                   // 保护页数量
    vm_area_t *vm_ranges;                   // 空闲区域链表头
    rbtree_t tree;                          // 已分配对象红黑树(按地址索引)
    rbtree_t user_tree;                     // 用户指针红黑树(用于 userptr)
    pthread_mutex_t fmm_mutex;              // 线程同步互斥锁
    bool is_cpu_accessible;                 // CPU 可访问性标志
    const manageable_aperture_ops_t *ops;   // 操作函数指针
};

2.2 成员字段详解

2.2.1 地址空间定义(base & limit)

功能

  • base:定义 aperture 管理的虚拟地址空间起始位置
  • limit:定义地址空间的结束位置(包含)

特性

  • 地址范围为 [base, limit],即两端都是闭区间
  • 有效性检查:base < limit 且两者都非 NULL
  • 地址范围大小:limit - base + 1

应用示例

// SVM aperture 可能的地址范围
base  = 0x00007f0000000000
limit = 0x00007fffffffffff
// 总大小约 1TB
2.2.2 内存对齐(align)

作用

  • 指定该 aperture 分配内存的最小对齐要求
  • 所有分配的起始地址必须是 align 的倍数

对齐策略

基础对齐 = MAX(aperture.align, 用户请求对齐)
动态对齐 = 2^n,其中 n 使得 2^n >= size/2 且 2^n <= GPU_HUGE_PAGE_SIZE
最终对齐 = MAX(基础对齐, 动态对齐)

典型值

  • SVM aperture:GPU_HUGE_PAGE_SIZE (2MB)
  • GPUVM aperture:根据 GPU 型号,1MB 或 2MB
  • CPUVM aperture:PAGE_SIZE (4KB)
  • Scratch aperture:PAGE_SIZEGPU_HUGE_PAGE_SIZE

性能影响

  • 减少 TLB 缺失
  • 支持 GPU 的大页优化
  • 提高内存访问效率
2.2.3 保护页机制(guard_pages)

设计目的

  • 在每个分配对象后添加不可访问的保护页
  • 捕获内存越界访问错误
  • 通过 VM fault 触发段错误,便于调试

实现原理

实际分配大小 = 请求大小 + (guard_pages * PAGE_SIZE)
保护页区域 = [对象结束地址 + 1, 对象结束地址 + guard_pages * PAGE_SIZE]

配置方式

  • 环境变量 HSA_SVM_GUARD_PAGES 控制
  • 默认值为 1 页(4KB)
  • 可配置为 0(禁用)或更大值

内存开销

假设分配 1000 个对象,每个 1MB,guard_pages = 1
保护页总开销 = 1000 × 4KB = 4MB
开销比例 = 4MB / 1000MB ≈ 0.4%
2.2.4 空闲区域链表(vm_ranges)

数据结构

typedef struct vm_area {
    void *start;           // 空闲区域起始地址
    void *end;             // 空闲区域结束地址
    struct vm_area *next;  // 下一个空闲区域
    struct vm_area *prev;  // 上一个空闲区域
} vm_area_t;

管理策略

  • 双向链表按地址升序排列
  • 初始状态:整个 aperture 是一个大的空闲区域
  • 分配内存:从链表中找到足够大的"洞"
  • 释放内存:将区域插回链表,必要时合并相邻区域

核心操作

  1. 分配(reserved_aperture_allocate_aligned)
遍历 vm_ranges 链表
    if (当前区域到下一区域的间隙 >= 请求大小)
        在间隙中分配
        如果需要,分割现有区域
        返回分配的地址
    next
if (最后一个区域之后的空间 >= 请求大小)
    在末尾分配
返回 NULL(分配失败)
  1. 释放(reserved_aperture_release)
找到包含该地址的 vm_area
if (释放大小 == 区域大小)
    完全移除该区域
else if (从区域开始释放)
    缩小区域起始地址
else if (从区域结束释放)
    缩小区域结束地址
else
    分割区域为两部分

优化特性

  • 地址结束对齐:对于未指定对齐的分配,将对象的结束地址对齐
  • 合并操作:相邻的空闲区域自动合并
  • 扩展操作:新分配紧邻现有区域时,直接扩展而非创建新区域
2.2.5 红黑树索引(tree & user_tree)

tree(主索引树)

  • 键值rbtree_key(address, size)
  • 存储:所有已分配的 vm_object_t
  • 索引:按对象的虚拟地址(start)索引
  • 查找:O(log n) 时间复杂度

user_tree(用户指针索引树)

  • 键值rbtree_key(userptr, userptr_size)
  • 存储:通过 userptr 注册的 vm_object_t
  • 索引:按用户提供的原始指针(userptr)索引
  • 用途:支持用户内存注册和反注册

性能优势

  • 查找:O(log n) vs 链表的 O(n)
  • 插入/删除:O(log n)
  • 支持范围查询和最近邻查找
2.2.6 线程同步(fmm_mutex)

保护范围

  • vm_ranges 链表的修改
  • treeuser_tree 的插入/删除
  • 内存对象的创建和销毁

锁的粒度

  • 每个 aperture 独立的互斥锁
  • 不同 aperture 的操作可以并发
  • 同一 aperture 的操作串行化

性能考虑

  • 避免在持有锁时调用内核接口(ioctl)
  • 最小化临界区大小
  • 在某些只读查询中可能避免加锁(通过 vm_object 的不变性)
2.2.7 CPU 可访问性(is_cpu_accessible)

标志意义

  • true:CPU 可以直接访问该 aperture 的内存
  • false:仅 GPU 可访问,CPU 访问会导致段错误

影响行为

  1. 内存释放时
if (aperture->is_cpu_accessible) {
    // 重置 NUMA 策略
    mbind(address, size, MPOL_DEFAULT, NULL, 0, 0);
    
    // 保留地址空间但移除物理映射
    mmap(address, size, PROT_NONE, 
         MAP_ANONYMOUS | MAP_NORESERVE | MAP_PRIVATE | MAP_FIXED, -1, 0);
}
  1. mmap aperture 必须可访问
if (!aper->is_cpu_accessible) {
    pr_err("MMap Aperture must be CPU accessible\n");
    return NULL;
}

典型配置

  • SVM apertures:true(CPU-GPU 共享)
  • GPUVM aperture(APU):false
  • CPUVM aperture:true
  • Memory handle aperture:false
  • Scratch physical:部分 true(调试模式)
2.2.8 操作函数指针(ops)

接口定义

typedef struct {
    void *(*allocate_area_aligned)(manageable_aperture_t *aper, 
                                   void *addr, uint64_t size, uint64_t align);
    void (*release_area)(manageable_aperture_t *aper, 
                        void *addr, uint64_t size);
} manageable_aperture_ops_t;

两种实现

  1. reserved_aperture_ops(预留式):
static const manageable_aperture_ops_t reserved_aperture_ops = {
    reserved_aperture_allocate_aligned,  // 从 vm_ranges 链表分配
    reserved_aperture_release            // 归还到 vm_ranges 链表
};
  1. mmap_aperture_ops(按需式):
static const manageable_aperture_ops_t mmap_aperture_ops = {
    mmap_aperture_allocate_aligned,  // 调用 mmap 分配
    mmap_aperture_release            // 调用 munmap 释放
};

策略选择

  • 现代 GPU(GFXv9+,47 位地址空间):优先 mmap_aperture_ops
  • 传统 GPU 或受限环境:使用 reserved_aperture_ops
  • 初始化时动态测试并选择

多态调用

static void *aperture_allocate_area_aligned(manageable_aperture_t *app,
                                           void *address, uint64_t size, uint64_t align) {
    return app->ops->allocate_area_aligned(app, address, size, align);
}

static void aperture_release_area(manageable_aperture_t *app,
                                  void *address, uint64_t size) {
    app->ops->release_area(app, address, size);
}

3. 使用 manageable_aperture_t 的 Aperture 类型

3.1 完整对比表

Aperture 类型使用 manageable_aperture_t结构体字段位置操作策略主要用途
SVM Aperturesvm_t.apertures[2]reserved/mmap 自适应CPU-GPU 统一地址空间
GPUVM Aperturegpu_mem_t.gpuvm_aperturereservedAPU 的 GPU 虚拟内存
Scratch Physicalgpu_mem_t.scratch_physicalreserveddGPU 临时内存物理管理
CPUVM Aperturecpuvm_aperturemmap系统内存追踪
Memory Handle Aperturemem_handle_aperturereserved内存句柄生成
LDS Aperture×gpu_mem_t.lds_aperture-只读,内核管理
Scratch Aperture×gpu_mem_t.scratch_aperture-只读,虚拟地址范围
MMIO Aperture×gpu_mem_t.mmio_aperture-直接映射,不需管理

4. 核心操作流程

4.1 Aperture 查找流程(fmm_find_aperture)

输入:地址 address
输出:对应的 manageable_aperture_t

1. Memory Handle 范围检查
   if (address in [START_NON_CANONICAL_ADDR, ...])
       return &mem_handle_aperture

2. dGPU 环境
   if (address in dgpu_aperture)
       if (address in scratch_physical)
           return &gpu_mem[i].scratch_physical
       else
           return dgpu_aperture
   else if (address in dgpu_alt_aperture)
       return dgpu_alt_aperture
   else
       return dgpu_aperture  // 可能是 userptr

3. APU 环境
   if (address in dgpu_aperture)
       return dgpu_aperture
   for each GPU:
       if (address in gpu_mem[i].gpuvm_aperture)
           return &gpu_mem[i].gpuvm_aperture
   return &cpuvm_aperture  // 默认

4. 设置 aperture 类型信息(HsaApertureInfo)
   返回

5. 设计模式与架构优势

5.1 策略模式(Strategy Pattern)

体现ops 函数指针实现不同的分配策略

优势

  • 同一接口,多种实现
  • 运行时动态选择策略
  • 易于扩展新的分配策略

代码示例

// 统一接口
void *allocate(manageable_aperture_t *aper, size_t size) {
    return aper->ops->allocate_area_aligned(aper, NULL, size, aper->align);
}

// 策略1:预留式
static void *reserved_aperture_allocate(...) { /* 从链表分配 */ }

// 策略2:按需式
static void *mmap_aperture_allocate(...) { /* 调用 mmap */ }

// 初始化时选择策略
if (modern_gpu && test_mmap_success())
    aperture->ops = &mmap_aperture_ops;
else
    aperture->ops = &reserved_aperture_ops;

5.2 对象池模式(Object Pool)

体现vm_ranges 链表管理空闲地址池

优势

  • 减少 mmap/munmap 系统调用
  • 地址重用,减少碎片
  • 快速分配和释放

5.3 索引加速(Index Acceleration)

体现:双红黑树索引

优势

  • 快速查找:O(log n) vs O(n)
  • 支持多维度查询(地址 + userptr)
  • 范围查询和最近邻查找

5.4 延迟绑定(Lazy Binding)

体现:mmap_aperture 不预留地址空间

优势

  • 减少虚拟内存占用
  • 更灵活的地址分配
  • 适应大地址空间

6. 总结

manageable_aperture_t 是 libhsakmt 内存管理的基石,它将简单的地址范围扩展为功能完备的内存管理器:

  1. 统一接口:提供一致的分配/释放接口,屏蔽底层实现差异
  2. 高效索引:红黑树实现 O(log n) 查找,支持大规模内存分配
  3. 灵活策略:通过 ops 函数指针支持多种分配策略
  4. 线程安全:每个 aperture 独立的互斥锁保证并发安全
  5. 调试友好:保护页机制帮助捕获内存越界错误
  6. 性能优化:智能对齐、区域合并等技术提升性能
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DeeplyMind

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值