2. VideoBuf2 核心概念:vb2 核心数据结构、vb2_queue、vb2_buffer、内存模型、操作回调
VideoBuf2 (vb2) 是 V4L2 框架中负责视频缓冲区管理的核心子系统。理解其核心数据结构、队列模型、内存模型以及操作回调机制,是进行 DMA 缓冲区分配与调优的基础。本章将深入剖析这些核心概念,帮助您建立扎实的底层认知。
2.1 vb2 核心数据结构全景
vb2 框架围绕几个关键数据结构构建,它们共同协作,完成从用户空间请求到内核 DMA 缓冲区分配、管理和回收的完整生命周期。下表列出了最核心的数据结构及其职责:
| 数据结构 | 核心作用 | 所属层次 |
|---|---|---|
vb2_queue | 管理整个视频流(如一个摄像头设备节点)的缓冲区队列 | 驱动层 / 核心层 |
vb2_buffer | 代表单个缓冲区实例,包含状态、索引、操作回调 | 核心层 |
vb2_v4l2_buffer | V4L2 扩展结构,包含 v4l2_buffer 元数据 | V4L2 接口层 |
vb2_plane | 描述缓冲区内的单个平面(用于非连续格式如 NV12) | 核心层 |
vb2_mem_ops | 内存操作回调集合,定义分配、释放、映射等接口 | 内存模型层 |
vb2_ops | 驱动操作回调集合,定义队列设置、缓冲区准备等接口 | 驱动层 |
关键理解: vb2_queue 是“管理者”,vb2_buffer 是“被管理者”,vb2_mem_ops 和 vb2_ops 是“策略接口”。驱动开发者主要实现 vb2_ops,而内存分配策略由 vb2_mem_ops 封装。
2.2 vb2_queue:缓冲区队列的中央控制器
vb2_queue 是每个视频流实例的核心结构体。它负责跟踪所有缓冲区状态、管理空闲和已入队列表、协调内存分配,并作为驱动与 V4L2 核心交互的桥梁。
调优要点: vb2_queue 的 min_buffers_needed 字段决定了驱动启动 DMA 所需的最小缓冲区数量。设置过小可能导致 DMA 饥饿,设置过大则浪费内存。对于高分辨率视频,建议根据帧大小和延迟要求动态计算此值。
2.3 vb2_buffer:缓冲区实例的生命周期
vb2_buffer 是单个缓冲区的抽象,它不直接包含数据,而是通过 planes 数组指向实际的内存区域。其状态机转换是理解 vb2 工作流程的关键。
2.3.1 缓冲区状态机
2.3.2 关键字段解析
struct vb2_buffer {
struct vb2_queue *vb2_queue; // 所属队列
unsigned int index; // 缓冲区索引 (0..nbuffers-1)
unsigned int type; // 缓冲区类型
unsigned int memory; // 内存模型 (VB2_MEMORY_MMAP/USERPTR/DMABUF)
unsigned int num_planes; // 平面数量
struct vb2_plane planes[VB2_MAX_PLANES]; // 平面数组
enum vb2_buffer_state state; // 当前状态
// ... 其他字段
};
常见误区: vb2_buffer 的 planes 数组大小固定为 VB2_MAX_PLANES(通常为 4)。对于多平面格式(如 NV12 的 2 平面),务必正确设置 num_planes 和每个 plane 的 m.fd、length、bytesused 等字段,否则会导致 DMA 操作越界或数据损坏。
2.4 内存模型:DMA 缓冲区的三种分配策略
vb2 支持三种内存模型,分别对应不同的应用场景和性能特征。驱动通过 vb2_queue 的 io_modes 字段声明支持的模型,用户空间通过 VIDIOC_REQBUFS 的 memory 字段选择。
| 内存模型 | 枚举值 | 分配方式 | CPU 访问 | 典型场景 |
|---|---|---|---|---|
| MMAP | VB2_MEMORY_MMAP | 内核分配物理连续/非连续内存,通过 mmap 映射到用户空间 | 直接访问映射地址 | 摄像头预览、编码输入 |
| USERPTR | VB2_MEMORY_USERPTR | 用户空间分配内存,通过用户指针传递给内核 | 直接访问用户指针 | 需要与用户态库共享内存 |
| DMABUF | VB2_MEMORY_DMABUF | 通过 dma-buf 机制共享 DMA 缓冲区 | 通过 dma-buf 接口间接访问 | 跨设备共享(如 ISP → GPU) |
2.4.1 MMAP 模型(最常用)
MMAP 是默认且最常用的模型。内核通过 vb2_mem_ops 中的 alloc() 回调分配 DMA 缓冲区,然后通过 mmap 系统调用映射到用户空间。分配的内存可以是 DMA 连续(通过 dma_alloc_coherent)或 DMA 非连续(通过 dma_buf + scatterlist),取决于驱动实现和硬件约束。
2.4.2 USERPTR 模型(零拷贝接收)
USERPTR 允许用户空间直接提供已分配的内存缓冲区。内核通过 get_user_pages 将用户页锁定并映射到 DMA 地址空间。此模型避免了内核到用户空间的数据拷贝,但要求用户空间内存必须物理连续或通过 IOMMU 支持。
2.4.3 DMABUF 模型(跨设备共享)
DMABUF 是最高级的模型,通过 dma-buf 文件描述符实现缓冲区共享。一个设备导出 dma-buf,另一个设备导入。此模型在复杂多媒体 pipeline(如 ISP → GPU → 显示)中至关重要,可避免多次内存拷贝。
性能对比: MMAP 在单设备场景下性能最优(分配快、CPU 访问直接);USERPTR 适合用户态已有内存池的场景;DMABUF 在多设备 pipeline 中延迟最低,但实现复杂度最高。对于 DMA 调优,优先选择 MMAP 并确保分配的是 DMA 连续内存。
2.5 操作回调:驱动与核心的契约
vb2 框架通过两组回调函数实现驱动与核心的解耦:vb2_ops 用于驱动逻辑控制,vb2_mem_ops 用于内存分配策略。
2.5.1 vb2_ops:驱动行为回调
| 回调函数 | 调用时机 | 驱动职责 |
|---|---|---|
queue_setup | REQBUFS 或 STREAMON 时 | 验证格式、设置缓冲区数量和大小 |
buf_prepare | 每个缓冲区入队前 | 验证缓冲区参数、准备 DMA 描述符 |
buf_queue | 缓冲区入队后 | 将缓冲区加入硬件 DMA 队列 |
start_streaming | STREAMON 时 | 启动 DMA 引擎、开始传输 |
stop_streaming | STREAMOFF 时 | 停止 DMA、取消未完成缓冲区 |
buf_finish | 缓冲区出队后 | 清理 DMA 描述符、解除映射 |
2.5.2 vb2_mem_ops:内存操作回调
struct vb2_mem_ops {
void *(*alloc)(struct vb2_buffer *vb, struct device *dev,
unsigned long size);
void (*put)(void *buf_priv);
struct dma_buf *(*get_dmabuf)(struct vb2_buffer *vb,
void *buf_priv);
int (*map_dmabuf)(struct vb2_buffer *vb, struct dma_buf *dbuf);
void (*unmap_dmabuf)(struct vb2_buffer *vb, struct dma_buf *dbuf);
void *(*get_userptr)(struct device *dev, unsigned long vaddr,
unsigned long size, enum dma_data_direction dir);
void (*put_userptr)(void *buf_priv);
// ... 其他回调
};
调优实践: 在 queue_setup 回调中,根据硬件对齐要求调整 sizeimage(例如对齐到 32 字节)。在 buf_prepare 中,提前分配 DMA 描述符并建立映射,避免在 buf_queue 中做耗时操作。对于高帧率场景,建议在 start_streaming 中预填充多个缓冲区到硬件队列。
2.6 核心概念关系总结
下图展示了 vb2_queue、vb2_buffer、vb2_mem_ops 和 vb2_ops 之间的协作关系:
学习路径建议: 掌握本章核心概念后,建议阅读内核源码 drivers/media/common/videobuf2/videobuf2-core.c 中的 __vb2_queue_cancel() 和 vb2_core_qbuf() 函数,它们展示了缓冲区状态转换和队列管理的完整实现。下一章将深入 vb2_queue 的初始化与配置细节。