3. DMA-BUF 框架入门:核心数据结构、导出与导入机制、文件描述符与共享语义

DMA-BUF 是 Linux 内核中用于在设备驱动(如 GPU、VPU、Display Controller)与用户空间之间共享连续物理内存或 IOMMU/IOMMU 映射内存的核心框架。在全志 DRM 移植中,DMA-BUF 是连接显示控制器(DE2/DE3)与 GPU/VPU 的“内存管道”。本章从数据结构、导出/导入流程、文件描述符共享三个维度,拆解 DMA-BUF 的底层机制。

关键认知: DMA-BUF 不是一种新的内存分配器,而是一种内存共享与同步契约。它通过文件描述符(fd)将一段物理内存的访问权跨驱动、跨进程传递,并配合 fence 机制保证访问顺序。

3.1 核心数据结构:dma_buf 与 dma_buf_attachment

DMA-BUF 框架围绕三个核心结构体展开,它们定义在 include/linux/dma-buf.h 中:

结构体角色关键字段
struct dma_buf共享内存的“对象句柄”,对用户空间暴露 fdsize, file, attachments 链表, ops
struct dma_buf_attachment一个设备(如 DRM 驱动)对 dma_buf 的“绑定”dev, dmabuf, priv, sgt
struct sg_table物理内存的分散/聚合表,描述 DMA 地址与长度sgl, nents, orig_nents
全志平台提示: 全志 DE2/DE3 的显示引擎通常需要连续物理内存(或经过 IOMMU 映射的连续 IOVA)。在导出 DMA-BUF 时,sg_table 中的 dma_address 必须与显示控制器的地址窗口对齐(通常 4KB 对齐,部分模块要求 64KB 对齐)。
DMA-BUF 核心数据结构关系 struct dma_buf size, file, priv, ops attachments 链表头 用户空间 fd 导出者驱动 dma_buf_attachment dev, dmabuf, priv sgt (sg_table) 导入者驱动 sg_table sgl, nents, dma_address 关键关系 1 个 dma_buf 可被多个 attachment 绑定 每个 attachment 持有独立 sgt

3.2 导出机制:从分配器到文件描述符

导出(Export)是 DMA-BUF 生命周期的起点。一个典型的导出流程如下:

  1. 分配物理内存:通过 dma_alloc_coherent()、ion_alloc() 或 DRM GEM 的 drm_gem_cma_create() 获得物理内存。
  2. 构造 dma_buf:调用 dma_buf_export(),传入 struct dma_buf_export_info,其中包含 ops(回调函数集)、size、priv(私有数据,通常指向 GEM 对象)。
  3. 获取文件描述符:dma_buf_export() 返回 struct dma_buf *,再通过 dma_buf_fd() 将其转换为用户空间可用的 fd。
  4. 用户空间传递 fd:通过 ioctl 参数或 IPC 机制(如 dma-heap 的 DMA_HEAP_IOCTL_ALLOC)将 fd 传递给消费者。
// 导出者驱动中的典型代码片段
struct dma_buf *dmabuf;
struct dma_buf_export_info exp_info = {
    .exp_name = "sunxi-drm",
    .ops = &sunxi_dmabuf_ops,   // 包含 attach, detach, map_dma_buf, release 等回调
    .size = gem_obj->size,
    .flags = O_RDWR,
    .priv = gem_obj,            // 指向 DRM GEM 对象
};

dmabuf = dma_buf_export(&exp_info);
if (IS_ERR(dmabuf))
    return PTR_ERR(dmabuf);

// 转换为 fd 返回用户空间
int fd = dma_buf_fd(dmabuf, O_CLOEXEC);
全志调试要点: 在导出时,务必检查 dma_buf_export_info.ops 中的 map_dma_buf 回调。全志显示控制器通常需要物理地址连续的 sg_table,如果 map_dma_buf 返回的 sgt 中 dma_address 不连续,DE2/DE3 的图层地址寄存器将无法正确配置,导致显示花屏或黑屏。
DMA-BUF 导出流程 1. 分配物理内存 dma_alloc_coherent 2. 构造 dma_buf dma_buf_export() 3. 获取 fd dma_buf_fd() 4. 传递 fd ioctl / IPC 内核空间 用户空间持有 fd mmap() 映射到用户态 通过 fd 传给其他驱动

3.3 导入机制:从文件描述符到设备访问

导入(Import)是消费者驱动(如全志 DRM 的 KMS 驱动)获取 DMA-BUF 访问权的步骤。导入者不拥有内存,但可以通过 dma_buf_attach() 和 dma_buf_map_attachment() 获得设备可访问的 DMA 地址。

// 导入者驱动中的典型代码片段
// 从用户空间获取 fd
int fd = args->fd;

// 通过 fd 获取 dma_buf 对象(增加引用计数)
struct dma_buf *dmabuf = dma_buf_get(fd);
if (IS_ERR(dmabuf))
    return PTR_ERR(dmabuf);

// 绑定到本设备(创建 attachment)
struct dma_buf_attachment *attach = dma_buf_attach(dmabuf, dev);
if (IS_ERR(attach)) {
    dma_buf_put(dmabuf);
    return PTR_ERR(attach);
}

// 获取 DMA 映射后的 sg_table(物理地址列表)
struct sg_table *sgt = dma_buf_map_attachment(attach, DMA_TO_DEVICE);
if (IS_ERR(sgt)) {
    dma_buf_detach(dmabuf, attach);
    dma_buf_put(dmabuf);
    return PTR_ERR(sgt);
}

// 使用 sgt->sgl 中的 dma_address 配置显示控制器
// ... 配置 DE2/DE3 图层寄存器 ...

// 使用完毕后解映射
dma_buf_unmap_attachment(attach, sgt, DMA_TO_DEVICE);
dma_buf_detach(dmabuf, attach);
dma_buf_put(dmabuf);
引用计数管理: dma_buf_get() 增加引用计数,dma_buf_put() 减少引用计数。忘记 dma_buf_put() 会导致内存泄漏;过早释放则导致悬空指针。在全志平台调试时,可通过 /sys/kernel/debug/dma_buf/bufinfo 查看当前所有活跃的 dma_buf 及其引用计数。

3.4 文件描述符与共享语义

DMA-BUF 的文件描述符(fd)是跨进程、跨驱动共享的核心载体。其共享语义基于以下机制:

机制说明全志平台应用场景
fd 传递通过 Unix Domain Socket 或 ioctl 参数传递 fdCamera 驱动将预览帧 fd 传给 DRM 驱动做 overlay
引用计数每个 fd 对应内核中 struct file,dma_buf_get/put 管理生命周期VPU 解码完成后释放 fd,DRM 驱动仍可安全使用
mmap 共享用户空间可通过 mmap(fd, ...) 直接访问 DMA-BUF 内存CPU 直接修改 framebuffer 内容(如 UI 合成)
Fence 同步DMA-BUF 可附加 dma_fence,保证读写顺序GPU 渲染完成后通知 DE2 开始扫描输出
DMA-BUF 文件描述符共享语义 struct dma_buf refcount = 3 fd = 7, 9, 12 进程 A (Camera) fd=7, mmap 写入 进程 B (DRM) fd=9, 导入显示 进程 C (VPU) fd=12, 解码输出 所有进程通过不同的 fd 指向同一个内核 dma_buf 对象,共享同一段物理内存

3.5 全志平台调试清单

在移植全志 DRM 驱动时,DMA-BUF 相关的常见问题及调试方法如下:

  • 问题:导入后显示花屏 → 检查 sg_table 中的 dma_address 是否连续,DE2/DE3 的图层地址寄存器是否配置了正确的起始地址。
  • 问题:dma_buf_get 返回 -EBADF → 用户空间传递的 fd 无效,检查 ioctl 参数传递路径是否正确。
  • 问题:内存泄漏 → 使用 cat /sys/kernel/debug/dma_buf/bufinfo 查看未释放的 dma_buf,检查是否缺少 dma_buf_put()。
  • 问题:同步时序错误 → 确认导出者和导入者之间是否使用了 dma_fence,全志 DE2/DE3 的 Vblank 中断是否与 fence 信号正确关联。
实战建议: 在 dma_buf_export_info.ops 中实现 show_fdinfo 回调,可以在 /proc/<pid>/fdinfo/<fd> 中看到 dma_buf 的大小、引用计数和导出者名称,这是调试用户空间 fd 泄漏的利器。