接下来的两篇文章对AMD KFD的BO设计分析系列 0:开篇涉及的概念和结构体作了一个整体的分析,内容较多,偏理论一些,可以和后面的文章反复对照。如果你对drm系统还不熟悉,那先理解drm框架,可以参见:linux drm子系统专栏介绍。不管有没有基础,该文都是一个参考性的文档,不必都理解。
目录
1. 概述与继承层次
1.1 核心继承关系图
┌──────────────────────┐
│ drm_gem_object │ ← DRM 通用层(最上层)
│ (GEM 缓冲对象基类) │
└──────────────────────┘
▲
│ 继承(嵌入)
│
┌──────────────────────┐
│ ttm_buffer_object │ ← TTM 内存管理层
│ (内存管理扩展) │
└──────────────────────┘
▲
│ 继承(嵌入)
│
┌──────────────────────┐
│ amdgpu_bo │ ← AMD 驱动层
│ (AMD GPU 特定扩展) │
└──────────────────────┘
▲
│ 关联(指针)
│
┌──────────────────────┐
│ kgd_mem │ ← KFD 计算层
│ (ROCm/HSA 扩展) │
└──────────────────────┘
1.2 层次职责概览
| 层次 | 结构体 | 主要职责 | 典型用户 |
|---|---|---|---|
| DRM 层 | drm_gem_object | 用户空间接口、引用计数、mmap | libdrm、Mesa |
| TTM 层 | ttm_buffer_object | 内存域管理、页面分配、驱逐 | DRM 驱动 |
| 驱动层 | amdgpu_bo | GPU 特定属性、VM 管理、Pin | amdgpu 驱动 |
| 计算层 | kgd_mem | 多 GPU 映射、共享内存、HSA | ROCm/KFD |
1.3 继承方式说明
C 语言中的"继承"通过结构体嵌入实现
// 示例:TTM 继承 GEM
struct ttm_buffer_object {
struct drm_gem_object base; // ← 第一个成员,允许类型转换
// ... TTM 扩展字段
};
// 安全转换
struct ttm_buffer_object *tbo = ...;
struct drm_gem_object *gem = &tbo->base; // 向上转换(安全)
// 向下转换(需确保类型正确)
struct drm_gem_object *gem = ...;
struct ttm_buffer_object *tbo = container_of(gem, struct ttm_buffer_object, base);
2. drm_gem_object - DRM 层抽象
2.1 结构定义
// include/drm/drm_gem.h
struct drm_gem_object {
/* === 引用计数管理 === */
struct kref refcount; // 对象引用计数
unsigned handle_count; // 用户句柄数量
/* === 设备关联 === */
struct drm_device *dev; // 所属 DRM 设备
/* === 内存映射 === */
struct file *filp; // SHMEM 后端文件(可选)
struct drm_vma_offset_node vma_node; // mmap 偏移节点
/* === 基础属性 === */
size_t size; // 对象大小(字节)
int name; // 全局名称(flink)
/* === DMA-BUF 导入/导出 === */
struct dma_buf *dma_buf; // 导出的 dma-buf
struct dma_buf_attachment *import_attach; // 导入的 dma-buf 附件
/* === 同步与预留 === */
struct dma_resv *resv; // 预留对象指针
struct dma_resv _resv; // 内嵌预留对象
/* === 操作函数集 === */
const struct drm_gem_object_funcs *funcs;
};
2.2 核心职责
(1) 用户空间接口管理
// 职责:提供统一的用户空间句柄管理
// 创建 GEM 句柄(用户可见)
int drm_gem_handle_create(struct drm_file *file_priv,
struct drm_gem_object *obj,
u32 *handlep);
// 通过句柄查找对象
struct drm_gem_object *drm_gem_object_lookup(struct drm_file *filp,
u32 handle);
// 关闭句柄(减少引用)
int drm_gem_handle_delete(struct drm_file *filp, u32 handle);
用户空间视角:
// 用户空间代码
struct drm_gem_create args = {
.size = 4096,
};
ioctl(fd, DRM_IOCTL_GEM_CREATE, &args);
uint32_t handle = args.handle; // 用户获得句柄
// 内核内部:
// handle → drm_gem_object 映射
(2) 内存映射(mmap)支持
// 职责:支持将 GEM 对象映射到用户空间
// 创建 mmap 偏移
int drm_gem_create_mmap_offset(struct drm_gem_object *obj);
// 用户 mmap 时的处理
int drm_gem_mmap(struct file *filp, struct vm_area_struct *vma);
mmap 流程:
用户调用 mmap(fd, offset, size)
↓
drm_gem_mmap()
↓
通过 vma_node 找到 drm_gem_object
↓
调用 obj->funcs->mmap() (驱动实现)
↓
建立用户空间映射
(3) 引用计数管理
// 职责:确保对象生命周期正确
// 增加引用
void drm_gem_object_get(struct drm_gem_object *obj) {
kref_get(&obj->refcount);
}
// 减少引用(可能释放)
void drm_gem_object_put(struct drm_gem_object *obj) {
kref_put(&obj->refcount, drm_gem_object_free);
}
引用计数来源:
| 引用类型 | 说明 | 示例 |
|---|---|---|
| 句柄引用 | 用户空间持有句柄 | handle_count > 0 |
| 内核引用 | 驱动内部使用 | refcount > handle_count |
| DMA-BUF 导出 | 导出为 dma-buf | dma_buf != NULL |
(4) DMA-BUF 导入/导出
// 导出为 dma-buf
struct dma_buf *drm_gem_prime_export(struct drm_gem_object *obj,
int flags);
// 从 dma-buf 导入
struct drm_gem_object *drm_gem_prime_import(struct drm_device *dev,
struct dma_buf *dma_buf);
跨进程/设备共享:
进程 A: GEM 对象 → 导出 dma-buf → 文件描述符
│
├─ 通过 Unix socket 发送 fd
↓
进程 B: 导入 dma-buf → 创建 GEM 对象
2.3 操作函数集(Polymorphism)
struct drm_gem_object_funcs {
void (*free)(struct drm_gem_object *obj);
int (*open)(struct drm_gem_object *obj, struct drm_file *file);
void (*close)(struct drm_gem_object *obj, struct drm_file *file);
// mmap 支持
int (*mmap)(struct drm_gem_object *obj, struct vm_area_struct *vma);
// DMA-BUF 操作
struct sg_table *(*get_sg_table)(struct drm_gem_object *obj);
void *(*vmap)(struct drm_gem_object *obj);
void (*vunmap)(struct drm_gem_object *obj, void *vaddr);
// Pin/Unpin(固定内存)
int (*pin)(struct drm_gem_object *obj);
void (*unpin)(struct drm_gem_object *obj);
};
多态机制:
// 不同驱动实现不同的函数集
static const struct drm_gem_object_funcs amdgpu_gem_funcs = {
.free = amdgpu_gem_object_free,
.mmap = amdgpu_gem_object_mmap,
// ...
};
// 调用时自动分派到正确实现
obj->funcs->free(obj); // → amdgpu_gem_object_free()
2.4 设计模式分析
模式 1:引用计数(Reference Counting)
目的: 自动管理对象生命周期
void use_gem_object(struct drm_gem_object *obj) {
drm_gem_object_get(obj); // 增加引用
// 使用对象...
drm_gem_object_put(obj); // 减少引用,可能释放
}
模式 2:抽象工厂(Abstract Factory)
目的: 隐藏具体类型,提供统一接口
// DRM 核心不需要知道具体是 amdgpu_bo 还是 i915_bo
void drm_core_function(struct drm_gem_object *obj) {
obj->funcs->pin(obj); // 多态调用
}
2.5 典型使用场景
场景 1:用户空间分配缓冲区
// 用户空间:创建缓冲区
struct drm_gem_create create = { .size = 1024*1024 };
ioctl(drm_fd, DRM_IOCTL_GEM_CREATE, &create);
uint32_t handle = create.handle;
// 内核驱动:
// 1. drm_gem_object 创建
// 2. 分配后端存储(VRAM/GTT)
// 3. 注册到句柄表
场景 2:mmap 到用户空间
// 用户空间:映射缓冲区
void *mapped = mmap(NULL, size, PROT_READ|PROT_WRITE,
MAP_SHARED, drm_fd, mmap_offset);
// 内核:
// drm_gem_mmap() → 通过 vma_node 找到 obj → obj->funcs->mmap()
场景 3:跨进程共享(DMA-BUF)
// 进程 A:导出
int prime_fd = drm_ioctl(DRM_IOCTL_PRIME_HANDLE_TO_FD, handle);
send_fd_to_process_b(prime_fd);
// 进程 B:导入
uint32_t imported_handle = drm_ioctl(DRM_IOCTL_PRIME_FD_TO_HANDLE,
received_fd);
三、ttm_buffer_object - TTM 内存管理层
3.1 结构定义
// include/drm/ttm/ttm_bo_api.h
struct ttm_buffer_object {
/* === 继承 DRM GEM === */
struct drm_gem_object base; // ← 嵌入 GEM 对象
/* === TTM 设备与类型 === */
struct ttm_bo_device *bdev; // TTM 设备
enum ttm_bo_type type; // BO 类型(设备/用户/内核)
void (*destroy)(struct ttm_buffer_object *);
/* === 内存属性 === */
unsigned long num_pages; // 页面数量
size_t acc_size; // 会计大小(含元数据)
/* === 引用计数(双层) === */
struct kref kref; // 主引用计数
struct kref list_kref; // LRU 列表引用
/* === 内存位置与状态 === */
struct ttm_mem_reg mem; // 当前内存区域
struct ttm_tt *ttm; // 系统内存页表
bool evicted; // 是否被驱逐
/* === 页面存储 === */
struct file *persistent_swap_storage; // 持久化交换存储
/* === LRU 管理 === */
struct list_head lru; // LRU 链表
struct list_head ddestroy; // 延迟销毁链表
struct list_head swap; // 交换链表
/* === 移动同步 === */
struct dma_fence *moving; // 移动操作 fence
unsigned priority; // 优先级
/* === GPU 地址 === */
uint64_t offset; // GPU 地址空间偏移
struct sg_table *sg; // Scatter-gather 表
};
3.2 核心职责
(1) 内存域管理
TTM 支持多种内存类型:
// 内存域定义
enum ttm_pl_type {
TTM_PL_SYSTEM = 0, // 系统内存(可换出)
TTM_PL_TT = 1, // GTT(Graphics Translation Table)
TTM_PL_VRAM = 2, // 显存
TTM_PL_PRIV = 3, // 驱动私有域
};
内存区域描述:
struct ttm_mem_reg {
void *mm_node; // 内存管理器节点
unsigned long start; // 起始页框号
unsigned long size; // 大小(页)
unsigned long num_pages; // 页数
uint32_t page_alignment; // 页对齐
uint32_t mem_type; // 内存类型
uint32_t placement; // 放置标志
};
(2) 内存迁移与驱逐
驱逐场景:
VRAM 不足
↓
选择低优先级 BO 驱逐
↓
ttm_bo_evict()
↓
移动到 GTT 或系统内存
↓
更新 GPU 页表
迁移函数:
// 验证并可能迁移 BO
int ttm_bo_validate(struct ttm_buffer_object *bo,
struct ttm_placement *placement,
struct ttm_operation_ctx *ctx);
// 实际移动操作
int ttm_bo_move(struct ttm_buffer_object *bo,
bool evict,
struct ttm_operation_ctx *ctx,
struct ttm_mem_reg *new_mem);
迁移路径示例:
| 源 | 目标 | 方法 | 说明 |
|---|---|---|---|
| VRAM | GTT | DMA 拷贝 | 通过 SDMA 引擎 |
| GTT | VRAM | DMA 拷贝 | 上传到显存 |
| VRAM | 系统 | 两步迁移 | VRAM→GTT→系统 |
| 系统 | GTT | Bind | 绑定页面到 GART |
(3) 页面分配与管理
系统内存页表(ttm_tt):
struct ttm_tt {
struct page **pages; // 页面数组
uint32_t page_flags; // 页面标志
unsigned long num_pages; // 页数
struct sg_table *sg; // DMA 映射
dma_addr_t *dma_address; // DMA 地址数组
enum ttm_caching_state caching_state;
enum {
tt_bound,
tt_unbound,
tt_unpopulated,
} state;
};
页面分配流程:
// 1. 分配 ttm_tt 结构
ttm_tt_create(bo);
// 2. 填充页面(populate)
ttm_tt_populate(ttm); // 分配物理页
// 3. 绑定到 GPU(如 GTT)
ttm_tt_bind(ttm, &bo->mem);
// 4. GPU 可访问
(4) LRU(Least Recently Used)管理
LRU 列表用途:
- 驱逐候选对象选择
- 内存回收优先级
- 减少碎片化
// 移动到 LRU 尾部(最近使用)
void ttm_bo_move_to_lru_tail(struct ttm_buffer_object *bo,
struct ttm_lru_bulk_move *bulk);
// 从 LRU 删除(Pin 时)
void ttm_bo_del_from_lru(struct ttm_buffer_object *bo);
// 添加到 LRU(Unpin 时)
void ttm_bo_add_to_lru(struct ttm_buffer_object *bo);
LRU 驱逐策略:
LRU 头 [最旧] ← ← ← ← [最新] LRU 尾
│
↓ 驱逐时从这里开始
选择驱逐对象
(5) 预留机制(Reservation)
目的: 确保多个操作的原子性
// 预留 BO(加锁)
int ttm_bo_reserve(struct ttm_buffer_object *bo,
bool interruptible,
bool no_wait,
struct ww_acquire_ctx *ticket);
// 释放预留
void ttm_bo_unreserve(struct ttm_buffer_object *bo);
预留使用场景:
// 执行命令提交时
int submit_command(struct ttm_buffer_object *bo) {
// 1. 预留所有相关 BO
ttm_bo_reserve(bo, true, false, NULL);
// 2. 验证位置(可能迁移)
ttm_bo_validate(bo, &placement, &ctx);
// 3. 添加 fence
dma_resv_add_excl_fence(bo->base.resv, fence);
// 4. 释放预留
ttm_bo_unreserve(bo);
}
3.3 与 drm_gem_object 的协同
继承关系实现:
// TTM 创建时初始化 GEM 基类
int ttm_bo_init(struct ttm_bo_device *bdev,
struct ttm_buffer_object *bo,
unsigned long size, ...) {
// 初始化 GEM 部分
drm_gem_private_object_init(bdev->ddev, &bo->base, size);
// 设置 GEM 操作函数
bo->base.funcs = &ttm_gem_object_funcs;
// 初始化 TTM 特定部分
bo->bdev = bdev;
bo->type = type;
// ...
}
类型转换辅助函数:
// GEM → TTM
static inline struct ttm_buffer_object *
drm_gem_to_ttm_bo(struct drm_gem_object *gem) {
return container_of(gem, struct ttm_buffer_object, base);
}
// 使用示例
void process_gem_object(struct drm_gem_object *gem) {
struct ttm_buffer_object *bo = drm_gem_to_ttm_bo(gem);
// 现在可以使用 TTM 功能
ttm_bo_validate(bo, ...);
}
3.4 内存域状态机
[创建]
↓
tt_unpopulated ─→ [分配页面] ─→ tt_unbound
│ ↓
│ [绑定到 GPU]
│ ↓
│ tt_bound
│ ↓
└────────← [解绑/销毁] ←─────────┘
状态说明:
| 状态 | 说明 | 内存分配 | GPU 可访问 |
|---|---|---|---|
tt_unpopulated | 未分配页面 | ❌ | ❌ |
tt_unbound | 已分配但未绑定 | ✅ | ❌ |
tt_bound | 已绑定到 GPU | ✅ | ✅ |
3.5 典型使用场景
场景 1:创建并分配 VRAM BO
struct ttm_buffer_object *bo;
struct ttm_placement placement = {
.num_placement = 1,
.placement = &vram_placement,
};
// 创建 BO
ttm_bo_init(bdev, bo, size, ttm_bo_type_device,
&placement, 0, false, NULL, acc_size, NULL, NULL,
&destroy_callback);
// TTM 自动:
// 1. 在 VRAM 分配空间
// 2. 初始化 GEM 对象
// 3. 加入 LRU 列表
场景 2:VRAM 不足时驱逐
// VRAM 分配失败
if (!vram_available(size)) {
// TTM 自动驱逐
// 1. 从 LRU 选择牺牲对象
list_for_each_entry_safe(bo, tmp, &lru_list, lru) {
if (can_evict(bo)) {
ttm_bo_evict(bo); // 移动到 GTT
if (vram_available(size))
break;
}
}
}
场景 3:CPU/GPU 内存迁移
// BO 初始在系统内存
// 应用首次 GPU 访问时迁移到 VRAM
// 用户提交绘制命令
submit_draw_command(bo);
// 驱动检测并迁移
ttm_bo_reserve(bo, false, false, NULL);
placement.num_placement = 1;
placement.placement = &vram_place;
// 迁移:系统内存 → VRAM
ttm_bo_validate(bo, &placement, &ctx);
// 内部调用:ttm_bo_move() → DMA 拷贝
ttm_bo_unreserve(bo);
下文:amdgpu_bo/kgd_mem分析,还在构思编写中…
780

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



