2. DMA-BUF 核心数据结构:struct dma_buf、struct dma_buf_attachment、struct sg_table

在 DRM Prime 导出与 V4L2 导入的整个数据通路中,有三个数据结构构成了 DMA-BUF 机制的骨架。理解它们的职责与协作关系,是编写正确、高效多媒体驱动的基础。

2.1 数据结构全景图

下图展示了三个核心结构体在“导出端 → 内存对象 → 导入端”流程中的位置与关联:

DMA-BUF 核心数据结构关系图 DMA-BUF 核心数据结构关系图 导出端 (DRM/KMS) struct dma_buf 文件对象 · 引用计数 · 导出器 ops dma_buf_export() struct dma_buf_attachment 导入端私有绑定 · 设备映射 struct dma_buf_attachment 导入端私有绑定 · 设备映射 dma_buf_attach() struct sg_table DMA 地址 · 长度 · 偏移 struct sg_table DMA 地址 · 长度 · 偏移 dma_buf_map_attachment() dma_buf_map_attachment() 导入端 (V4L2) dma_buf_attach() 每个导入端设备独立创建 attachment,每个 attachment 可独立 map 出 sg_table
关键认知:struct dma_buf 是“共享内存文件”,struct dma_buf_attachment 是“设备与文件的绑定契约”,struct sg_table 是“设备可见的 DMA 地址表”。三者层层递进,缺一不可。

2.2 struct dma_buf —— 共享内存的文件抽象

struct dma_buf 是 DMA-BUF 机制的最顶层抽象,本质上是一个匿名文件(anon_inode),通过文件描述符(fd)在进程间传递。它封装了被共享的物理内存以及操作该内存的回调函数集。

成员 类型 说明
size size_t 缓冲区大小(字节)
file struct file * 关联的匿名文件,用于 fd 传递
attachments struct list_head 挂载所有 attachment 的链表头
ops const struct dma_buf_ops * 导出器提供的操作函数表
priv void * 导出器私有数据(如 DRM GEM 对象指针)
exp_name const char * 导出器名称(调试用)

导出器通过 dma_buf_export() 创建该结构体,并填充 dma_buf_ops 回调。V4L2 导入端通过 dma_buf_get() 从 fd 获得 struct dma_buf * 指针。

核心操作函数表 dma_buf_ops:
  • attach() — 设备 attach 时回调,分配设备相关资源
  • detach() — 设备 detach 时回调,释放设备相关资源
  • map_dma_buf() — 返回 sg_table,提供 DMA 地址映射
  • unmap_dma_buf() — 解除 DMA 地址映射
  • release() — 最后一个引用释放时回调,回收内存

2.3 struct dma_buf_attachment —— 设备与缓冲区的绑定

每个想要访问 DMA-BUF 的设备(无论是导出端还是导入端)都必须先创建一个 struct dma_buf_attachment。它代表一个设备与一个 dma_buf 之间的绑定关系。

成员 类型 说明
dmabuf struct dma_buf * 指向绑定的 dma_buf
dev struct device * 绑定设备的 struct device
node struct list_head 用于挂入 dma_buf->attachments 链表
priv void * 设备私有数据(如 IOMMU 映射表)
importer_ops struct dma_buf_attach_ops * 导入端回调(内核 5.6+)

创建 attachment 的 API 为:

struct dma_buf_attachment *dma_buf_attach(struct dma_buf *dmabuf,
                                          struct device *dev);
避坑提示:同一个 dma_buf 可以被多个设备 attach,每个设备得到独立的 attachment。但导出器在 attach() 回调中可能分配硬件资源(如 IOMMU 页表),因此不要创建多余的 attachment。

2.4 struct sg_table —— DMA 地址散列表

当导入端设备准备好进行 DMA 传输时,需要将 attachment 转换为设备可见的 DMA 地址列表。这个列表由 struct sg_table 描述,它是分散/聚集表(Scatter-Gather Table)的内核标准表示。

成员 类型 说明
sgl struct scatterlist * 散列表项链表头
nents unsigned int DMA 映射后的有效表项数
orig_nents unsigned int 原始表项数(映射前)

每个 struct scatterlist 包含:

  • dma_address — 设备总线地址
  • length — 该段长度(字节)
  • offset — 页内偏移
  • page_link — 关联的物理页

获取 sg_table 的 API 为:

struct sg_table *dma_buf_map_attachment(struct dma_buf_attachment *attach,
                                        enum dma_data_direction direction);
V4L2 导入典型流程:
  1. 从 V4L2 缓冲区获得 DMABUF fd
  2. dma_buf_get(fd) → 获得 struct dma_buf *
  3. dma_buf_attach(dmabuf, v4l2_dev) → 获得 attachment
  4. dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL) → 获得 sg_table
  5. 遍历 sg_table->sgl,配置 DMA 描述符
  6. 传输完成后:dma_buf_unmap_attachment() → dma_buf_detach() → dma_buf_put()

2.5 三者的生命周期与引用计数

DMA-BUF 核心结构生命周期 DMA-BUF 核心结构生命周期 dma_buf_export() 创建 dma_buf + fd dma_buf_attach() 创建 attachment dma_buf_map() 获得 sg_table DMA 传输 硬件读写 dma_buf_unmap() 释放 sg_table dma_buf_detach() 销毁 attachment dma_buf_put() 释放 dma_buf 引用 引用计数规则:dma_buf_get() 增加引用,dma_buf_put() 减少引用; 当引用计数归零时,内核自动调用 dma_buf_ops.release() 释放物理内存
实战要点:
  • 每个 dma_buf_map_attachment() 调用必须配对 dma_buf_unmap_attachment()
  • sg_table 在 map/unmap 之间有效,unmap 后不可再访问
  • V4L2 驱动应在 QBUF 时 map,DQBUFF 后 unmap,避免长时间占用 DMA 映射资源
  • 多设备共享时,每个设备独立 map/unmap,互不干扰

2.6 数据结构对比总结

结构体 创建 API 销毁 API 核心职责 生命周期
struct dma_buf dma_buf_export() dma_buf_put() 共享内存文件抽象,持有物理内存 从 export 到最后一个 put
struct dma_buf_attachment dma_buf_attach() dma_buf_detach() 设备与 dma_buf 的绑定关系 从 attach 到 detach
struct sg_table dma_buf_map_attachment() dma_buf_unmap_attachment() 设备 DMA 地址散列表 从 map 到 unmap
常见错误:忘记调用 dma_buf_unmap_attachment() 会导致 IOMMU 页表泄漏;忘记 dma_buf_detach() 会导致 attachment 链表残留;忘记 dma_buf_put() 会导致 dma_buf 文件引用泄漏。这三个泄漏在多媒体长稳测试中极易触发 OOM。