GPU DMA-BUF 把 GPU allocation 变成可由其他设备使用的共享对象。本文以 AMD GPU 软件栈为主线,说明 HSA 指针如何关联 KFD allocation 与 GEM BO,AMDGPU 如何导出 DMA-BUF,以及 RNIC 注册该 fd 后 attach、pin 与 map 流程。
1. 概述
GPU DMA-BUF 路径包含三个不同问题:exporter 先把 GPU allocation 封装为共享对象;importer 再取得面向自身设备的 DMA mapping;应用最后负责安排 GPU 和 RDMA 的执行顺序。下图分成四段:
hipMalloc()返回 GPU pointer。ROCr 在 allocation map 中定位包含该指针的 allocation,libhsakmt 把指针转换成 KFD handle 和导出对象内 offset。- AMDKFD 由 handle 找到
kgd_mem,再通过 GEM PRIME 导出amdgpu_bo。amdgpu_gem_prime_export()把 DMA-BUF OPS 设置为amdgpu_dmabuf_ops。 - 应用把 fd、offset、length 和 RDMA IOVA 传给
ibv_reg_dmabuf_mr()。RNIC importer 经 attach 和 map 取得 SG/DMA address;仅 pinned 分支显式执行 pin。 - HCA 使用 MR IOVA 和 key 访问映射后的 GPU backing storage。应用以 GPU signal/stream completion 和 RDMA CQ completion 为同步点,在确认前一设备完成访问后再允许另一设备使用 buffer,从而保证 GPU 与 RNIC 之间的访问顺序。

2. 用户态程序
2.1 接口
下面的示例从 ROC_SHMEM 和 RCCL 的实际 DMA-BUF 注册方式中提取主要代码,并用普通 Verbs 表达一次 RDMA WRITE。
| 接口定义 | 功能 |
|---|---|
hipError_t hipMalloc(void **ptr, size_t sizeBytes); |
在当前 HIP device 上分配 GPU memory,成功后把 GPU pointer 写入 ptr。 |
hipError_t hipFree(void *ptr); |
释放 hipMalloc() 返回的 allocation。调用前必须停止使用该 allocation 的 GPU 与 RDMA 工作。 |
hipError_t hipStreamSynchronize(hipStream_t stream); |
等待指定 stream 中此前提交的命令完成;示例用它建立 GPU producer 到 RNIC consumer 的顺序。 |
hsa_status_t hsa_amd_portable_export_dmabuf(const void *ptr, size_t size, int *dmabuf, uint64_t *offset); |
导出包含 [ptr, ptr + size) 的单个 allocation,返回底层共享对象的 fd 及 ptr 在对象中的 offset;对象可能大于应用 allocation。 |
hsa_status_t hsa_amd_portable_export_dmabuf_v2(const void *ptr, size_t size, int *dmabuf, uint64_t *offset, uint64_t flags); |
v2 增加 mapping type flags;HSA_AMD_DMABUF_MAPPING_TYPE_PCIE 用于请求 PCIe mapping。无此要求时使用 NONE,等价于基础接口。 |
hsa_status_t hsa_amd_portable_close_dmabuf(int dmabuf); |
关闭 HSA 导出的 DMA-BUF fd。调用后,调用者保存的整数值不会自动改变。 |
struct ibv_mr *ibv_reg_dmabuf_mr(struct ibv_pd *pd, uint64_t offset, size_t length, uint64_t iova, int fd, int access); |
把 fd 引用的 [offset, offset + length) 注册为 MR,使用 iova 作为数据面逻辑起点;offset 与 iova 的 system-page 内偏移必须相同。 |
int ibv_dereg_mr(struct ibv_mr *mr); |
注销 MR。调用前应用必须保证没有尚未完成的 WR 继续引用该 MR。 |
int ibv_post_send(struct ibv_qp *qp, struct ibv_send_wr *wr, struct ibv_send_wr **bad_wr); |
把发送 WR 提交给 QP;返回成功只表示 WR 已被软件接受,不表示 HCA 已完成访问。 |
int ibv_poll_cq(struct ibv_cq *cq, int num_entries, struct ibv_wc *wc); |
从 CQ 取得 WC。示例等待成功的发送 completion 后才撤销 MR。 |
2.2 简化示例
GPU VA 用作 MR IOVA;VA 须与接口返回的 offset 保持相同页内偏移,不能从应用 allocation 起点自行推算。
c
#include <hip/hip_runtime.h>
#include <hsa/hsa_ext_amd.h>
#include <infiniband/verbs.h>
#include <stdint.h>
#include <stdlib.h>
struct rdma_endpoint {
struct ibv_pd *pd;
struct ibv_qp *qp;
struct ibv_cq *cq;
uint64_t remote_addr;
uint32_t remote_rkey;
};
static int wait_send_completion(struct rdma_endpoint *ep)
{
struct ibv_wc wc;
for (;;) {
int n = ibv_poll_cq(ep->cq, 1, &wc);
if (n < 0)
return -1;
if (n == 0)
continue;
return wc.status == IBV_WC_SUCCESS && wc.wr_id == 1 &&
wc.qp_num == ep->qp->qp_num && wc.opcode == IBV_WC_RDMA_WRITE ? 0 : -1;
}
}
int write_gpu_buffer(struct rdma_endpoint *ep, hipStream_t producer,
size_t length)
{
void *gpu_buf = NULL;
struct ibv_mr *mr = NULL;
int dmabuf_fd = -1;
uint64_t dmabuf_offset = 0;
int rc = -1;
if (length == 0 || length > UINT32_MAX)
return -1;
if (hipMalloc(&gpu_buf, length) != hipSuccess)
goto out;
/* 真实程序先在 producer stream 中生成 gpu_buf 的内容。 */
if (hipStreamSynchronize(producer) != hipSuccess)
goto out;
if (hsa_amd_portable_export_dmabuf(gpu_buf, length,
&dmabuf_fd,
&dmabuf_offset) != HSA_STATUS_SUCCESS)
goto out;
mr = ibv_reg_dmabuf_mr(ep->pd, dmabuf_offset, length,
(uint64_t)(uintptr_t)gpu_buf,
dmabuf_fd,
IBV_ACCESS_LOCAL_WRITE |
IBV_ACCESS_REMOTE_READ |
IBV_ACCESS_REMOTE_WRITE);
if (mr == NULL)
goto out;
struct ibv_sge sge = {
.addr = (uint64_t)(uintptr_t)gpu_buf,
.length = (uint32_t)length,
.lkey = mr->lkey,
};
struct ibv_send_wr wr = {
.wr_id = 1,
.sg_list = &sge,
.num_sge = 1,
.opcode = IBV_WR_RDMA_WRITE,
.send_flags = IBV_SEND_SIGNALED,
.wr.rdma.remote_addr = ep->remote_addr,
.wr.rdma.rkey = ep->remote_rkey,
};
struct ibv_send_wr *bad_wr = NULL;
if (ibv_post_send(ep->qp, &wr, &bad_wr) != 0)
goto out;
if (wait_send_completion(ep) != 0)
abort();
rc = 0;
out:
if (mr != NULL && ibv_dereg_mr(mr) != 0)
abort();
if (dmabuf_fd >= 0)
hsa_amd_portable_close_dmabuf(dmabuf_fd);
if (gpu_buf != NULL)
hipFree(gpu_buf);
return rc;
}
3. HSA 与 KFD
3.1 Allocation 查找
HIP allocation 最终登记在 ROCr 的 allocation map 中。Runtime::DmaBufExport() 使用 upper_bound(ptr) 找到不大于 ptr 的候选记录,再检查 ptr 和 ptr + size 是否都位于同一 allocation。这里的关键结果不是重新创建 BO,而是确认 allocation base、allocation size 和 ptr 的相对位置。
hsa_amd_portable_export_dmabuf() 支持 allocation 子区间。fd 引用底层共享对象,offset 是 ptr 相对该对象的偏移;存在子分配时,对象起点不等于应用 allocation 起点。注册应使用接口返回的 offset。
3.2 导出调用
下图展示从 HSA API 到 KFD ioctl 的调用层次。图中省略 API table wrapper,保留指针查找、handle 转换和 ioctl 三个改变对象语义的节点。
hsa_amd_portable_export_dmabuf()只做 API 参数检查,然后进入Runtime::DmaBufExport()。- ROCr 查找 allocation 并检查 GPU agent;libhsakmt 再从 aperture 找到
vm_object_t,计算子区间 offset,并取obj->handles[0]。 AMDKFD_IOC_EXPORT_DMABUF接收的是 KFD handle,不是原始 GPU VA;用户指针到 handle 的转换已经在 libhsakmt 完成。

3.3 KFD 对象
kfd_ioctl_export_dmabuf() 根据 handle 中的 GPU id 选择 kfd_process_device,再通过 IDR handle 取得 kgd_mem。kgd_mem 是 KFD 对 allocation 的内核描述,主线相关字段如下。
c
struct kgd_mem {
/* allocation 对应的 AMDGPU BO */
struct amdgpu_bo *bo;
/* 已缓存的导出对象 */
struct dma_buf *dmabuf;
/* allocation 的 GPU VA 起点 */
uint64_t va;
/* VRAM/GTT 等允许或当前 domain 信息 */
uint32_t domain;
/* KFD DRM client 中引用同一 GEM object 的 handle */
uint32_t gem_handle;
/* 可写、显存类型等 allocation 属性 */
uint32_t alloc_flags;
};
KFD handle 用于进程内核对象查找,gem_handle 用于 DRM PRIME 导出,DMA-BUF fd 用于跨子系统共享;三者都是句柄,但所属命名空间和生命周期不同。
4. AMDGPU 导出
4.1 BO 与 GEM
kgd_mem->bo 指向 amdgpu_bo,其 TTM base 同时承载 drm_gem_object。KFD allocation 因而可以通过 gem_handle 进入通用 DRM PRIME 导出,而不需要复制 VRAM/GTT 内容。
kfd_mem_export_dmabuf() 只在 mem->dmabuf 为空时创建导出对象。重复导出复用缓存对象,amdgpu_amdkfd_gpuvm_export_dmabuf() 再执行 get_dma_buf(),为当前调用方返回独立引用。
4.2 PRIME 导出
下图说明 KFD allocation 如何通过 GEM 和 DRM PRIME 导出为 DMA-BUF,以及 AMDGPU object-specific exporter 与通用 exporter 的关系。
- KFD handle 同时编码 GPU ID 和 allocation handle;
kfd_device_by_id()确定设备,kfd_process_device_translate_handle()以进程设备上下文中的alloc_idr为边界,将其转换为kgd_mem。 - 导出路径包含 allocation 级
mem->dmabuf、GEM object 级obj->dma_buf和drm_file级 PRIME handle cache。amdgpu_amdkfd_gpuvm_export_dmabuf()与drm_gem_prime_handle_to_dmabuf()使重复导出复用同一 DMA-BUF,同时为每个调用方建立独立引用。 export_and_register_object()把 object-specific exporter 作为优先策略。AMDGPU 路径通过obj->funcs->export()进入amdgpu_gem_prime_export(),完成 TTM export 准备并安装amdgpu_dmabuf_ops;通用 fallback 则保留drm_gem_prime_export()创建的默认 ops。dma_buf_fd()成功后,导出引用由 fd 接管;如果 fd 安装失败,kfd_ioctl_export_dmabuf()必须通过dma_buf_put()撤销该引用。因此 fd 承载跨子系统传递的 DMA-BUF 引用,而不是另一个 GPU allocation。

4.3 Exporter 操作表
amdgpu_dmabuf_ops 把通用 DMA-BUF 生命周期连接到 AMDGPU BO:
c
const struct dma_buf_ops amdgpu_dmabuf_ops = {
.attach = amdgpu_dma_buf_attach,
.pin = amdgpu_dma_buf_pin,
.unpin = amdgpu_dma_buf_unpin,
.map_dma_buf = amdgpu_dma_buf_map,
.unmap_dma_buf = amdgpu_dma_buf_unmap,
.release = drm_gem_dmabuf_release,
.begin_cpu_access = amdgpu_dma_buf_begin_cpu_access,
.mmap = drm_gem_dmabuf_mmap,
.vmap = amdgpu_dma_buf_vmap,
.vunmap = amdgpu_dma_buf_vunmap,
};
当前操作表没有 AMDGPU 专用 .detach。detach 仍然是完整生命周期的一步,但 DMA-BUF Core 只从 attachment list 删除对象并释放 attachment;AMDGPU 的设备映射清理发生在 unmap/unpin。
5. AMDGPU DMA-BUF
5.1 Attach、Pin 与 Map
下图从 RDMA Core 的 DMA-BUF 内存获取入口展开 exporter 回调。图中展示 pinned/KSM 分支;普通 MTT 分支跳过 dma_buf_pin(),但复用相同的 attach 和 map 机制。
- Attach 确定当前 importer 能否直接访问 VRAM,但不在此时改变 BO placement。
dma_buf_dynamic_attach()以 importer 的allow_peer2peer初始化attach->peer2peer,amdgpu_dma_buf_attach()再按 buffer 属性和设备拓扑检查该能力:- 允许 P2P 需同时满足三个条件:importer 声明
allow_peer2peer=true;BO 不是 GFX12+ 上启用AMDGPU_GEM_CREATE_GFX12_DCC的 surface;amdgpu_dmabuf_is_xgmi_accessible()或pci_p2pdma_distance() >= 0证明存在可达路径。 - mlx5 attachment 的
dma_buf_attach_adev()返回NULL,所以 XGMI 判断恒为不可达;它只有在pci_p2pdma_distance()返回非负值时才保留 P2P 能力。 - importer 不允许 P2P、对象为 GFX12+ DCC surface,或 XGMI 与 PCI P2P 都不可达时,AMDGPU 只清除
attach->peer2peer,attachment 本身仍可建立。amdgpu_vm_bo_update_shared()遍历bo->vm_bo,通过amdgpu_vm_update_shared()把相关 VM 的内存统计从 private 更新为 shared,不负责 BO 迁移或 DMA mapping。
- 允许 P2P 需同时满足三个条件:importer 声明
- Pin 综合全部 attachments 的能力,确定 BO 可固定的位置。
amdgpu_dma_buf_pin()只有在所有 attachments 都保留 P2P 时才允许 VRAM domain;任一 attachment 不支持 P2P 就排除 VRAM,并在 allowed domains 包含 GTT 时固定到 GTT。amdgpu_bo_pin()对已固定且 domain 兼容的 BO 只增加pin_count,不发生迁移;首次固定则由amdgpu_bo_get_preferred_domain()选择 domain,amdgpu_bo_placement_from_domain()生成 placement,ttm_bo_validate()在当前 resource 不满足 placement 时执行迁移,最后由ttm_bo_pin()固定位置。 - Map 按 BO 的实际 TTM resource 生成面向 RNIC 的 SG/DMA address。已 pin 的 BO 直接映射当前 resource,不再迁移;普通 MTT 分支的 BO 若尚未 pin,
amdgpu_dma_buf_map()以 GTT 为基本候选,只在 BO 偏好 VRAM 且 attachment 保留 P2P 时加入 VRAM,ttm_bo_validate()可能因此迁移 BO。TT resource 通过drm_prime_pages_to_sg()与dma_map_sgtable()映射 system pages;VRAM 通过amdgpu_vram_mgr_alloc_sgt()与dma_map_resource()映射 VRAM aperture;MMIO remap 则由amdgpu_ttm_mmio_remap_alloc_sgt()生成 I/O resource mapping。 ib_umem_dmabuf_map_pages()在 exporter map 之后按 DMA-BUF offset/length 裁剪 SG,并等待 migration fence。这个边界保证 MR 使用的是已定位的 backing storage。

5.2 P2P 与 GTT
AMDGPU 的策略不是"无法 P2P 就注册失败",而是优先选择仍可被 importer 访问的位置。amdgpu_dma_buf_pin() 在所有 attachments 都允许 P2P 时可以把 BO 固定在 VRAM;若任一 attachment 清除了 peer2peer,VRAM 会从 pin domain 中移除,剩余可用位置为 GTT 时就固定到 GTT。部分 AMD DKMS 兼容代码还会在内核缺少 DMA-BUF move-notify 支持时直接排除 VRAM,避免 RDMA 与 GPU 对可移动 BO 的固定要求发生冲突。
amdgpu_dma_buf_map() 对未固定 BO 采用相似策略:GTT 始终是基本候选,VRAM 只有在 BO 偏好 VRAM 且 attachment 允许 P2P 时才加入候选。最终位置仍由 TTM validation、BO allowed/preferred domains 和资源状态共同决定,所以"可能回退到 GTT"比"必然复制到 GTT"更准确。
5.3 begin_cpu_access
amdgpu_dma_buf_begin_cpu_access() 不是 RNIC 注册路径的一部分,而是 DMA-BUF 被 CPU 访问前的 exporter 回调。它有两类直接调用来源:用户态对 DMA-BUF fd 发出 DMA_BUF_IOCTL_SYNC,由 dma_buf_ioctl() 转入 dma_buf_begin_cpu_access();内核消费者也可以直接调用该通用接口。
dma_buf_begin_cpu_access()先调用amdgpu_dmabuf_ops.begin_cpu_access。AMDGPU 只有在访问方向包含 CPU 读取且 BO 支持 GTT 时才考虑改变 placement;纯写访问或不支持 GTT 的 BO 保持原位置。- 需要优化 CPU 读取时,回调 reserve BO。已 pin 的 BO 不能迁移;未 pin 且
allowed_domains包含 GTT 的 BO 才会生成 GTT placement,并由ttm_bo_validate()在必要时迁移,最后解除 reserve。 - exporter 回调成功后,DMA-BUF Core 再通过
dma_resv_wait_timeout()等待隐式同步 fence。该等待不覆盖显式同步事务,CPU 实际读写仍应与dma_buf_end_cpu_access()成对出现。

5.4 mmap
drm_gem_dmabuf_mmap() 服务于用户态 VMA:用户对 DMA-BUF fd 执行 mmap() 时由 dma_buf_mmap_internal() 进入,importer 已持有 dma_buf 时也可以调用 dma_buf_mmap() helper。两种入口完成范围检查后都调用 exporter 的 .mmap,再由该 DRM helper 转入 drm_gem_prime_mmap()。
drm_gem_prime_mmap()先加入 GEM VMA offset,再在两条 mmap 路径中二选一。对象提供.mmap时直接调用具体的amdgpu_gem_object_mmap();否则构造临时 DRM file,调用obj->dev->driver->fops->mmap(),AMDGPU 的入口是drm_gem_mmap()。AMDGPU BO 实际采用前一条路径,后一条是 DRM PRIME 的兼容分支。amdgpu_gem_object_mmap()把"禁止 CPU 映射"和"继续建立 TTM VMA"作为互斥结果:usermm BO 或带AMDGPU_GEM_CREATE_NO_CPU_ACCESS的 BO 返回-EPERM;其余 BO 才经drm_gem_ttm_mmap()进入ttm_bo_mmap_obj()。图中省略了仅做 GEM→TTM 转换的 helper。ttm_bo_mmap_obj()不立即映射 backing pages,而是增加 BO 引用、把 BO 保存到vm_private_data,并保留drm_gem_prime_mmap()已安装的amdgpu_gem_vm_ops;真正的数据页映射推迟到 CPU page fault。- Page fault 调用
amdgpu_gem_fault():先 reserve BO,amdgpu_bo_fault_reserve_notify()保证 placement 对 CPU 可见;不可见且未 pin 的 VRAM BO 可由ttm_bo_validate()移到 CPU-visible VRAM 或 GTT,已 pin 的不可见 BO 返回SIGBUS。随后ttm_bo_vm_fault_reserved()对 I/O resource 使用ttm_bo_io_mem_pfn(),对 system pages 使用page_to_pfn(),最终由vmf_insert_pfn_prot()把 PFN 写入 CPU 页表。

5.5 vmap
amdgpu_dma_buf_vmap() 由 DMA-BUF Core 的 dma_buf_vmap() 调用,面向需要连续内核虚拟地址的内核消费者;dma_buf_vmap_unlocked() 只是先取得 dma_resv 锁再调用它。通过该映射访问数据时,仍需用 dma_buf_begin_cpu_access()/dma_buf_end_cpu_access() 建立一致性边界。
dma_buf_vmap()先检查 DMA-BUF 的 vmap cache。已有映射时只增加vmapping_counter并复用vmap_ptr,因此amdgpu_dma_buf_vmap()只在首次映射时执行。- AMDGPU 先以
GTT | VRAM调用amdgpu_bo_pin(),目标是固定一个可映射位置,并不强制迁移到 GTT。随后drm_gem_dmabuf_vmap()通过对象回调进入drm_gem_ttm_vmap();图中省略只负责校验和转发的drm_gem_vmap_locked()。ttm_bo_vmap()对 I/O resource 复用mem->bus.addr,或按 cache 属性调用ioremap_wc()、ioremap_cache()、ioremap();对 system pages 则先执行ttm_bo_populate(),再用vmap()建立连续的内核虚拟地址映射。 - 映射失败时会立即调用
amdgpu_bo_unpin()。正常路径在最后一个dma_buf_vunmap()释放缓存映射时进入amdgpu_dma_buf_vunmap(),先撤销 TTM 映射,再解除 BO pin。

6. 对象与地址
共享路径同时出现对象句柄、虚拟地址、对象内偏移和设备地址。下图按使用顺序,介绍这些概念。

| 名称 | 产生者与作用域 | 使用者 | 语义 |
|---|---|---|---|
| HSA pointer / GPU VA | HIP/ROCr 进程 GPU VA 空间 | GPU 指令、ROCr allocation map、应用 | 指向 allocation 内的某个字节,不一定是 allocation base。 |
| KFD handle | libhsakmt 保存的 allocation 对象 | AMDKFD ioctl | 在 KFD 进程/设备对象表中定位 kgd_mem,不是数据面地址。 |
amdgpu_bo / GEM object |
AMDKFD、AMDGPU、DRM | TTM、GEM PRIME、DMA-BUF exporter | 表示 backing storage 及其 placement、reservation 与引用。 |
| VRAM/GTT | TTM placement | GPU、AMDGPU、HCA | 表示 BO 当前或允许的内存位置,不是另一套应用虚拟地址。 |
| DMA-BUF fd | dma_buf_fd() 安装到进程 fd table |
用户态传递、dma_buf_get() |
引用共享对象;关闭 fd 只释放 fd 自身引用。 |
| DMA-BUF offset | libhsakmt 返回的对象内偏移 | ibv_reg_dmabuf_mr()、SG 裁剪 |
在底层共享对象内定位注册起点。 |
| DMA address | exporter 与 DMA API 面向 attachment device 生成 | RNIC DMA/IOMMU、MR 地址翻译 | SG 中供 RNIC 使用的设备地址;启用 IOMMU 转换时再映射到目标物理地址。 |
| RDMA IOVA | 应用传入 Verbs | SGE、远端请求、MKey 地址检查 | MR 的逻辑地址起点,不等于 DMA-BUF offset,也不等于 DMA address。 |
lkey / rkey |
RNIC MR 注册结果 | 本地 SGE 或远端请求 | 与 RDMA IOVA 一起选择 MR、权限和地址转换。 |
设 HSA 返回的 offset 为 d,注册长度为 L、iova = V,则区间内地址 V + n 对应共享对象偏移 d + n。d 不一定是相对应用 allocation 的偏移;RNIC 通过 MR 翻译将 IOVA 区间映射到 SG 的 DMA 地址段。
7. P2P 数据访问
7.1 两条路径
下图并列展示 VRAM P2P 与 GTT 路径。两条路径对应用暴露相同的 DMA-BUF MR 和 IOVA,但 HCA 最终访问的位置不同。

VRAM 路径要求 attachment 保持 peer2peer,BO 允许并偏好 VRAM,且平台能为 RNIC 建立有效 P2P DMA address。HCA 经 PCIe P2P 访问 GPU VRAM,CPU 不参与业务数据复制。AMDGPU 对 GFX12 DCC surface 主动禁止这一路径,因为设备本地压缩元数据不适合普通 peer 直接访问。
GTT 路径使用 system pages。GPU 通过 GART/GPUVM 访问这些页,RNIC 则通过主机 DMA mapping 访问同一 backing storage。它通常适用于更广泛的拓扑,但数据位置和性能特征不再等同于直接访问 VRAM。
7.2 拓扑条件
源码直接使用 pci_p2pdma_distance() 判断 importer device 与 exporter GPU 的 P2P 可达性。实际部署还需要同时检查 PCIe root complex、switch、ACS、IOMMU mode、BAR/peer aperture、DMA mask、GPU/RNIC 驱动和固件能力。仅凭 IOMMU 或 ACS 的存在不能决定结果;应以平台形成的实际 P2P mapping 和 exporter 返回的 SG 为准。
8. 同步边界
8.1 Reservation 与 Fence
AMDGPU GEM BO 的 reservation object 同时作为导出 DMA-BUF 的 dma_resv。attachment、pin、map 和 mapping invalidation 使用该锁保护 placement 与映射变化;ib_umem_dmabuf_map_pages() 在得到 SG 后等待 DMA_RESV_USAGE_KERNEL fence,确保 exporter 的迁移完成,SG 指向的 backing storage 已经稳定。
8.2 GPU 与 RDMA
下图使用 UML 时序图显示两种所有权的交接顺序。参与者从左到右为 GPU stream、应用线程、RNIC/HCA 和远端应用,时间自上而下推进。

GPU producer→RNIC consumer 的安全顺序是:GPU kernel 写入数据,应用观察 HSA signal、HIP event 或 stream completion,再提交引用该 MR 的 WR。RNIC producer→GPU consumer 的安全顺序是:HCA 完成本地 DMA,应用取得相应 WC,再把依赖传入 GPU stream 或在 CPU 侧提交后续 kernel。
CQ completion 的精确含义取决于 opcode 和本地/远端角色。发送侧 RDMA WRITE completion 允许本端复用源 buffer,但不能保证远端已经收到数据。
9. 释放与生命周期
9.1 释放调用
下图聚焦 MR importer 的清理分支;fd close 与 allocation free 是独立入口。三类引用可以在不同时间消失,最终通过 DMA-BUF/GEM 引用计数汇合。
- 应用先停止提交新的 WR,并等待引用 MR 的 GPU/RDMA 工作完成。
ibv_dereg_mr()进入 RNIC 注销路径,最终由 RDMA DMA-BUF umem 执行 revoke、unmap、detach 和dma_buf_put();pinned 分支还执行 unpin。- fd close 释放 fd table 持有的 DMA-BUF 引用;它可以早于 MR 注销,但不能替代 MR 注销。
hipFree()释放应用/KFD allocation 引用;DMA-BUF、GEM 或 importer 仍持有引用时,backing memory 要等到最后一个引用消失后才释放。

9.2 Importer 清理
ib_umem_dmabuf_release() 先在 reservation lock 下调用 revoke。revoke 撤销 SG mapping,并在 pinned 状态下调用 dma_buf_unpin();随后 dma_buf_detach() 删除 attachment,dma_buf_put() 释放 importer 的共享对象引用。
AMDGPU 的 unmap 对 GTT SG 调用 dma_unmap_sgtable() 并释放 SG,对 VRAM SG 调用 VRAM manager 的 free 接口;unpin 则调用 amdgpu_bo_unpin(),允许 BO 再次移动。AMDGPU 没有 .detach 回调,因此 detach 本身只完成 DMA-BUF Core attachment 管理。
9.3 BO 回收
fd、kgd_mem->dmabuf cache、每个 importer 和 GEM object 都可能持有独立引用。关闭 fd 不会使仍在使用的 MR 失效;注销 MR 也不会自动关闭应用 fd;hipFree() 不保证 backing storage 立即消失。drm_gem_dmabuf_release() 释放 DMA-BUF 对 GEM object 的引用,只有 KFD/GEM、fd 和 importer 引用全部消失,BO 才具备最终回收条件。
错误回滚遵循相反顺序:map 失败先撤销已建立的 mapping/pin,attachment 创建后若发生失败,则执行 detach/put,用户态注册失败时,关闭 fd 并释放 allocation。示例正常回收时进入 out,无法确认完成或注销失败时终止进程,不继续释放 buffer。
10. NVIDIA DMA-BUF
10.1 导出
CUDA Driver API 提供以下 DMA-BUF 导出接口:
| 接口定义 | 功能 |
|---|---|
CUresult cuMemGetHandleForAddressRange(void *handle, CUdeviceptr dptr, size_t size, CUmemRangeHandleType handleType, unsigned long long flags); |
当 handleType 为 CU_MEM_RANGE_HANDLE_TYPE_DMA_BUF_FD 时,把 fd 写入 handle。dptr 和 size 必须按 host page 对齐;应用应先查询 CU_DEVICE_ATTRIBUTE_DMA_BUF_SUPPORTED。 |
int close(int fd); |
关闭 CUDA 返回的 DMA-BUF fd;CUDA 接口没有与 AMD HSA close 对应的专用关闭入口。 |
当前 CUDA 文档还定义了 CU_MEM_RANGE_FLAG_DMA_BUF_MAPPING_TYPE_PCIE:支持的平台可要求经 PCIe BAR1 建立 DMA-BUF,否则接口返回错误。NVSHMEM 在 data-direct 模式使用该 flag,但在普通模式把 flags 设为 0。
导出之后,普通 RNIC 路径仍调用同一个 ibv_reg_dmabuf_mr(pd, offset, length, iova, fd, access)。NVSHMEM 先把 pointer 向下对齐到 host page,把 size 向上取整,然后以 offset 0 注册对齐后的范围;AMD HSA 接口则直接返回原 pointer 在导出的 DMA-BUF 对象中的 offset。
10.2 部署差异
| 项目 | AMD ROCm | NVIDIA CUDA |
|---|---|---|
| Allocation | hipMalloc() 或 HSA memory pool |
cuMemAlloc(),或 reserve/map 完整覆盖的 VMM range |
| 导出接口 | hsa_amd_portable_export_dmabuf() |
cuMemGetHandleForAddressRange() |
| 用户态层次 | ROCr → libhsakmt → /dev/kfd |
CUDA Driver → NVIDIA kernel driver |
| Exporter | AMDKFD/amdgpu GEM PRIME | NVIDIA GPU driver DMA-BUF exporter |
| Offset | 接口返回导出的 DMA-BUF 对象内 offset | 常见用法先做 host-page 对齐,再导出对齐 range |
| fd 关闭 | hsa_amd_portable_close_dmabuf() 或等价 close |
close() |
| RNIC 接口 | ibv_reg_dmabuf_mr() |
ibv_reg_dmabuf_mr() |
| P2P 请求 | v2 API 可带 HSA_AMD_DMABUF_MAPPING_TYPE_PCIE;AMDGPU 再按 attachment/topology 选择 placement |
可使用 CU_MEM_RANGE_FLAG_DMA_BUF_MAPPING_TYPE_PCIE 请求 PCIe BAR1 mapping |
| 传统路径 | 依赖 ROCm/网络库的普通 MR 或其他 peer-memory 路径 | legacy nvidia-peermem/peer-memory 路径 |
DMA-BUF 与 nvidia-peermem 是两种部署路径。DMA-BUF 依赖 CUDA/驱动暴露 exporter 和 Linux DMA-BUF importer;legacy peer-memory 由 nvidia-peermem 向 RDMA peer-memory client 提供页面映射。应用应按 CUDA device attribute、Verbs symbol、内核驱动模式和平台拓扑做运行时探测,不能仅以 CUDA 版本号推断可用性。
11. 总结
- GPU 内存可直接导出为 DMA-BUF,无需复制内容;fd 引用共享对象,接口返回的 offset 定位注册区间。
- DMA-BUF 为 GPU exporter 与 RNIC importer 提供统一接口。AMD 通过
amdgpu_dmabuf_ops支持 attachment、pin 和 DMA mapping,NVIDIA 通过 CUDA Driver API 导出 fd;两者都可使用ibv_reg_dmabuf_mr()注册 MR,但能力判断和部署依赖不同。 - RNIC 直接访问 VRAM 取决于 BO 属性、attachment 能力和 PCIe 拓扑。P2P 条件满足时可以建立 VRAM SG;条件不满足且 BO 允许 GTT 时可以回退到 GTT,但非 P2P allocation 不一定能够注册成功。
- GPU VA、DMA-BUF offset、DMA address 和 RDMA IOVA 属于不同地址空间。MR key、IOVA 和 SG 共同建立 RDMA 地址到 GPU allocation backing storage 的映射。
begin_cpu_access、mmap和vmap分别承担 CPU 访问同步、用户态映射和内核态映射,与 RNIC 注册路径相互独立。dma_resv/fence、GPU stream/HSA signal 和 Work Completion 分别约束内存迁移、GPU 工作和 RDMA 操作。应用必须显式连接这些同步域,才能安全地在 GPU 与 RNIC 之间交接 buffer 所有权。- MR、attachment、DMA-BUF fd 和 GPU allocation 持有独立引用。相关 GPU/RDMA 工作完成后才能撤销这些资源;只有全部引用都已释放,AMDGPU BO 及其 backing storage 才能最终回收。
12. 参考资料
12.1 源码位置
| 功能 | 目录 | 文件 |
|---|---|---|
| HSA DMA-BUF API 与 ROCr 导出 | rocm-systems/projects/rocr-runtime/runtime/hsa-runtime/ | inc/hsa_ext_amd.h、core/runtime/hsa_ext_amd.cpp、core/runtime/runtime.cpp |
| libhsakmt pointer/handle 转换 | rocm-systems/projects/rocr-runtime/libhsakmt/ | src/memory.c、src/fmm.c、include/hsakmt/hsakmt.h |
| AMDKFD ioctl 与 GPUVM 对象 | linux/drivers/gpu/drm/amd/ | amdkfd/kfd_chardev.c、amdgpu/amdgpu_amdkfd.h、amdgpu/amdgpu_amdkfd_gpuvm.c |
| AMDGPU exporter 与 P2P mapping | linux/drivers/gpu/drm/amd/amdgpu/ | amdgpu_dma_buf.c、amdgpu_dma_buf.h |
| DRM PRIME 与 DMA-BUF Core | linux/drivers/gpu/drm/、linux/drivers/dma-buf/ | drm_prime.c、dma-buf.c、dma-resv.c |
| Verbs DMA-BUF 接口 | rdma-core/libibverbs/、rdma-core/providers/mlx5/ | verbs.h、verbs.c、cmd_mr.c、providers/mlx5/verbs.c |
| RDMA DMA-BUF attachment | linux/drivers/infiniband/core/ | umem_dmabuf.c |
| AMD 应用用法 | rocm-systems/projects/rocshmem/src/gda/、rocm-systems/projects/rccl/src/include/ | ibv_wrapper.cpp、provider_gda_mlx5.cpp、rocmwrap.h |
| NVIDIA 应用与 exporter 对照 | nvshmem/src/modules/transport/、open-gpu-kernel-modules/kernel-open/nvidia-drm/ | common/transport_ib_common.cpp、ibgda/ibgda.cpp、nvidia-drm-gem-dma-buf.c |
12.2 官方文档
- Linux Kernel Documentation,Buffer Sharing and Synchronization:linux/Documentation/driver-api/dma-buf.rst。
- Linux Kernel Documentation,PCI Peer-to-Peer DMA Support:linux/Documentation/driver-api/pci/p2pdma.rst。
- AMD ROCr Runtime,
hsa_ext_amd.h中hsa_amd_portable_export_dmabuf()的接口说明。 - NVIDIA,CUDA Driver API --- Memory Management :https://docs.nvidia.com/cuda/cuda-driver-api/cuda_driver_api/group__CUDA__MEM.html。
- NVIDIA,GPUDirect RDMA :https://docs.nvidia.com/cuda/gpudirect-rdma/。