3. V4L2 核心数据结构:关系与作用

V4L2 框架通过一组精心设计的内核数据结构,将复杂的视频设备驱动抽象为可管理的层次。理解 struct video_device、struct v4l2_device、struct v4l2_subdev、v4l2_fh 和 vb2_queue 之间的关系,是掌握 V4L2 驱动开发的关键。这些结构体共同构成了从物理硬件到用户空间 /dev/videoX 节点的完整数据通路。

3.1 核心结构体总览

下表总结了五个核心结构体的角色与定位:

结构体 头文件 核心作用 生命周期
struct v4l2_device media/v4l2-device.h 顶层容器,管理整个 V4L2 设备实例,提供子设备注册与通知机制 驱动加载 → 卸载
struct video_device media/v4l2-dev.h 字符设备抽象,关联 /dev/videoX 节点,实现 file_operations 设备创建 → 销毁
struct v4l2_subdev media/v4l2-subdev.h 表示一个功能子单元(如传感器、ISP、解码器),通过 v4l2_device 注册 子设备探测 → 移除
v4l2_fh media/v4l2-fh.h 文件句柄上下文,管理每个打开实例的事件订阅、优先级和私有数据 open() → release()
vb2_queue media/videobuf2-core.h 视频缓冲队列引擎,负责缓冲区分配、入队、出队和硬件 DMA 交互 队列初始化 → 释放

3.2 结构体关系架构图

下图展示了 V4L2 核心数据结构之间的组合与引用关系:

V4L2 核心数据结构关系图 V4L2 核心数据结构关系图 struct v4l2_device 顶层容器 / 设备实例 subdevs 链表 管理所有子设备 video_devices 链表 管理所有视频节点 notify 回调 子设备 → 主设备通知 struct v4l2_subdev 传感器 / ISP / 解码器 struct video_device /dev/videoX 字符设备 v4l2_fh 文件句柄上下文 open() 创建 vb2_queue 视频缓冲队列引擎 通过 ioctl 访问 关键关系说明: • v4l2_device 是顶层容器,包含 subdevs 链表和 video_devices 链表 • video_device 通过 v4l2_fh 管理每个打开的文件句柄,v4l2_fh 再关联 vb2_queue

3.3 各结构体深度解析

3.3.1 struct v4l2_device — 顶层容器

struct v4l2_device 是整个 V4L2 设备实例的根节点。它通常嵌入在更大的设备结构体中(如 struct camera_device),并提供以下核心能力:

  • 子设备管理:通过 struct list_head subdevs 链表维护所有注册的 v4l2_subdev 实例。
  • 视频节点管理:通过 struct list_head video_devices 链表跟踪所有 video_device 实例。
  • 通知机制:提供 notify() 回调,允许子设备向主设备发送异步事件(如传感器状态变化)。
  • 调试与锁:包含 spinlock_t 和调试文件系统接口。

驱动开发要点: 在 probe 函数中,首先调用 v4l2_device_register() 注册顶层设备。该函数接受 struct device *parent 作为参数,建立与硬件设备的父子关系。

3.3.2 struct video_device — 字符设备抽象

struct video_device 是用户空间直接交互的对象。每个 video_device 对应一个 /dev/videoX 节点,其核心成员包括:

  • fops:struct v4l2_file_operations *fops,定义 open/release/read/write/ioctl 等操作。
  • v4l2_dev:指向所属的 v4l2_device 实例。
  • queue:指向 vb2_queue,用于流 I/O 的缓冲队列。
  • ctrl_handler:V4L2 控制处理器,管理曝光、增益等控制参数。
  • dev_debug:调试标志位,控制调试信息的输出级别。

注册流程通常如下:

// 1. 分配并初始化 video_device
struct video_device *vdev = video_device_alloc();
vdev->fops = &my_fops;
vdev->v4l2_dev = &my_v4l2_dev;
vdev->queue = &my_queue;
strscpy(vdev->name, "My Camera", sizeof(vdev->name));

// 2. 设置 ioctl 操作集
vdev->ioctl_ops = &my_ioctl_ops;

// 3. 注册到 V4L2 框架
video_register_device(vdev, VFL_TYPE_VIDEO, -1);

3.3.3 struct v4l2_subdev — 子设备抽象

现代摄像头系统通常包含多个硬件模块:传感器、ISP、闪光灯控制器等。struct v4l2_subdev 为每个模块提供统一的抽象接口。关键特性:

  • ops 操作集:包含 core、video、pad、sensor 等多组回调函数。
  • pad 与路由:通过 struct media_entity 支持媒体控制器拓扑,定义输入/输出 pad。
  • 内部锁:提供 lock 字段,保护子设备状态。
  • 异步注册:支持 v4l2_async_subdev 机制,允许子设备在不确定的时间点完成探测。

最佳实践: 在 Camera HAL3 实现中,每个物理摄像头传感器通常对应一个 v4l2_subdev。通过 v4l2_subdev_call() 宏调用子设备操作,实现驱动层与硬件解耦。

3.3.4 v4l2_fh — 文件句柄上下文

v4l2_fh 是每个打开的文件描述符的私有数据结构。它解决了多客户端并发访问时的状态隔离问题:

  • 事件订阅:管理 v4l2_event 的订阅列表,支持异步事件通知。
  • 优先级管理:记录当前文件句柄的访问优先级(V4L2_PRIORITY_*)。
  • 私有数据:通过 fh->priv 指针,驱动可以挂载自定义上下文。
  • 链表节点:通过 list 字段挂入 video_device 的 fh_list。

典型用法:

static int my_open(struct file *file)
{
    struct video_device *vdev = video_devdata(file);
    struct v4l2_fh *fh = kzalloc(sizeof(*fh), GFP_KERNEL);

    v4l2_fh_init(fh, vdev);
    v4l2_fh_add(fh);
    file->private_data = fh;

    return 0;
}

static int my_release(struct file *file)
{
    struct v4l2_fh *fh = file->private_data;

    v4l2_fh_del(fh);
    v4l2_fh_exit(fh);
    kfree(fh);

    return 0;
}

3.3.5 vb2_queue — 视频缓冲队列引擎

vb2_queue 是 V4L2 框架中最复杂、最重要的数据结构之一。它实现了完整的 DMA 缓冲管理生命周期:

阶段 操作 回调函数
队列初始化 vb2_queue_init() queue_setup:协商缓冲区格式与数量
缓冲区分配 REQBUFS ioctl buf_init / buf_alloc:分配 DMA 内存
缓冲区入队 QBUF ioctl buf_prepare:准备硬件访问
缓冲区出队 DQBUG ioctl buf_finish:完成硬件访问
流开启 STREAMON ioctl start_streaming:启动 DMA
流停止 STREAMOFF ioctl stop_streaming:停止 DMA

vb2_queue 的核心成员:

  • type:缓冲区类型(V4L2_BUF_TYPE_VIDEO_CAPTURE / V4L2_BUF_TYPE_VIDEO_OUTPUT)。
  • io_modes:支持的 I/O 模式(VB2_MMAP / VB2_USERPTR / VB2_DMABUF)。
  • ops:struct vb2_ops,包含上述生命周期回调。
  • mem_ops:struct vb2_mem_ops,定义内存分配与映射操作。
  • bufs:缓冲区指针数组,管理所有已分配的 vb2_buffer。
  • done_list:已完成(等待出队)的缓冲区链表。

3.4 数据流全景图

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

V4L2 数据流全景图 V4L2 数据流全景图 用户空间 Camera HAL3 / 应用程序 open / ioctl / mmap 内核空间 — 字符设备层 video_device / v4l2_fh / file_operations v4l2_ioctl 分发 V4L2 核心层 vb2_queue / v4l2_subdev / 控制框架 vb2_ops / subdev_ops 回调 驱动层 DMA 引擎 / I2C 控制 / 硬件寄存器 硬件设备(Sensor / ISP / DMA)

3.5 实战要点总结

常见陷阱:

  • 生命周期管理:v4l2_device 必须在所有子设备和视频设备之前注册,在它们之后注销。
  • 锁顺序:避免在 vb2_ops 回调中持有 v4l2_device 的全局锁,否则可能导致死锁。
  • v4l2_fh 泄漏:每个 open() 必须对应一个 release(),确保 v4l2_fh_del() 和 v4l2_fh_exit() 被调用。
  • 缓冲区对齐:vb2_queue 的 buf_struct_size 必须正确设置,否则会导致缓冲区结构体访问越界。