2. videobuf2 核心概念
videobuf2(vb2)是 V4L2 框架中负责缓冲区管理的核心模块,它抽象了从驱动到用户空间的数据传递路径。理解 vb2 的三大核心概念——队列结构、缓冲区状态机和内存模型——是进行缓冲区映射调优的前提。
2.1 vb2_queue 结构体
struct vb2_queue 是 videobuf2 框架中最核心的数据结构,它代表一个缓冲区队列实例。每个视频设备节点(如 /dev/video0)通常对应一个或多个 vb2_queue,用于管理输入(capture)或输出(output)方向的缓冲区。
关键字段速览:以下表格列出了 vb2_queue 中与映射调优最相关的字段。
| 字段名 | 类型 | 说明 | 调优关注点 |
|---|---|---|---|
type |
enum v4l2_buf_type | 缓冲区类型(V4L2_BUF_TYPE_VIDEO_CAPTURE 等) | 决定队列用途 |
io_modes |
u32 | 支持的 I/O 模式位掩码(VB2_MMAP / VB2_USERPTR / VB2_DMABUF) | 限制可用内存模型 |
buf_struct_size |
size_t | 每个缓冲区私有结构体的大小 | 驱动扩展数据时需设置 |
min_buffers_needed |
int | 驱动正常工作的最少缓冲区数量 | 影响内存占用和延迟 |
ops |
struct vb2_ops * | 回调函数集(queue_setup、buf_prepare、buf_queue 等) | 驱动核心逻辑 |
mem_ops |
struct vb2_mem_ops * | 内存后端操作函数集 | 决定内存分配/映射行为 |
num_buffers |
int | 当前已分配的缓冲区数量 | 动态调整队列深度 |
// 驱动中初始化 vb2_queue 的典型代码片段
struct vb2_queue q;
q.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
q.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
q.buf_struct_size = sizeof(struct my_driver_buffer);
q.min_buffers_needed = 2;
q.ops = &my_vb2_ops;
q.mem_ops = &vb2_dma_contig_memops;
ret = vb2_queue_init(&q);
if (ret)
dev_err(dev, "vb2_queue_init failed: %d\n", ret);
2.2 Buffer 状态机
vb2 中的每个缓冲区都遵循严格的状态机转换。理解状态转换是调试缓冲区问题的基础。状态机确保缓冲区在用户空间和内核空间之间安全传递。
调优提示:在性能调优时,关注 QUEUED → DONE 的转换延迟。如果 DONE 队列堆积,说明硬件处理速度跟不上或 DQBU 调用频率过低。可以通过增加
min_buffers_needed 或调整队列深度来缓解。
2.3 内存模型
vb2 支持三种内存模型,分别对应不同的数据传递场景。选择合适的内存模型直接影响系统吞吐量和 CPU 占用。
| 模型 | 标志 | 内存归属 | 映射方式 | 典型场景 | 性能特征 |
|---|---|---|---|---|---|
| MMAP | VB2_MMAP |
内核分配 | mmap() 系统调用 | 标准 V4L2 应用、嵌入式设备 | 零拷贝(从内核到用户),但需内核分配连续内存 |
| USERPTR | VB2_USERPTR |
用户分配 | 用户指针直接传递 | 用户管理内存池、与第三方库共享 | 需内核 pin 住用户内存页,可能产生额外开销 |
| DMABUF | VB2_DMABUF |
外部设备 | DMA-BUF 文件描述符 | GPU/ISP/VPU 间零拷贝、多设备流水线 | 真正的设备间零拷贝,但需要硬件支持 |
2.3.1 MMAP 模型
MMAP 是最常用的模型。内核在驱动初始化时分配 DMA 缓冲区,用户空间通过 mmap() 将缓冲区映射到自己的地址空间。该模型在大多数 SoC 平台上实现了从硬件到用户空间的零拷贝。
// 用户空间 MMAP 流程(伪代码)
struct v4l2_requestbuffers req = {0};
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_REQBUFS, &req);
for (i = 0; i < req.count; i++) {
struct v4l2_buffer buf = {0};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
ioctl(fd, VIDIOC_QUERYBUF, &buf);
// 通过 buf.m.offset 进行 mmap
buffers[i].addr = mmap(NULL, buf.length,
PROT_READ | PROT_WRITE, MAP_SHARED,
fd, buf.m.offset);
}
2.3.2 USERPTR 模型
USERPTR 允许用户空间分配内存,然后将指针传递给内核。内核需要将这些用户页 pin 住(防止被换出),并建立硬件可访问的 DMA 映射。该模型在用户需要管理自己的内存池时非常有用。
性能警告:USERPTR 模型在每次 QBUF 时都可能触发
get_user_pages() 和 DMA 映射建立,如果缓冲区地址频繁变化,会导致显著的性能开销。建议在初始化时分配固定缓冲区池,避免运行时动态分配。
2.3.3 DMABUF 模型
DMABUF 是最高级的内存模型,它通过 DMA-BUF 文件描述符在不同设备驱动之间共享缓冲区。例如,ISP 输出的图像可以直接传递给 GPU 或编码器,而无需经过 CPU 拷贝。
// DMABUF 导入示例(驱动侧)
static int my_buf_prepare(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct my_driver *dev = vb2_get_drv_priv(vb->vb2_queue);
// 从 DMABUF 文件描述符获取 DMA 地址
dma_buf = dma_buf_get(vbuf->dmabuf_fd);
if (IS_ERR(dma_buf))
return PTR_ERR(dma_buf);
// 将 dma_buf 映射到设备地址空间
sg_table = dma_buf_map_attachment(dev->attach, DMA_FROM_DEVICE);
if (IS_ERR(sg_table)) {
dma_buf_put(dma_buf);
return PTR_ERR(sg_table);
}
// 设置硬件描述符指向 sg_table
// ...
return 0;
}
调优决策指南:
- 优先选择 MMAP:除非有明确理由,MMAP 在大多数场景下提供最佳性能和兼容性。
- 谨慎使用 USERPTR:仅在需要与现有用户内存池集成时使用,并注意 pin 页的开销。
- 积极采用 DMABUF:在多设备流水线(如 Camera → GPU → Display)中,DMABUF 是消除冗余拷贝的关键。