AMD rocr-libhsakmt分析系列6-2:共享机制-import

前文:AMD rocr-libhsakmt分析系列6-1:共享机制-export分析了export hsa的bo,本文分析import graphics传过来的bo。

概述

函数名称 : hsaKmtRegisterSharedHandle/hsakmt_fmm_register_graphics_handle
功能: 将图形栈(通过 DMA-BUF)导出的 GPU 内存注册到 ROCm/HSA 栈,实现图形与计算的内存互操作


1. 函数签名与参数

c 复制代码
HSAKMT_STATUS hsakmt_fmm_register_graphics_handle(
    HSAuint64 GraphicsResourceHandle,          // DMA-BUF 文件描述符
    HsaGraphicsResourceInfo *GraphicsResourceInfo,  // 输出:资源信息
    uint32_t *gpu_id_array,                    // GPU ID 数组(可选)
    uint32_t gpu_id_array_size,                // GPU ID 数组大小
    HSA_REGISTER_MEM_FLAGS RegisterFlags       // 注册标志
);

参数详解

参数 类型 方向 说明
GraphicsResourceHandle HSAuint64 IN 图形栈导出的 DMA-BUF fd
GraphicsResourceInfo HsaGraphicsResourceInfo* OUT 返回的资源信息(地址、大小、元数据)
gpu_id_array uint32_t* IN 要映射的 GPU ID 列表(可为 NULL)
gpu_id_array_size uint32_t IN GPU ID 数组大小
RegisterFlags HSA_REGISTER_MEM_FLAGS IN 注册标志(如是否需要虚拟地址)

2. 函数主流程分析

2.1 流程概览

复制代码
┌──────────────────────────────────────────────────────────────────┐
│ 1. 参数验证                                                       │
│    - 检查 gpu_id_array 与 gpu_id_array_size 的一致性               │
└────────────────┬─────────────────────────────────────────────────┘
                 │
                 ▼
┌──────────────────────────────────────────────────────────────────┐
│ 2. 第一个 IOCTL: AMDKFD_IOC_GET_DMABUF_INFO                       │
│    - 输入: DMA-BUF fd                                             │
│    - 输出: Buffer 信息(大小、GPU ID、标志、元数据)                  │
│    - 特性: 支持元数据动态调整(两次调用机制)                          │
└────────────────┬─────────────────────────────────────────────────┘
                 │
                 ▼
┌──────────────────────────────────────────────────────────────────┐
│ 3. 选择内存 Aperture                                              │
│    - 无 VA 要求 → mem_handle_aperture                             │
│    - SVM 需求 → svm.dgpu_aperture                                 │
│    - 普通情况 → gpu_mem[id].gpuvm_aperture                        │
└────────────────┬─────────────────────────────────────────────────┘
                 │
                 ▼
┌──────────────────────────────────────────────────────────────────┐
│ 4. 虚拟地址分配                                                    │
│    - aperture_allocate_area_aligned()                            │
│    - 对齐要求: IMAGE_ALIGN (256KB)                                │
│    - 互斥锁保护: aperture->fmm_mutex                               │
└────────────────┬─────────────────────────────────────────────────┘
                 │
                 ▼
┌──────────────────────────────────────────────────────────────────┐
│ 5. 第二个 IOCTL: AMDKFD_IOC_IMPORT_DMABUF                         │
│    - 输入: DMA-BUF fd, VA 地址, GPU ID                            │
│    - 输出: KFD handle                                             │
│    - 功能: 将 DMA-BUF 导入到 KFD 并映射到指定 VA                     │
└────────────────┬─────────────────────────────────────────────────┘
                 │
                 ▼
┌──────────────────────────────────────────────────────────────────┐
│ 6. 创建 VM 对象并注册                                              │
│    - aperture_allocate_object()                                  │
│    - 设置内存标志(强制 CoarseGrain)                               │
│    - 保存元数据和 GPU ID 数组                                      │
└────────────────┬─────────────────────────────────────────────────┘
                 │
                 ▼
┌──────────────────────────────────────────────────────────────────┐
│ 7. 填充输出信息                                                    │
│    - MemoryAddress: 分配的虚拟地址                                 │
│    - SizeInBytes: Buffer 大小                                     │
│    - Metadata: 元数据指针                                          │
│    - NodeId: GPU 节点 ID                                          │
└──────────────────────────────────────────────────────────────────┘

3. 核心 IOCTL 分析

3.1 第一个 IOCTL: AMDKFD_IOC_GET_DMABUF_INFO

3.1.1 IOCTL 参数结构
c 复制代码
struct kfd_ioctl_get_dmabuf_info_args {
    __u64 size;           // [OUT] Buffer 大小(字节)
    __u64 metadata_ptr;   // [IN/OUT] 元数据缓冲区指针
    __u32 metadata_size;  // [IN/OUT] 元数据大小
                          //   IN: 用户分配的缓冲区大小
                          //   OUT: 实际元数据大小
    __u32 gpu_id;         // [OUT] 创建此 buffer 的 GPU ID
    __u32 flags;          // [OUT] 内存标志 (KFD_IOC_ALLOC_MEM_FLAGS_*)
    __u32 dmabuf_fd;      // [IN] DMA-BUF 文件描述符
};
3.1.2 实现代码
c 复制代码
// 准备参数
infoArgs.dmabuf_fd = GraphicsResourceHandle;
infoArgs.metadata_size = GRAPHICS_METADATA_DEFAULT_SIZE;  // 预设默认大小
metadata = calloc(infoArgs.metadata_size, 1);
infoArgs.metadata_ptr = (uint64_t)metadata;

r = hsakmt_ioctl(hsakmt_kfd_fd, AMDKFD_IOC_GET_DMABUF_INFO, (void *)&infoArgs);
3.1.3 IOCTL 功能详解

作用:

  1. 验证 DMA-BUF: 检查 fd 是否有效且为 AMD GPU buffer
  2. 获取 Buffer 属性 :
    • size: Buffer 的实际大小
    • gpu_id: 创建该 buffer 的 GPU 标识
    • flags: 内存属性标志(可写、一致性等)
  3. 提取元数据: 读取图形栈设置的元数据
3.1.4 内核侧处理(概念)
复制代码
KFD 内核驱动侧:
1. 通过 dmabuf_fd 找到对应的 dma_buf 对象
2. 从 dma_buf->priv 获取 amdgpu_bo (buffer object)
3. 读取 BO 属性:
   - bo->tbo.base.size → size
   - bo->preferred_domains → flags
   - bo->metadata → 拷贝到用户空间
4. 确定所属 GPU ID
5. 返回给用户空间

3.2 Aperture 选择逻辑

c 复制代码
// 找到对应的 GPU 内存管理器
gpu_mem_id = gpu_mem_find_by_gpu_id(infoArgs.gpu_id);
// 根据需求选择 Aperture
if (!gpu_id_array && gpu_id_array_size == 0 && 
    !RegisterFlags.ui32.requiresVAddr) {
    // 场景 1: 不需要虚拟地址的导入
    aperture = &mem_handle_aperture;
} else if (hsakmt_topology_is_svm_needed(
               gpu_mem[gpu_mem_id].EngineId)) {
    // 场景 2: 需要 SVM (Shared Virtual Memory)
    aperture = svm.dgpu_aperture;
} else {
    // 场景 3: 普通 GPU 虚拟内存
    aperture = &gpu_mem[gpu_mem_id].gpuvm_aperture;
    aperture_base = aperture->base;
}
Aperture 类型说明
Aperture 类型 使用场景 特点
mem_handle_aperture 仅需要 handle,不需要 VA 节省虚拟地址空间
svm.dgpu_aperture SVM 应用(CPU/GPU 共享地址) 地址对所有设备可见
gpuvm_aperture 普通 GPU 专用内存 每个 GPU 独立地址空间

Aperture 选择影响:

  • 性能: SVM 可能有额外的一致性开销
  • 地址空间: 不同 aperture 管理不同的地址范围
  • 可访问性: 决定哪些设备可以访问该内存

3.3 虚拟地址分配

c 复制代码
// 在选定的 aperture 中分配虚拟地址
mem = aperture_allocate_area_aligned(
    aperture,        // 目标 aperture
    NULL,            // 地址提示(NULL = 自动选择)
    infoArgs.size,   // 大小
    IMAGE_ALIGN      // 对齐 (256KB)
);
}

关键技术点:

  1. 线程安全: 使用互斥锁保护 aperture 操作
  2. 对齐要求 : 256KB 对齐
    • 符合 GPU 页表要求
    • 优化图形资源访问性能
    • 可能与纹理 tiling 要求相关
  3. 自动分配 : 传递 NULL 让系统自动选择最佳地址

内部机制 :

c 复制代码
aperture_allocate_area_aligned() {
    // 在 aperture 的空闲区域列表中查找
    // 考虑对齐要求
    // 返回分配的虚拟地址
}

3.4 第二个 IOCTL: AMDKFD_IOC_IMPORT_DMABUF

3.4.1 IOCTL 参数结构
c 复制代码
struct kfd_ioctl_import_dmabuf_args {
    __u64 va_addr;   // [IN] 目标虚拟地址
                     //      如果为 0,则不需要映射到 VA
    __u64 handle;    // [OUT] KFD 内部 handle
    __u32 gpu_id;    // [IN] 目标 GPU ID
    __u32 dmabuf_fd; // [IN] DMA-BUF 文件描述符
};
3.4.2 实现代码
c 复制代码
// 准备导入参数
if (aperture == &mem_handle_aperture)
    importArgs.va_addr = 0;  // 不需要 VA 映射
else
    importArgs.va_addr = VOID_PTRS_SUB(mem, aperture_base);  // 计算相对地址

importArgs.gpu_id = infoArgs.gpu_id;
importArgs.dmabuf_fd = GraphicsResourceHandle;

// 调用 IOCTL
r = hsakmt_ioctl(hsakmt_kfd_fd, AMDKFD_IOC_IMPORT_DMABUF, (void *)&importArgs);
if (r) {
    pthread_mutex_unlock(&aperture->fmm_mutex);
    goto error_release_aperture;
}
3.4.3 VA 地址计算
c 复制代码
// VOID_PTRS_SUB 宏定义(概念)
#define VOID_PTRS_SUB(p1, p2) ((char*)(p1) - (char*)(p2))

// 示例:
// aperture_base = 0x8000000000
// mem = 0x8000100000
// va_addr = 0x8000100000 - 0x8000000000 = 0x100000 (相对偏移)

为什么使用相对地址:

  • GPU 页表中记录的是相对于 aperture base 的偏移
  • 内核驱动会将相对地址转换为实际物理地址映射
  • 支持 aperture 的动态重定位
3.4.4 IOCTL 功能详解

内核侧处理流程:

复制代码
KFD 内核驱动侧:
┌─────────────────────────────────────────────────────────────┐
│ 1. DMA-BUF 查找                                              │
│    - dma_buf = dma_buf_get(dmabuf_fd)                       │
│    - amdgpu_bo = dma_buf->priv                              │
└────────────────┬────────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────────────┐
│ 2. BO 导入到 KFD                                             │
│    - 增加 BO 引用计数                                         │
│    - 创建 KFD 内部的 kfd_bo_va_list 结构                      │
│    - 分配 handle (唯一标识符)                                 │
└────────────────┬────────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────────────┐
│ 3. GPU 页表映射 (如果 va_addr != 0)                           │
│    - 计算物理地址: bo->tbo.resource->start                    │
│    - 更新 GPU 页表: VA → PA 映射                              │
│    - 设置页表项属性(缓存策略、权限等)                           │
└────────────────┬────────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────────────┐
│ 4. 返回 handle                                               │
│    - 用户空间通过 handle 管理该映射                             │
│    - Unmap/Free 时需要提供此 handle                           │
└─────────────────────────────────────────────────────────────┘

关键操作:

  1. 引用计数管理: 防止 BO 在使用时被释放
  2. 页表更新: 建立 GPU 可访问的虚拟地址映射
  3. Handle 生成: 提供用户空间管理接口

3.5 VM 对象创建与注册

c 复制代码
// 转换内存标志
mflags = fmm_translate_ioc_to_hsa_flags(infoArgs.flags);
mflags.ui32.CoarseGrain = 1;  // 强制设置为粗粒度内存

// 创建 VM 对象
obj = aperture_allocate_object(
    aperture,             // 所属 aperture
    mem,                  // 虚拟地址
    importArgs.handle,    // KFD handle
    infoArgs.size,        // 大小
    mflags                // 内存标志
);

关键点 - 强制 CoarseGrain:

c 复制代码
mflags.ui32.CoarseGrain = 1;  // ⚠️ 无条件设置

为什么强制 CoarseGrain:

  1. 图形内存特性: 图形栈分配的内存通常是粗粒度的
  2. 性能考虑: 粗粒度内存带宽更高,适合图形资源
  3. 一致性简化: 避免细粒度一致性维护开销

3.6 填充输出信息

c 复制代码
GraphicsResourceInfo->MemoryAddress = mem;
GraphicsResourceInfo->SizeInBytes = infoArgs.size;
GraphicsResourceInfo->Metadata = (void *)(unsigned long)infoArgs.metadata_ptr;
GraphicsResourceInfo->MetadataSizeInBytes = infoArgs.metadata_size;
hsakmt_gpuid_to_nodeid(infoArgs.gpu_id, &GraphicsResourceInfo->NodeId);

return HSAKMT_STATUS_SUCCESS;

返回信息汇总:

字段 来源 说明
MemoryAddress mem 分配的虚拟地址(ROCm 栈可见)
SizeInBytes infoArgs.size Buffer 大小
Metadata infoArgs.metadata_ptr 元数据指针
MetadataSizeInBytes infoArgs.metadata_size 元数据大小
NodeId gpu_id 转换 HSA 节点 ID

4. 关键技术点总结

4.1 两个 IOCTL 的协同

复制代码
AMDKFD_IOC_GET_DMABUF_INFO          AMDKFD_IOC_IMPORT_DMABUF
        │                                    │
        ├─ 获取 buffer 属性                   ├─ 导入 buffer 到 KFD
        ├─ 验证 DMA-BUF 有效性                ├─ 建立 VA → PA 映射
        ├─ 读取元数据                         ├─ 更新 GPU 页表
        └─ 返回 GPU ID、大小、标志             └─ 返回 KFD handle
                │                                    │
                └────────── 信息传递 ──────────────┘
                    (gpu_id, size, flags)

为什么需要两个 IOCTL:

  1. 职责分离: 查询验证 vs 执行操作
  2. 灵活性: 用户可以先查询再决定是否导入
  3. 性能优化: 批量查询多个 buffer 后一次性导入
  4. 错误处理: 查询失败不影响系统状态

4.2 DMA-BUF 的生命周期管理

复制代码
图形栈                  用户空间 (Thunk)              KFD 内核
  │                          │                         │
  │ amdgpu_bo_export         │                         │
  │─────────────────────────>│                         │
  │ (返回 dmabuf_fd)         │                          │
  │                          │ GET_DMABUF_INFO         │
  │                          │────────────────────────>│
  │                          │                         │ dma_buf_get(fd)
  │                          │                         │ 引用计数 +1
  │                          │<────────────────────────│
  │                          │ (返回 buffer 信息)       │
  │                          │                         │
  │                          │ IMPORT_DMABUF           │
  │                          │────────────────────────>│
  │                          │                         │ dma_buf_get(fd)
  │                          │                         │ 引用计数 +1
  │                          │<────────────────────────│
  │                          │ (返回 handle)            │
  │                          │                         │
  │ close(dmabuf_fd)         │                         │
  │<─────────────────────────│                         │
  │                          │                         │ 引用计数 -1
  │                          │                         │ (仍然 > 0)
  │ amdgpu_bo_free           │                         │
  │<─────────────────────────│                         │
  │                          │                         │ 引用计数 -1
  │                          │                         │ (仍然 > 0)
  │                          │ FREE_MEMORY_OF_GPU      │
  │                          │────────────────────────>│
  │                          │                         │ 引用计数 -1
  │                          │                         │ (降为 0,释放)

关键点:

  • DMA-BUF fd 可以在导入后立即关闭
  • KFD 持有独立的引用,保证 buffer 不会被过早释放
  • 只有所有引用都释放后,物理内存才真正回收

4.3 内存标志的重要性

c 复制代码
// 从 KFD 标志转换
mflags = fmm_translate_ioc_to_hsa_flags(infoArgs.flags);

// ⚠️ 强制设置 CoarseGrain
mflags.ui32.CoarseGrain = 1;

内存标志影响的方面:

  1. 缓存策略: CoarseGrain vs FineGrain
  2. 一致性模型: 是否需要缓存刷新
  3. 访问权限: ReadOnly vs Writable
  4. 性能: 粗粒度内存带宽更高

5. 应用场景示例

场景 1: Vulkan 计算后处理

c 复制代码
// Vulkan 渲染输出
VkDeviceMemory vkMem = RenderScene();

// 导出为 DMA-BUF
int fd = vkGetMemoryFdKHR(vkMem);

// 注册到 ROCm
HsaGraphicsResourceInfo info;
hsakmt_fmm_register_graphics_handle(
    fd, &info, NULL, 0, {0}
);

// HIP kernel 进行后处理
PostProcessKernel<<<...>>>(info.MemoryAddress, ...);

// 继续 Vulkan 渲染(无需拷贝)

场景 2: 视频解码 + AI 分析

c 复制代码
// VA-API 硬件解码
VASurfaceID surface = DecodeH264Frame();

// 导出帧
VADRMPRIMESurfaceDescriptor desc;
vaExportSurfaceHandle(surface, &desc);

// 注册到 ROCm
HsaGraphicsResourceInfo info;
hsakmt_fmm_register_graphics_handle(
     desc.objects[0].fd, &info, NULL, 0, {0}
);

// 直接在 GPU 上运行推理
RunObjectDetection(model, info.MemoryAddress);

6. 总结与改进建议

6.1 核心功能

hsakmt_fmm_register_graphics_handle 函数是 ROCm 图形互操作的关键入口点,通过两个 IOCTL 实现:

  1. GET_DMABUF_INFO: 查询 DMA-BUF buffer 的属性和元数据
  2. IMPORT_DMABUF: 将 buffer 导入到 KFD 并建立虚拟地址映射

6.2 改进建议

纯属在工作中遇到的问题,不一定合理。官方不支持,只能自己加了。

建议 1: 添加性能计数器

c 复制代码
struct {
    uint64_t total_imports;
    uint64_t metadata_reallocs;
    uint64_t avg_import_time_us;
} import_stats;

建议 2: 支持批量导入

c 复制代码
HSAKMT_STATUS hsakmt_fmm_register_graphics_handles_batch(
    HSAuint64 *handles,
    uint32_t count,
    HsaGraphicsResourceInfo *infos
);

建议 3: 异步导入支持

c 复制代码
// 适用于大量小 buffer 的场景
HSAKMT_STATUS hsakmt_fmm_register_graphics_handle_async(
    HSAuint64 handle,
    completion_callback_t callback
);
相关推荐
赖small强1 小时前
【Linux驱动开发】Linux DMA 技术详解与驱动开发实战
linux·dma·直接内存访问
斌蔚司李1 小时前
笔记本、台式机、平板二合一?Mac、Win、Linux?
linux·macos·电脑
Dillon Dong1 小时前
【超详细】Ubuntu 上 MySQL 5.7 升级 MySQL 8 完整指南
linux·mysql·ubuntu
DARLING Zero two♡1 小时前
【Linux操作系统】简学深悟启示录:线程同步与互斥
linux·运维·服务器
hhwyqwqhhwy1 小时前
linux 驱动iic
linux·运维·服务器
知识分享小能手1 小时前
CentOS Stream 9入门学习教程,从入门到精通, CentOS Stream 9中的文件和目录管理(3)
linux·学习·centos
一只努力学习的Cat.1 小时前
Linux:NAPT等其他补充内容
linux·运维·网络
摸鱼仙人~1 小时前
VMware配置从开始踩坑总结-2025最新
linux·ubuntu
做咩啊~1 小时前
CentOS 7部署OpenLDAP+phpLDAPadmin实现统一认证
linux·运维·centos