2. videobuf2 核心概念

videobuf2(vb2)是 V4L2 框架中负责缓冲区管理的核心模块,它抽象了从驱动到用户空间的数据传递路径。理解 vb2 的三大核心概念——队列结构、缓冲区状态机和内存模型——是进行缓冲区映射调优的前提。

vb2 核心概念关系图 vb2_queue Buffer 状态机 DEQUEUED → PREPARED → QUEUED → DONE → ERROR 内存模型 MMAP USERPTR DMABUF vb2_queue 管理缓冲区生命周期与内存后端

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 中的每个缓冲区都遵循严格的状态机转换。理解状态转换是调试缓冲区问题的基础。状态机确保缓冲区在用户空间和内核空间之间安全传递。

vb2_buffer 状态机 DEQUEUED PREPARED QUEUED DONE ERROR QBUF 自动 硬件完成 硬件错误 DQBU 注意:PREPARED 状态是 vb2 内部状态,用户空间不可见。 用户空间只感知 DEQUEUED、QUEUED、DONE 和 ERROR。
调优提示:在性能调优时,关注 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 是消除冗余拷贝的关键。
三种内存模型数据流对比 MMAP 硬件 DMA mmap 零拷贝,内核分配 USERPTR 硬件 pin 用户 用户分配,需 pin 页 DMABUF 设备A FD 设备B 设备间零拷贝