2. DMA-BUF 核心数据结构:struct dma_buf、struct dma_buf_attachment、struct sg_table
在 DRM Prime 导出与 V4L2 导入的整个数据通路中,有三个数据结构构成了 DMA-BUF 机制的骨架。理解它们的职责与协作关系,是编写正确、高效多媒体驱动的基础。
2.1 数据结构全景图
下图展示了三个核心结构体在“导出端 → 内存对象 → 导入端”流程中的位置与关联:
关键认知: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 导入典型流程:
- 从 V4L2 缓冲区获得 DMABUF fd
dma_buf_get(fd)→ 获得 struct dma_buf *dma_buf_attach(dmabuf, v4l2_dev)→ 获得 attachmentdma_buf_map_attachment(attach, DMA_BIDIRECTIONAL)→ 获得 sg_table- 遍历 sg_table->sgl,配置 DMA 描述符
- 传输完成后:
dma_buf_unmap_attachment()→dma_buf_detach()→dma_buf_put()
2.5 三者的生命周期与引用计数
实战要点:
- 每个
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。