4. V4L2 核心数据结构:struct video_device、struct v4l2_device、struct vb2_queue

在 V4L2 摄像头驱动中,三个核心数据结构构成了驱动框架的骨架:struct video_device 代表用户空间看到的设备节点,struct v4l2_device 是顶层管理容器,struct vb2_queue 负责视频缓冲区的管理与流转。理解这三者的关系与职责,是编写稳定、高效的摄像头驱动的第一步。

4.1 数据结构关系总览

下图展示了三个核心数据结构在驱动中的组织关系:

V4L2 核心数据结构关系图 struct v4l2_device struct video_device struct vb2_queue 包含 包含 关联 用户空间 (dev/videoX) 摄像头硬件 (Sensor) 提供接口 驱动 DMA 说明: v4l2_device 是顶层容器,管理 video_device 和 vb2_queue;video_device 面向用户空间,vb2_queue 面向硬件 DMA。

4.2 struct v4l2_device — 顶层容器

struct v4l2_device 是 V4L2 驱动的根对象,负责管理所有子设备、注册信息和全局状态。每个物理摄像头设备对应一个 v4l2_device 实例。

4.2.1 核心字段

字段 类型 说明
dev struct device * 指向内核通用设备结构,用于电源管理、sysfs 等
v4l2_dev struct list_head 用于将本设备链接到全局 V4L2 设备链表
subdevs struct list_head 子设备链表头,管理所有关联的 subdev
name char[32] 设备名称,用于日志和识别
notify void (*notify)() 子设备通知回调函数
ctrl_handler struct v4l2_ctrl_handler * 控制句柄,管理亮度、对比度等控制项

4.2.2 初始化与注册

#include <media/v4l2-device.h>

struct v4l2_device v4l2_dev;

// 1. 初始化 v4l2_device
v4l2_device_init(&v4l2_dev, &pdev->dev);

// 2. 注册(通常在 probe 函数中)
v4l2_device_register(&pdev->dev, &v4l2_dev);

// 3. 注销(在 remove 函数中)
v4l2_device_unregister(&v4l2_dev);
最佳实践: v4l2_device 应在驱动 probe 函数中尽早初始化,作为所有子设备和 video_device 的父容器。注销时,内核会自动清理已注册的子设备。

4.3 struct video_device — 设备节点抽象

struct video_device 是用户空间通过 /dev/videoX 访问的接口抽象。它封装了文件操作、ioctl 处理和设备能力信息。

4.3.1 核心字段

字段 类型 说明
fops const struct v4l2_file_operations * 文件操作函数集(open、release、ioctl 等)
ioctl_ops const struct v4l2_ioctl_ops * ioctl 操作函数集(VIDIOC_QUERYCAP、VIDIOC_S_FMT 等)
v4l2_dev struct v4l2_device * 指向所属的 v4l2_device 容器
queue struct vb2_queue * 指向视频缓冲队列
minor int 次设备号,由内核自动分配
name char[32] 设备名称,显示在 sysfs 中
device_caps u32 设备能力标志(V4L2_CAP_VIDEO_CAPTURE 等)

4.3.2 注册流程

struct video_device vdev;

// 1. 分配并初始化 video_device
vdev = video_device_alloc();
if (!vdev)
    return -ENOMEM;

// 2. 设置文件操作和 ioctl 操作
vdev->fops = &my_fops;
vdev->ioctl_ops = &my_ioctl_ops;

// 3. 关联 v4l2_device 和 vb2_queue
vdev->v4l2_dev = &v4l2_dev;
vdev->queue = &vb2_q;

// 4. 设置设备能力
vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;

// 5. 注册 video_device
video_register_device(vdev, VFL_TYPE_VIDEO, -1);

// 6. 注销(在 remove 中)
video_unregister_device(vdev);
video_device_release(vdev);
注意: video_device_alloc() 和 video_device_release() 必须配对使用。如果使用栈上分配的 struct video_device,应使用 video_set_drvdata() 和 video_device_release_empty() 避免双重释放。

4.4 struct vb2_queue — 视频缓冲队列

struct vb2_queue 是 videobuf2 框架的核心,负责管理视频缓冲区的分配、入队、出队和 DMA 操作。它屏蔽了不同内存类型(V4L2_MEMORY_MMAP/USERPTR/DMABUF)的差异。

4.4.1 核心字段

字段 类型 说明
type enum v4l2_buf_type 缓冲区类型(V4L2_BUF_TYPE_VIDEO_CAPTURE 等)
io_modes unsigned int 支持的 I/O 模式(VB2_MMAP | VB2_USERPTR | VB2_DMABUF)
ops const struct vb2_ops * 队列操作回调函数集
mem_ops const struct vb2_mem_ops * 内存操作回调函数集
buf_struct_size unsigned int 每个缓冲区私有数据的大小
drv_priv void * 驱动私有数据指针
timestamp_flags unsigned int 时间戳标志(V4L2_BUF_FLAG_TIMESTAMP_*)

4.4.2 struct vb2_ops 回调函数

回调函数 调用时机 典型实现
queue_setup 缓冲区分配前 设置图像格式、大小、缓冲区数量
buf_prepare 缓冲区入队前 验证缓冲区参数,准备 DMA 映射
buf_queue 缓冲区入队后 将缓冲区加入硬件 DMA 队列
start_streaming 启动视频流 开启摄像头传感器,启动 DMA
stop_streaming 停止视频流 停止 DMA,关闭传感器,清理缓冲区
buf_finish 缓冲区出队后 解除 DMA 映射,释放资源

4.4.3 初始化示例

struct vb2_queue vb2_q;

// 1. 初始化 vb2_queue 结构
vb2_q.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vb2_q.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
vb2_q.drv_priv = &my_dev;
vb2_q.buf_struct_size = sizeof(struct my_buffer);
vb2_q.ops = &my_vb2_ops;
vb2_q.mem_ops = &vb2_dma_contig_memops;
vb2_q.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;

// 2. 注册队列
vb2_queue_init(&vb2_q);

// 3. 关联到 video_device
vdev->queue = &vb2_q;

4.5 三者协作流程

下图展示了从用户空间请求到硬件 DMA 完成的完整数据流:

V4L2 数据流协作流程 1. 用户 open /dev/videoX 2. video_device 处理 ioctl 3. vb2_queue 分配缓冲区 4. 用户 QBUF 入队 5. buf_queue 回调 6. 硬件 DMA 填充 7. DQBUF 出队 8. 数据返回用户空间 循环

4.6 三者的关键差异对比

维度 v4l2_device video_device vb2_queue
职责 顶层容器管理 设备节点抽象 缓冲区管理
面向对象 内核驱动层 用户空间接口 硬件 DMA 层
注册函数 v4l2_device_register() video_register_device() vb2_queue_init()
生命周期 驱动 probe 到 remove 注册后到注销前 流开启到流关闭
关键回调 notify fops、ioctl_ops vb2_ops
错误处理 子设备注册失败 设备号冲突 内存不足、DMA 错误

4.7 常见错误与避坑指南

  • 未初始化 v4l2_device: 在注册 video_device 之前,必须确保 v4l2_device 已经初始化并注册,否则会导致内核崩溃。
  • video_device 与 vb2_queue 关联遗漏: 如果 vdev->queue 未正确指向 vb2_queue,用户空间的 VIDIOC_REQBUFS 将返回 ENOTTY。
  • vb2_ops 回调未实现: queue_setup 和 buf_queue 是必须实现的回调,缺少会导致缓冲区操作失败。
  • 内存类型不匹配: io_modes 必须与 mem_ops 匹配,例如使用 VB2_DMA_CONTIG 时需搭配 vb2_dma_contig_memops。
  • 并发访问未加锁: vb2_queue 的操作可能被多个线程同时调用,驱动需使用自旋锁或互斥锁保护共享资源。

4.8 本章小结

三个核心数据结构构成了 V4L2 摄像头驱动的基石:

  • struct v4l2_device 作为顶层容器,管理设备生命周期和子设备。
  • struct video_device 提供用户空间访问接口,封装文件操作和 ioctl。
  • struct vb2_queue 负责缓冲区分配、DMA 传输和流控制。