2. V4L2 核心数据结构:struct video_device、struct v4l2_device、struct vb2_queue
在深入 mmap 与 poll 实现之前,必须理解 V4L2 框架中三个最核心的数据结构。它们构成了从设备注册、视频节点管理到缓冲区队列流转的完整骨架。掌握这些结构体的职责与关联,是编写正确驱动与应用程序的基础。
2.1 数据结构全景关系
下图展示了三个核心结构体在 V4L2 框架中的层次关系与协作方式:
2.2 struct v4l2_device — 顶层设备容器
struct v4l2_device 是 V4L2 框架中最顶层的抽象结构,代表一个完整的视频设备实例。它作为容器管理所有子设备(subdev)和全局状态信息。
| 字段 | 类型 | 说明 |
|---|---|---|
dev |
struct device * |
指向内核通用设备结构,用于电源管理、sysfs 等 |
subdevs |
struct list_head |
子设备链表头,管理所有挂载的 subdev |
ctrl_handler |
struct v4l2_ctrl_handler * |
控制处理器,管理亮度、对比度等控制项 |
name |
char[32] |
设备名称,用于调试和标识 |
notify |
void (*notify)() |
子设备通知回调函数 |
驱动开发要点:在 probe 函数中,驱动必须首先调用 v4l2_device_register() 注册顶层设备。该函数接受父 struct device * 和 struct v4l2_device * 作为参数。所有后续的 video_device 和 subdev 都依附于此结构。
2.3 struct video_device — 字符设备节点
struct video_device 是用户空间直接交互的接口,对应 /dev/videoX 字符设备节点。它封装了 file_operations 的实现,并将用户请求路由到具体的驱动回调。
| 字段 | 类型 | 说明 |
|---|---|---|
fops |
const struct v4l2_file_operations * |
V4L2 文件操作函数表(open/release/ioctl/mmap/poll) |
v4l2_dev |
struct v4l2_device * |
指向所属的顶层 v4l2_device |
queue |
struct vb2_queue * |
指向缓冲区队列,用于流 I/O |
minor |
int |
次设备号,用于标识设备节点 |
device_caps |
u32 |
设备能力标志(如 V4L2_CAP_VIDEO_CAPTURE) |
ioctl_ops |
const struct v4l2_ioctl_ops * |
ioctl 操作函数表,处理 VIDIOC_* 命令 |
关键理解:用户空间的 open("/dev/video0") 最终调用的是 video_device.fops->open。而 ioctl(fd, VIDIOC_REQBUFS, ...) 则通过 video_device.ioctl_ops 路由到 vb2_queue 的缓冲区管理函数。这种分层设计使得驱动开发者只需实现底层硬件操作,V4L2 框架自动完成标准接口的封装。
2.4 struct vb2_queue — 缓冲区队列引擎
struct vb2_queue 是 V4L2 帧采集的核心引擎,负责缓冲区生命周期管理、内存分配、DMA 映射以及流控制。mmap 映射和 poll 机制都直接依赖于 vb2_queue 的正确配置。
| 字段 | 类型 | 说明 |
|---|---|---|
type |
enum v4l2_buf_type |
缓冲区类型(V4L2_BUF_TYPE_VIDEO_CAPTURE 等) |
io_modes |
u32 |
支持的 I/O 模式位掩码(VB2_MMAP | VB2_USERPTR | VB2_DMABUF) |
ops |
const struct vb2_ops * |
驱动回调函数表(queue_setup/buf_prepare/buf_queue/start_streaming/stop_streaming) |
mem_ops |
const struct vb2_mem_ops * |
内存操作函数表(alloc/put/get_dmabuf/attach_dmabuf 等) |
buf_struct_size |
unsigned int |
每个缓冲区私有数据的大小(驱动可扩展) |
min_buffers_needed |
unsigned int |
启动流所需的最小缓冲区数量 |
timestamp_flags |
u32 |
时间戳标志(V4L2_BUF_FLAG_TIMESTAMP_*) |
2.5 vb2_ops 回调函数详解
struct vb2_ops 是驱动与 vb2_queue 之间的契约。驱动必须实现这些回调,vb2_queue 在合适的时机调用它们:
| 回调函数 | 调用时机 | 驱动职责 |
|---|---|---|
queue_setup |
REQBUFS 或 CREATE_BUFS 时 | 返回缓冲区数量、大小和格式信息 |
buf_prepare |
QBUF 时,缓冲区入队前 | 验证缓冲区参数,准备 DMA 映射 |
buf_queue |
QBUF 时,缓冲区入队后 | 将缓冲区加入硬件处理队列 |
start_streaming |
STREAMON 时 | 启动硬件采集,至少 min_buffers_needed 个缓冲区已入队 |
stop_streaming |
STREAMOFF 或出错时 | 停止硬件采集,取消未完成的缓冲区 |
buf_finish |
DQBUF 后或清理时 | 清理缓冲区资源(可选) |
常见陷阱:许多初学者在 queue_setup 中返回固定的缓冲区数量,但未考虑 min_buffers_needed 的约束。如果 min_buffers_needed = 4,而用户只请求 3 个缓冲区,start_streaming 将不会被调用。驱动应检查 reqbufs.count 并确保满足最小需求。
2.6 数据结构初始化流程
在驱动 probe 函数中,三个核心结构的初始化顺序至关重要:
- 注册 v4l2_device:调用
v4l2_device_register(parent_dev, &v4l2_dev) - 初始化 vb2_queue:填充
struct vb2_queue字段,调用vb2_queue_init(&v4l2_dev, &vb2_q) - 注册 video_device:设置
video_device.v4l2_dev和video_device.queue,调用video_register_device(&video_dev, VFL_TYPE_VIDEO, -1)
// 典型驱动 probe 中的初始化代码片段
static int mydriver_probe(struct platform_device *pdev)
{
struct mydriver_dev *dev;
int ret;
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
// 1. 注册 v4l2_device
ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev);
if (ret < 0)
return ret;
// 2. 初始化 vb2_queue
dev->vb2_q.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
dev->vb2_q.io_modes = VB2_MMAP | VB2_USERPTR;
dev->vb2_q.ops = &mydriver_vb2_ops;
dev->vb2_q.mem_ops = &vb2_dma_contig_memops;
dev->vb2_q.drv_priv = dev;
dev->vb2_q.min_buffers_needed = 2;
dev->vb2_q.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
ret = vb2_queue_init(&dev->vb2_q);
if (ret < 0)
goto err_v4l2;
// 3. 注册 video_device
dev->video_dev.v4l2_dev = &dev->v4l2_dev;
dev->video_dev.queue = &dev->vb2_q;
dev->video_dev.fops = &mydriver_fops;
dev->video_dev.ioctl_ops = &mydriver_ioctl_ops;
dev->video_dev.device_caps = V4L2_CAP_VIDEO_CAPTURE |
V4L2_CAP_STREAMING;
ret = video_register_device(&dev->video_dev,
VFL_TYPE_VIDEO, -1);
if (ret < 0)
goto err_queue;
platform_set_drvdata(pdev, dev);
return 0;
err_queue:
vb2_queue_release(&dev->vb2_q);
err_v4l2:
v4l2_device_unregister(&dev->v4l2_dev);
return ret;
}
2.7 数据结构关系总结
下表总结了三个核心结构在 mmap 与 poll 机制中的角色:
| 结构体 | mmap 中的角色 | poll 中的角色 |
|---|---|---|
v4l2_device |
提供设备上下文,管理内存分配器 | 提供中断处理上下文 |
video_device |
实现 v4l2_fops->mmap,调用 vb2_mmap |
实现 v4l2_fops->poll,调用 vb2_poll |
vb2_queue |
管理 DMA 缓冲区,建立用户空间映射 | 维护缓冲区状态,触发 POLLIN/POLLOUT 事件 |
核心结论:用户空间的 mmap 调用最终由 vb2_queue 的 mem_ops->mmap 完成物理内存到用户空间的映射。而 poll 调用则由 vb2_queue 的缓冲区状态机驱动:当有缓冲区完成采集(DQBUF 可用)时,触发 POLLIN 事件唤醒等待的进程。下一章将深入分析 mmap 映射的具体实现细节。