2. videobuf2核心概念
videobuf2(简称 vb2)是 V4L2 框架中负责缓冲区管理的核心中间层。它将驱动开发者从繁琐的 DMA 缓冲区分配、映射、排队和同步中解放出来,提供了一套统一的接口。要掌握 vb2,必须先理解其三大核心概念:vb2_queue(队列对象)、vb2_buffer(缓冲区对象)以及内存类型(Memory Type)。这三者构成了 vb2 框架的基石,决定了数据如何在用户空间与硬件之间流动。
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;
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() 时传入错误标志 |
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()转换。
-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。
2.4 三者的协作关系
vb2_queue、vb2_buffer 和内存类型三者之间是层层递进、紧密协作的关系:
- vb2_queue 作为顶层管理者,持有所有缓冲区的列表,并决定使用哪种内存操作函数。
- vb2_buffer 是队列中的个体,通过
planes[]数组描述每个平面的内存信息。 - 内存类型 通过
mem_ops影响vb2_plane中mem_priv的分配和释放方式。
当应用程序调用 QBUF 时,vb2 核心会根据 vb2_queue->memory 字段选择对应的 mem_ops,将用户传入的缓冲区信息转换为驱动可用的 DMA 地址,然后调用 vb2_ops->buf_queue() 将缓冲区加入硬件处理队列。整个过程对驱动开发者透明,只需关注 vb2_ops 回调的实现。