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_SIZE或GPU_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 是一个大的空闲区域
- 分配内存:从链表中找到足够大的"洞"
- 释放内存:将区域插回链表,必要时合并相邻区域
核心操作:
- 分配(reserved_aperture_allocate_aligned):
遍历 vm_ranges 链表
if (当前区域到下一区域的间隙 >= 请求大小)
在间隙中分配
如果需要,分割现有区域
返回分配的地址
next
if (最后一个区域之后的空间 >= 请求大小)
在末尾分配
返回 NULL(分配失败)
- 释放(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链表的修改tree和user_tree的插入/删除- 内存对象的创建和销毁
锁的粒度:
- 每个 aperture 独立的互斥锁
- 不同 aperture 的操作可以并发
- 同一 aperture 的操作串行化
性能考虑:
- 避免在持有锁时调用内核接口(ioctl)
- 最小化临界区大小
- 在某些只读查询中可能避免加锁(通过 vm_object 的不变性)
2.2.7 CPU 可访问性(is_cpu_accessible)
标志意义:
true:CPU 可以直接访问该 aperture 的内存false:仅 GPU 可访问,CPU 访问会导致段错误
影响行为:
- 内存释放时:
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);
}
- 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;
两种实现:
- reserved_aperture_ops(预留式):
static const manageable_aperture_ops_t reserved_aperture_ops = {
reserved_aperture_allocate_aligned, // 从 vm_ranges 链表分配
reserved_aperture_release // 归还到 vm_ranges 链表
};
- 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 Aperture | ✓ | svm_t.apertures[2] | reserved/mmap 自适应 | CPU-GPU 统一地址空间 |
| GPUVM Aperture | ✓ | gpu_mem_t.gpuvm_aperture | reserved | APU 的 GPU 虚拟内存 |
| Scratch Physical | ✓ | gpu_mem_t.scratch_physical | reserved | dGPU 临时内存物理管理 |
| CPUVM Aperture | ✓ | cpuvm_aperture | mmap | 系统内存追踪 |
| Memory Handle Aperture | ✓ | mem_handle_aperture | reserved | 内存句柄生成 |
| 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 内存管理的基石,它将简单的地址范围扩展为功能完备的内存管理器:
- 统一接口:提供一致的分配/释放接口,屏蔽底层实现差异
- 高效索引:红黑树实现 O(log n) 查找,支持大规模内存分配
- 灵活策略:通过 ops 函数指针支持多种分配策略
- 线程安全:每个 aperture 独立的互斥锁保证并发安全
- 调试友好:保护页机制帮助捕获内存越界错误
- 性能优化:智能对齐、区域合并等技术提升性能

被折叠的 条评论
为什么被折叠?



