2. videobuf2核心概念

videobuf2(简称 vb2)是 V4L2 框架中负责缓冲区管理的核心中间层。它将驱动开发者从繁琐的 DMA 缓冲区分配、映射、排队和同步中解放出来,提供了一套统一的接口。要掌握 vb2,必须先理解其三大核心概念:vb2_queue(队列对象)、vb2_buffer(缓冲区对象)以及内存类型(Memory Type)。这三者构成了 vb2 框架的基石,决定了数据如何在用户空间与硬件之间流动。

学习提示: 在阅读本节时,请将 vb2_queue 理解为“缓冲区管理器”,vb2_buffer 理解为“单个数据容器”,内存类型理解为“数据存放的物理/虚拟方式”。三者协同工作,缺一不可。

2.1 vb2_queue:缓冲区管理的中枢

struct vb2_queue 是 vb2 框架中最顶层的结构体,它代表了一个视频数据流(如一个视频采集通道)的缓冲区管理实体。驱动中每个需要处理视频数据的设备节点(如 /dev/video0)通常对应一个或多个 vb2_queue。

2.1.1 核心职责

  • 缓冲区生命周期管理: 负责缓冲区的分配、初始化、入队(QBUF)、出队(DQBUFF)以及释放。
  • 内存类型协调: 根据用户空间指定的内存类型(MMAP/USERPTR/DMABUF),调用对应的内存操作函数。
  • 驱动回调接口: 提供一组 vb2_ops 回调函数,驱动通过实现这些回调来参与缓冲区处理(如启动/停止流、填充数据)。
  • 状态跟踪: 维护缓冲区队列的状态(已入队、已出队、正在处理等),确保状态机正确转换。

2.1.2 关键字段

字段名 类型 说明
type enum v4l2_buf_type 缓冲区类型,如 V4L2_BUF_TYPE_VIDEO_CAPTURE
io_modes u32 支持的 I/O 模式掩码,如 VB2_MMAP | VB2_USERPTR | VB2_DMABUF
ops struct vb2_ops * 驱动实现的回调函数指针
mem_ops struct vb2_mem_ops * 内存操作函数指针,由 vb2 核心根据内存类型自动选择
buf_struct_size unsigned int 驱动自定义缓冲区结构体的大小,用于扩展 vb2_buffer
min_buffers_needed unsigned int 启动流所需的最小缓冲区数量
lock struct mutex * 保护队列操作的互斥锁

2.1.3 初始化流程

驱动在使用 vb2_queue 前,需要先填充该结构体,然后调用 vb2_queue_init() 进行注册。典型代码如下:

struct vb2_queue q;

q.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
q.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
q.drv_priv = &sdev;          // 驱动私有数据
q.buf_struct_size = sizeof(struct my_buffer);
q.ops = &my_vb2_ops;
q.mem_ops = &vb2_dma_contig_memops; // 使用连续 DMA 内存
q.min_buffers_needed = 2;
q.lock = &sdev->mutex;

ret = vb2_queue_init(&q);
if (ret)
    return ret;
vb2_queue 核心结构图 struct vb2_queue type / io_modes ops (vb2_ops) mem_ops buf_struct_size min_buffers_needed lock / drv_priv vb2_queue 是驱动与 vb2 核心交互的唯一入口, 所有缓冲区操作都通过它进行调度和管理。

2.2 vb2_buffer:单个缓冲区的抽象

struct vb2_buffer 是 vb2 框架中代表单个缓冲区的核心结构体。它不直接存储视频数据,而是通过 struct vb2_plane 数组来管理每个平面的 DMA 内存地址、长度和偏移量等信息。

2.2.1 结构层次

一个 vb2_buffer 可以包含多个平面(plane),每个平面对应一个独立的内存区域。对于 YUV 格式,通常有 2 或 3 个平面;对于 RGB 格式,通常只有 1 个平面。

struct vb2_buffer {
    struct vb2_queue      *vb2_queue;   // 所属队列
    unsigned int           index;       // 缓冲区索引
    unsigned int           type;        // 缓冲区类型
    unsigned int           memory;      // 内存类型
    struct vb2_plane       planes[VB2_MAX_PLANES]; // 平面数组
    // ... 其他内部字段
};

struct vb2_plane {
    void                  *mem_priv;    // 内存私有数据
    dma_addr_t             dma_addr;    // DMA 总线地址
    void                  *vaddr;       // 内核虚拟地址
    unsigned int           length;      // 平面长度
    unsigned int           bytesused;   // 实际数据长度
    // ... 其他字段
};

2.2.2 缓冲区状态机

vb2_buffer 在生命周期中会经历多个状态,驱动通过 vb2_ops 回调感知这些状态变化:

状态 说明 触发动作
VB2_BUF_STATE_DEQUEUED 缓冲区在用户空间,未被使用 应用程序调用 DQBUF 后
VB2_BUF_STATE_QUEUED 缓冲区已入队,等待硬件处理 应用程序调用 QBUF 后
VB2_BUF_STATE_ACTIVE 缓冲区正在被硬件使用 驱动调用 vb2_buffer_done() 前
VB2_BUF_STATE_DONE 缓冲区已处理完毕,等待用户读取 驱动调用 vb2_buffer_done() 后
VB2_BUF_STATE_ERROR 缓冲区处理出错 驱动调用 vb2_buffer_done() 时传入错误标志
vb2_buffer 状态转换图 DEQUEUED QUEUED ACTIVE DONE QBUF 硬件开始 vb2_buffer_done() DQBUF

2.3 内存类型:V4L2_MEMORY_MMAP / USERPTR / DMABUF

内存类型决定了缓冲区数据在物理内存中的存在形式,以及用户空间如何访问这些数据。vb2 框架通过 mem_ops 抽象层屏蔽了不同内存类型的差异,驱动开发者只需在初始化时指定支持的 io_modes,其余工作由 vb2 核心完成。

2.3.1 三种内存类型对比

特性 V4L2_MEMORY_MMAP V4L2_MEMORY_USERPTR V4L2_MEMORY_DMABUF
内存所有者 内核驱动 用户空间应用程序 其他驱动或设备
分配方式 驱动调用 dma_alloc_coherent() 等 用户调用 malloc() 或 mmap() 通过 DMA-BUF 文件描述符导入
用户访问方式 通过 mmap() 映射到用户空间 直接使用用户指针 通过 DMA-BUF 的 mmap() 或 exporter
零拷贝能力 是(内核到用户需一次拷贝) 是(用户指针直接用于 DMA) 是(完全零拷贝,跨设备共享)
适用场景 标准 V4L2 应用 用户自定义内存管理 多设备流水线(如 ISP + 编解码)
性能开销 中等(需一次拷贝) 低(需用户保证内存连续性) 最低(完全共享)

2.3.2 MMAP 模式详解

MMAP 是最传统、兼容性最好的内存类型。驱动在内核中分配 DMA 缓冲区,用户空间通过 mmap() 系统调用将这些缓冲区映射到自己的地址空间。vb2 核心负责管理映射的生命周期。

  • 优点: 应用程序无需关心内存分配细节,兼容所有 V4L2 应用。
  • 缺点: 数据在用户和内核间传递时,通常需要一次额外的拷贝(除非使用 V4L2_BUF_FLAG_NO_CACHE_INVALIDATE 等优化)。
  • 驱动实现: 驱动只需实现 vb2_ops->buf_prepare() 和 vb2_ops->buf_finish(),内存分配由 vb2_dma_contig_memops 或 vb2_vmalloc_memops 自动完成。

2.3.3 USERPTR 模式详解

USERPTR 模式允许应用程序自己分配内存(如通过 malloc() 或 posix_memalign()),然后将用户空间指针传递给驱动。驱动需要将这些用户地址映射到内核 DMA 地址空间。

  • 优点: 应用程序可以灵活管理内存池,避免内核-用户间的数据拷贝。
  • 风险: 用户必须保证内存物理连续(或使用 IOMMU/SMMU 支持分散-聚集),且内存不能被换出或释放。
  • 驱动实现: 驱动需要实现 vb2_ops->buf_prepare() 中的 vb2_plane_vaddr() 和 vb2_plane_dma_addr() 转换。
避坑指南: USERPTR 模式下,如果用户传入的内存不是物理连续的,且硬件不支持分散-聚集 DMA,驱动将返回 -EINVAL。建议在用户空间使用 dma_mmap_coherent() 或分配大页内存来保证连续性。

2.3.4 DMABUF 模式详解

DMABUF 是 Linux 内核提供的一种跨设备共享 DMA 缓冲区的机制。一个设备(Exporter)分配并导出 DMA 缓冲区,另一个设备(Importer)通过文件描述符导入并使用该缓冲区。vb2 框架原生支持 DMABUF 导入。

  • 优点: 真正的零拷贝,多个硬件设备可以直接共享同一块物理内存,无需任何数据搬移。
  • 适用场景: 摄像头直接输出到 GPU 纹理、ISP 处理后直接送入编码器、显示控制器直接显示摄像头数据等。
  • 驱动实现: 驱动需要实现 vb2_ops->attach_dmabuf() 和 vb2_ops->detach_dmabuf() 回调,用于绑定和释放 DMA-BUF。
三种内存类型数据流对比 V4L2_MEMORY_MMAP 内核 DMA 用户 mmap 一次拷贝 V4L2_MEMORY_USERPTR 用户 malloc DMA 映射 零拷贝 V4L2_MEMORY_DMABUF Exporter Importer 零拷贝 内存类型选择建议 • 通用应用:优先使用 MMAP,兼容性最好 • 性能敏感:使用 USERPTR 或 DMABUF 实现零拷贝 • 多设备流水线:必须使用 DMABUF 避免数据搬移

2.4 三者的协作关系

vb2_queue、vb2_buffer 和内存类型三者之间是层层递进、紧密协作的关系:

  1. vb2_queue 作为顶层管理者,持有所有缓冲区的列表,并决定使用哪种内存操作函数。
  2. vb2_buffer 是队列中的个体,通过 planes[] 数组描述每个平面的内存信息。
  3. 内存类型 通过 mem_ops 影响 vb2_plane 中 mem_priv 的分配和释放方式。

当应用程序调用 QBUF 时,vb2 核心会根据 vb2_queue->memory 字段选择对应的 mem_ops,将用户传入的缓冲区信息转换为驱动可用的 DMA 地址,然后调用 vb2_ops->buf_queue() 将缓冲区加入硬件处理队列。整个过程对驱动开发者透明,只需关注 vb2_ops 回调的实现。