1. 课程导入

在嵌入式 Linux 多媒体系统中,摄像头采集、视频编码、显示合成往往由不同硬件模块完成,而这些模块之间共享图像数据最高效的方式就是 DMA-BUF。本课程聚焦一个非常具体、也非常典型的工程问题:如何把 DRM 子系统通过 PRIME 机制导出的 dma-buf,安全、正确地传递到 V4L2 设备,并让整条零拷贝链路真正跑起来。本章不写代码,而是先把学习目标、核心概念和整体框架讲清楚,为后续 29 章打下认知地基。

一、学习目标

完成本课程后,你应当能够独立完成以下任务:

  • 理解 DRM PRIME、GEM handle、dma-buf fd 三者之间的转换关系,能说清“谁导出、谁引用、谁释放”。
  • 掌握 V4L2 的 VIDIOC_EXPBUF、VIDIOC_QBUF 与 dma-buf 导入导出接口的配合方式。
  • 能够在真实板卡上把 DRM 渲染缓冲导出为 dma-buf,并作为 V4L2 capture 或 output 队列的缓冲区使用。
  • 能定位常见的“缓冲区无法导入”“DMA 地址不一致”“帧撕裂”“引用计数泄漏”等问题。
  • 能读懂并修改一个最小可运行的零拷贝 demo,而不是只会调用封装库。
本课程的核心不是“背 API”,而是建立一条清晰的数据通路心智模型:谁生产帧、谁消费帧、缓冲区在哪些子系统之间流转、生命周期由谁负责。只要这条链路清楚,API 只是表达方式。

二、核心概念

在进入实战前,必须先厘清几个高频术语,它们贯穿整个课程:

概念 所属子系统 作用与关键点
GEM DRM 图形执行管理器,负责显存/系统内存对象的分配与句柄管理,是 DRM 内部对象。
PRIME DRM DRM 的跨设备/跨子系统共享机制,负责把 GEM 对象导出为 dma-buf,或从 dma-buf 导入。
dma-buf 内核通用 内核级缓冲区共享框架,用一个 fd 表示一块可被多设备访问的内存,核心是 attachment 与 fence。
V4L2 buffer V4L2 视频采集/输出队列中的缓冲区,可通过 DMABUF 内存类型与 dma-buf 绑定。
DMA-BUF fd 用户态 用户空间可见的文件描述符,是 DRM 与 V4L2 之间传递缓冲区的“通行证”。

其中最容易混淆的是 GEM handle 与 dma-buf fd:前者是 DRM 设备内部的局部句柄,只在对应 DRM fd 上有效;后者是内核全局的共享标识,可以跨进程、跨设备传递。PRIME 的职责,正是在这两者之间做转换。

三、整体框架

下图给出本课程要打通的完整链路。左侧是 DRM 侧的生产者(如 GPU 渲染、显示控制器),中间是 dma-buf 这一共享载体,右侧是 V4L2 侧的消费者(如 video capture、video output、ISP、编解码器)。用户态程序负责在两侧之间传递 fd,并管理队列与生命周期。

DRM PRIME → dma-buf → V4L2 零拷贝数据通路 DRM 子系统 GEM 对象分配 PRIME 导出 DRM_IOCTL_PRIME GPU / 显示控制器 dma-buf 共享层 dma_buf 对象 attachment / sg_table fence 同步 跨设备共享载体 V4L2 设备 VIDIOC_QBUF V4L2_MEMORY_DMABUF capture / output 队列 ISP / 编解码 / 显示 导出 fd 导入 fd 用户态:传递 fd、管理队列、控制生命周期

从图中可以看到,整条链路的关键节点只有三个:DRM 导出、dma-buf 承载、V4L2 导入。用户态程序并不直接搬运像素数据,它只负责把 fd 从左边传到右边,并保证在 V4L2 使用期间 dma-buf 不被释放。这正是“零拷贝”的本质:数据不动,动的是引用。

四、典型场景与最小流程

假设我们要把 GPU 渲染好的一帧画面,交给 V4L2 video output 设备做硬件编码。最小流程可以抽象为:

1. 打开 DRM 设备,创建 GEM 对象并完成渲染
2. 调用 DRM_IOCTL_PRIME_HANDLE_TO_FD,得到 dma-buf fd
3. 打开 V4L2 设备,设置格式与 V4L2_MEMORY_DMABUF
4. 将 dma-buf fd 填入 v4l2_buffer.m.fd,调用 VIDIOC_QBUF
5. 启动流式 IO,等待编码完成,再 DQBUF 回收
6. 关闭 dma-buf fd,释放 GEM 对象

看起来只有六步,但每一步都藏着工程细节:格式是否匹配、stride 是否对齐、DMA 地址是否连续、fence 是否需要显式同步、引用计数是否平衡。后续章节会逐一展开。

建议在阅读本课程时,手边准备一块支持 DRM 与 V4L2 的开发板,并打开内核源码中的 drivers/dma-buf/、drivers/gpu/drm/ 与 drivers/media/ 三个目录。很多疑问在源码里比在文档里更容易找到答案。

五、常见误区与复盘

初学者最容易踩的坑,往往不是 API 写错,而是对“所有权”和“生命周期”理解偏差。下面列出几个高频误区:

  • 误区一:把 GEM handle 当成 dma-buf fd 直接传给 V4L2。两者语义完全不同,必须先经过 PRIME 转换。
  • 误区二:导出 fd 后立刻关闭 DRM fd。dma-buf 的底层内存仍依赖原 DRM 对象的引用,过早关闭可能导致导入失败或悬空。
  • 误区三:忽略格式与 stride 对齐。DRM 侧的行对齐与 V4L2 侧要求不一致时,画面会出现错位或花屏。
  • 误区四:认为 dma-buf 一定物理连续。它只保证可被设备访问,是否连续取决于底层分配器与 IOMMU。
  • 误区五:忘记 fence 同步。GPU 还没写完就让 V4L2 读取,会得到撕裂或旧帧。
排障时优先确认三件事:fd 是否有效、格式是否一致、引用计数是否平衡。绝大多数“导入失败”和“花屏”问题,都能从这三条线索定位。

本章建立了整体认知:DRM 负责生产与导出,dma-buf 负责承载与共享,V4L2 负责消费与导入,用户态负责串联与生命周期管理。接下来的章节将沿着这条链路,从环境搭建、API 细节、代码实现到问题排查,逐步把这张框架图变成可运行的工程。