4. ioctl 基础:V4L2 ioctl 编号规则与核心 ioctl 列表

V4L2 驱动与用户空间应用程序之间的主要通信通道是 ioctl 系统调用。理解 V4L2 的 ioctl 编号规则是进行驱动开发和测试的基础,它决定了命令的唯一性、数据方向以及参数大小。

4.1 V4L2 ioctl 编号规则(_IOR / _IOW / _IOWR)

Linux 内核使用一套宏来生成唯一的 ioctl 命令编号,确保不同子系统之间的命令不会冲突。V4L2 严格遵循这套规则。每个 ioctl 命令编号由以下四部分组成:

  • 幻数(Magic Number / Type):V4L2 使用 'V'(ASCII 0x76)作为幻数。
  • 序号(Number / NR):在 V4L2 子系统内的唯一序号,从 0 开始递增。
  • 数据方向(Direction):指示数据在用户空间和内核空间之间的流动方向。
  • 数据大小(Size):传递的参数结构体的大小(以字节为单位)。

内核提供了四个宏来构造 ioctl 命令:

宏 方向 含义 典型用途
_IO(type, nr) 无数据 不带数据的简单命令 查询/设置状态(如流开关)
_IOR(type, nr, size) 从内核读 用户空间读取内核数据 查询能力、获取格式
_IOW(type, nr, size) 写入内核 用户空间向内核写入数据 设置格式、设置参数
_IOWR(type, nr, size) 双向 先写入后读取(或反之) 查询并修改、扩展命令
关键点: V4L2 的所有 ioctl 命令都使用 'V' 作为幻数。例如,VIDIOC_QUERYCAP 的定义为 _IOR('V', 0, struct v4l2_capability),表示这是一个从内核读取 v4l2_capability 结构体的命令。

以下是一个典型的 V4L2 ioctl 命令定义示例:

// 定义在 include/uapi/linux/videodev2.h 中
#define VIDIOC_QUERYCAP       _IOR('V',  0, struct v4l2_capability)
#define VIDIOC_ENUM_FMT       _IOWR('V',  2, struct v4l2_fmtdesc)
#define VIDIOC_S_FMT          _IOWR('V',  4, struct v4l2_format)
#define VIDIOC_REQBUFS        _IOWR('V',  8, struct v4l2_requestbuffers)
#define VIDIOC_QBUF           _IOWR('V', 15, struct v4l2_buffer)
#define VIDIOC_DQBUF          _IOWR('V', 17, struct v4l2_buffer)
#define VIDIOC_STREAMON       _IO('V', 18)
#define VIDIOC_STREAMOFF      _IO('V', 19)

4.2 核心 ioctl 列表(VIDIOC_QUERYCAP 等)

V4L2 规范定义了大量 ioctl 命令,覆盖了从设备查询到数据流控制的完整生命周期。以下是开发者和测试人员必须掌握的核心 ioctl 列表,按功能分组:

4.2.1 设备查询与能力

ioctl 命令 方向 参数结构体 功能描述
VIDIOC_QUERYCAP _IOR struct v4l2_capability 查询设备能力(驱动名、卡名、总线信息、支持的功能标志)
VIDIOC_ENUM_FMT _IOWR struct v4l2_fmtdesc 枚举支持的像素格式
VIDIOC_ENUM_FRAMESIZES _IOWR struct v4l2_frmsizeenum 枚举支持的帧尺寸
VIDIOC_ENUM_FRAMEINTERVALS _IOWR struct v4l2_frmivalenum 枚举支持的帧间隔(帧率)

4.2.2 格式协商

ioctl 命令 方向 参数结构体 功能描述
VIDIOC_G_FMT _IOWR struct v4l2_format 获取当前数据格式
VIDIOC_S_FMT _IOWR struct v4l2_format 设置数据格式(驱动可能调整参数)
VIDIOC_TRY_FMT _IOWR struct v4l2_format 尝试格式但不实际设置(用于探测)

4.2.3 缓冲区管理

ioctl 命令 方向 参数结构体 功能描述
VIDIOC_REQBUFS _IOWR struct v4l2_requestbuffers 请求分配缓冲区(内存映射/用户指针/DMABUF)
VIDIOC_QUERYBUF _IOWR struct v4l2_buffer 查询已分配缓冲区的信息(偏移、长度等)
VIDIOC_QBUF _IOWR struct v4l2_buffer 将缓冲区放入队列(入队)
VIDIOC_DQBUF _IOWR struct v4l2_buffer 从队列取出缓冲区(出队,通常阻塞等待)
VIDIOC_EXPBUF _IOWR struct v4l2_exportbuffer 导出缓冲区为 DMABUF 文件描述符

4.2.4 流控制

ioctl 命令 方向 参数结构体 功能描述
VIDIOC_STREAMON _IO 无(传入缓冲队列类型指针) 启动视频流
VIDIOC_STREAMOFF _IO 无(传入缓冲队列类型指针) 停止视频流

4.2.5 控制与事件

ioctl 命令 方向 参数结构体 功能描述
VIDIOC_QUERYCTRL _IOWR struct v4l2_queryctrl 查询控制项信息(亮度、对比度等)
VIDIOC_G_CTRL _IOWR struct v4l2_control 获取控制项当前值
VIDIOC_S_CTRL _IOWR struct v4l2_control 设置控制项值
VIDIOC_DQEVENT _IOR struct v4l2_event 出队一个事件(如帧丢失、控制变化)
测试提示: 在 v4l2-compliance 测试中,VIDIOC_QUERYCAP 是第一个被调用的 ioctl。如果这个调用失败,测试会立即终止并报告“无法查询设备能力”。确保驱动正确实现 vidioc_querycap 回调是驱动测试通过的第一步。

4.3 V4L2 ioctl 调用流程

下图展示了用户空间应用程序通过 ioctl 与 V4L2 驱动交互的典型流程,以及 ioctl 编号在其中的作用:

V4L2 ioctl 调用流程与编号规则 用户空间应用程序 系统调用:ioctl(fd, VIDIOC_xxx, &arg) V4L2 核心:根据 ioctl 编号查找驱动回调 _IOR('V', 0, ...) → vidioc_querycap 驱动回调函数(如 vidioc_querycap) 填充结构体 → 返回 0(成功)或 -EXXX(错误) 返回用户空间:检查返回值,处理数据 ioctl 编号由幻数('V') + 序号 + 方向 + 大小组成,内核通过编号直接索引到对应的驱动回调

4.4 ioctl 编号的调试与验证

在驱动开发和测试过程中,验证 ioctl 编号是否正确生成和解析非常重要。可以使用 strace 工具捕获应用程序发出的 ioctl 调用:

# 使用 strace 捕获 v4l2-ctl 的 ioctl 调用
strace -e ioctl v4l2-ctl -d /dev/video0 --list-formats 2>&1 | grep VIDIOC

# 输出示例:
# ioctl(3, VIDIOC_QUERYCAP, {driver="uvcvideo", card="UVC Camera"}) = 0
# ioctl(3, VIDIOC_ENUM_FMT, {index=0, type=V4L2_BUF_TYPE_VIDEO_CAPTURE, ...}) = 0
常见错误: 如果驱动中错误地使用了 _IOR 代替 _IOWR(或反之),虽然 ioctl 编号可能不同,但更严重的问题是内核的访问权限检查会失败。例如,一个需要写入数据的命令被错误标记为只读,内核可能会拒绝复制用户空间数据,导致驱动读取到未初始化的内存。

4.5 核心 ioctl 调用顺序

一个典型的 V4L2 视频采集应用程序会按照以下顺序调用核心 ioctl:

  1. VIDIOC_QUERYCAP:检查设备是否支持所需功能(如视频捕获)。
  2. VIDIOC_ENUM_FMT / VIDIOC_ENUM_FRAMESIZES:枚举支持的格式和分辨率。
  3. VIDIOC_S_FMT:设置采集格式和分辨率。
  4. VIDIOC_REQBUFS:请求分配缓冲区(通常为 3~4 个)。
  5. VIDIOC_QUERYBUF:查询每个缓冲区的信息,用于 mmap 映射。
  6. VIDIOC_QBUF:将所有缓冲区入队。
  7. VIDIOC_STREAMON:启动视频流。
  8. VIDIOC_DQBUF:循环出队已填充数据的缓冲区(处理数据)。
  9. VIDIOC_QBUF:将处理完的缓冲区重新入队。
  10. VIDIOC_STREAMOFF:停止视频流。
测试要点: v4l2-compliance 会严格按照上述顺序测试每个 ioctl 的合法性,并检查驱动在异常调用顺序下的行为(例如,在未调用 REQBUFS 的情况下直接调用 QBUF 应返回错误)。