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),让不同驱动、不同进程、甚至不同子系统都能引用同一块物理内存,而无需拷贝。
二、协作关系:从“各自为政”到“零拷贝共享”
在没有 DMA-BUF 的年代,跨子系统传递一帧图像通常要经过用户态中转:V4L2 采集到内存,read() 或 mmap() 到用户空间,再 write() 或拷贝到 DRM 的缓冲区。这条路径至少发生一次甚至两次内存拷贝,对 1080p60 或 4K 视频来说开销巨大。
DMA-BUF 引入后,协作方式变成“只传 fd,不传数据”。生产者把缓冲区导出为一个 dma-buf fd,消费者通过这个 fd 导入并直接访问同一块内存。整个过程中,像素数据始终停留在原地,CPU 不参与搬运,这就是所谓的零拷贝(zero-copy)。
具体到 DRM 与 V4L2 的对接,有两个方向:
- DRM → V4L2:GPU 渲染出一帧,通过 DRM 的 PRIME 机制导出为 dma-buf fd,再通过 V4L2 的
VIDIOC_QBUF把该 fd 排入采集/输出队列,交给编码器或显示设备消费。这正是本课程实战的核心方向。 - V4L2 → DRM:摄像头采集到一帧,V4L2 通过
VIDIOC_EXPBUF导出 dma-buf fd,DRM 通过drmPrimeFDToHandle导入为 GEM 对象,再作为 framebuffer 扫描到屏幕,实现相机预览。
三、关键接口与数据流对照
下面这张图展示了 DRM、V4L2 与 DMA-BUF 三者的协作拓扑:左侧是 DRM 侧的生产/消费路径,右侧是 V4L2 侧的生产/消费路径,中间通过 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。