2. Linux图形与视频子系统概览

在 Linux 内核里,图形显示与视频采集长期由两套独立演进的子系统负责:DRM(Direct Rendering Manager)面向显示与 GPU 渲染,V4L2(Video4Linux2)面向摄像头、编解码器、视频采集与输出设备。二者关注的硬件不同、编程模型不同,但在真实产品中又必须协作——例如摄像头采集的画面要直接送到屏幕上预览,或者 GPU 渲染的结果要编码成视频流。连接这两套子系统的关键桥梁,就是 DMA-BUF。理解三者的定位与协作关系,是后续“把 DRM PRIME 导出的 dma-buf 交给 V4L2 设备”这一实战目标的概念基础。

一、三者的定位:各管一段,各司其职

可以把整个数据通路想象成一条流水线:生产者负责产生像素,消费者负责使用像素,而缓冲区是像素的载体。DRM、V4L2、DMA-BUF 正好对应这三个角色中的不同层次。

  • DRM:管理显示控制器(CRTC)、图层(Plane)、连接器(Connector)、GPU 渲染引擎以及显存(GEM)对象。它既可以是像素的生产者(GPU 渲染),也可以是消费者(扫描输出到屏幕)。
  • V4L2:管理摄像头传感器、ISP、视频编解码器、HDMI 采集等设备。它通过 VIDIOC_* ioctl 暴露能力,用 vb2(videobuf2)框架管理采集/输出队列,是典型的像素生产者或消费者。
  • DMA-BUF:本身不是设备驱动,而是一套跨设备共享缓冲区的内核框架。它提供一个文件描述符(fd),让不同驱动、不同进程、甚至不同子系统都能引用同一块物理内存,而无需拷贝。
核心记忆点:DRM 和 V4L2 是“设备侧”的子系统,DMA-BUF 是“缓冲区侧”的共享机制。前者决定谁能读写,后者决定内存如何被共享。

二、协作关系:从“各自为政”到“零拷贝共享”

在没有 DMA-BUF 的年代,跨子系统传递一帧图像通常要经过用户态中转:V4L2 采集到内存,read() 或 mmap() 到用户空间,再 write() 或拷贝到 DRM 的缓冲区。这条路径至少发生一次甚至两次内存拷贝,对 1080p60 或 4K 视频来说开销巨大。

DMA-BUF 引入后,协作方式变成“只传 fd,不传数据”。生产者把缓冲区导出为一个 dma-buf fd,消费者通过这个 fd 导入并直接访问同一块内存。整个过程中,像素数据始终停留在原地,CPU 不参与搬运,这就是所谓的零拷贝(zero-copy)。

具体到 DRM 与 V4L2 的对接,有两个方向:

  1. DRM → V4L2:GPU 渲染出一帧,通过 DRM 的 PRIME 机制导出为 dma-buf fd,再通过 V4L2 的 VIDIOC_QBUF 把该 fd 排入采集/输出队列,交给编码器或显示设备消费。这正是本课程实战的核心方向。
  2. V4L2 → DRM:摄像头采集到一帧,V4L2 通过 VIDIOC_EXPBUF 导出 dma-buf fd,DRM 通过 drmPrimeFDToHandle 导入为 GEM 对象,再作为 framebuffer 扫描到屏幕,实现相机预览。

三、关键接口与数据流对照

下面这张图展示了 DRM、V4L2 与 DMA-BUF 三者的协作拓扑:左侧是 DRM 侧的生产/消费路径,右侧是 V4L2 侧的生产/消费路径,中间通过 dma-buf fd 完成跨子系统共享。

DRM 子系统 显示 / GPU 渲染 GPU 渲染引擎 渲染 → GEM 对象 GEM / PRIME drmPrimeHandleToFD CRTC / Plane / Connector 扫描输出到屏幕 Framebuffer drmModeAddFB2 V4L2 子系统 视频采集 / 编解码 摄像头 / ISP / 编码器 硬件采集或编码 videobuf2 (vb2) 缓冲区队列管理 VIDIOC_QBUF / DQBUF 入队 / 出队 VIDIOC_EXPBUF 导出 dma-buf fd DMA-BUF 跨设备共享缓冲区 fd 传递 · 零拷贝 dma_buf_export / import 导出 导入 导出 导入 DRM PRIME 与 V4L2 EXPBUF 通过 dma-buf fd 完成跨子系统零拷贝共享

从图中可以清晰看到:DRM 和 V4L2 各自维护自己的缓冲区管理逻辑,而 DMA-BUF 位于中间,充当“通用货币”。DRM 侧通过 PRIME 把 GEM 对象导出为 fd,V4L2 侧通过 VIDIOC_EXPBUF 把 vb2 缓冲区导出为 fd;反向导入时,DRM 用 drmPrimeFDToHandle,V4L2 用 VIDIOC_QBUF 携带 V4L2_BUF_TYPE_VIDEO_OUTPUT 和 memory=V4L2_MEMORY_DMABUF。

四、接口与参数对照表

维度 DRM V4L2 DMA-BUF
核心对象 GEM、CRTC、Plane、Connector vb2_queue、v4l2_buffer、format dma_buf、dma_buf_attachment
用户态入口 /dev/dri/card0 /dev/video0 文件描述符 fd
导出接口 drmPrimeHandleToFD VIDIOC_EXPBUF dma_buf_export
导入接口 drmPrimeFDToHandle VIDIOC_QBUF + DMABUF dma_buf_import
典型内存类型 VRAM / CMA / system MMAP / USERPTR / DMABUF 由 exporter 决定
同步机制 fence(dma_fence) fence(通过 dma-buf) dma_fence / reservation

五、实战场景:摄像头预览与视频编码

假设我们要做一个“摄像头采集 → GPU 处理 → 屏幕预览 + 编码存储”的应用,数据流如下:

摄像头 (V4L2 capture)
   │  VIDIOC_EXPBUF 导出 dma-buf fd
   ▼
GPU 处理 (DRM render)
   │  drmPrimeFDToHandle 导入 → 渲染 → drmPrimeHandleToFD 导出
   ▼
┌──────────────┬──────────────┐
│ 屏幕预览      │ 视频编码      │
│ DRM plane     │ V4L2 encoder │
│ 扫描输出      │ QBUF(DMABUF) │
└──────────────┴──────────────┘

这条链路中,每一段都通过 dma-buf fd 衔接,像素数据只在最初采集时写入一次,之后全程共享。如果中间任何一段退化成用户态拷贝,带宽和延迟都会显著恶化。

调试建议:用 dmesg | grep -i dma-buf 观察 dma-buf 的导出/导入日志;用 cat /sys/kernel/debug/dma_buf/bufinfo 查看当前系统中所有 dma-buf 的归属、大小和 attachment 情况,能快速判断缓冲区是否被正确共享。

六、常见误区与复盘

  • 误区一:以为 dma-buf 就是“一块内存”。 实际上 dma-buf 是一个内核对象,它可能对应 VRAM、CMA、system RAM,甚至由 IOMMU 映射后的地址。fd 只是引用句柄,不是物理地址。
  • 误区二:导出 fd 后就可以随意读写。 dma-buf 的访问必须经过 exporter 提供的 dma_buf_ops,包括 map_attachment、begin_cpu_access、end_cpu_access。CPU 访问前后必须做 cache 同步,否则会读到脏数据。
  • 误区三:忽略 fence 同步。 GPU 渲染完成和 V4L2 消费之间必须用 dma_fence 串行化,否则会出现“编码器读到半帧画面”的撕裂问题。
  • 误区四:认为所有 V4L2 设备都支持 DMABUF。 需要检查 VIDIOC_QUERYCAP 返回的 V4L2_CAP_* 以及 VIDIOC_REQBUFS 对 V4L2_MEMORY_DMABUF 的支持情况,老驱动可能只支持 MMAP。
在 DRM PRIME 导出 dma-buf 给 V4L2 时,务必确认双方对像素格式(fourcc)、stride、plane 数量的理解一致。格式不匹配不会报错,但画面会花屏或颜色错乱,这是实战中最常见的“静默故障”。