4. V4L2核心数据结构
在 V4L2 摄像头驱动中,三个核心数据结构构成了驱动框架的骨架:struct video_device 代表用户空间看到的设备节点,struct v4l2_device 是顶层管理容器,struct vb2_queue 负责视频缓冲区的管理与流转。理解这三者的关系与职责,是编写稳定、高效的摄像头驱动的第一步。
4.1 数据结构关系总览
下图展示了三个核心数据结构在驱动中的组织关系:
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 完成的完整数据流:
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 传输和流控制。