news 2026/5/19 9:18:25

Linux驱动之V4L2设备驱动详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux驱动之V4L2设备驱动详解

在学完v4l2应用层编程之后,我总是对底层camera驱动有着强烈的好奇心,底层是怎么驱动摄像头的?v4l2驱动和普通的字符设备驱动有什么区别?同样是通过ioctl来操作设备,为什么V4L2框架提供的ioctl CMD是固定的?

所以本文将从应用层open/read/write/ioctl开始,逐层解析v4l2框架的具体内容,同时深入理解V4L2驱动源码,最终总结出我们该如何写一个v4l2设备驱动以及为什么要使用V4L2框架。

在看本文之前,需要先具备Liunx字符设备驱动开发基础,V4L2应用编程基础

我们在使用V4L2应用层的时候,最繁杂的工作是通过一系列的ioctl来操作具体的硬件的,所以我们先从ioctl入手,解析一下在调用ioctl的时候,是怎么操作到具体摄像头的,在理解了ioctl的流程之后,类似open read write的系统调用就相当简单了。​​​​​

ioctl 指令功能说明
VIDIOC_QUERYCAP查询设备能力。传入v4l2_capability结构体,获取驱动名称、总线信息以及设备支持的功能(例如:是否为视频采集设备V4L2_CAP_VIDEO_CAPTURE,是否支持流式 I/OV4L2_CAP_STREAMING)。
VIDIOC_ENUM_FMT枚举支持格式。枚举设备支持的所有像素格式。应用程序通常使用循环(递增 index)来遍历。
VIDIOC_G_FMT获取当前格式。获取设备当前的格式设置。
VIDIOC_TRY_FMT测试格式支持。测试某一种格式或分辨率是否被硬件支持(硬件可能会自动修正为传入参数的近似值,但不会真正改变当前设置)。
VIDIOC_S_FMT设置视频格式。正式设置视频流的格式、分辨率和颜色空间。需传入v4l2_format结构体。
VIDIOC_REQBUFS申请缓冲区。向驱动申请分配视频缓冲区。传入v4l2_requestbuffers,告知驱动需要的缓冲区数量(通常 3-4 个)及内存管理方式(如V4L2_MEMORY_MMAP)。
VIDIOC_QUERYBUF查询缓冲区状态。查询已分配缓冲区的物理状态(长度、偏移量)。获取偏移量后,应用层可调用mmap()将内核内存映射到用户空间。
VIDIOC_QBUF缓冲区入队 (Queue)。将一个空闲的缓冲区“排队”交还给内核,供底层驱动向其中填充视频数据。
VIDIOC_DQBUF缓冲区出队 (Dequeue)。从内核“出队”一个已经装满视频数据的缓冲区,供应用层提取数据进行渲染或编码。
VIDIOC_STREAMON开启视频流。驱动开始捕获数据并填充到由QBUF交给它的缓冲区中。
VIDIOC_STREAMOFF停止视频流。硬件停止采集数据,所有缓冲区的状态会被重置。

VIDIOC_QUERYCTRL

VIDIOC_QUERYMENU

查询控制选项。查询设备支持哪些调节选项(例如:亮度调节的具体范围、是否包含自动对焦菜单等)。

VIDIOC_G_CTRL

VIDIOC_S_CTRL

获取/设置基础控制。用于获取或设置旧版的基础控制参数。

VIDIOC_G_EXT_CTRLS

VIDIOC_S_EXT_CTRLS

获取/设置扩展控制。用于获取或设置扩展控制参数,支持批量设置以及更复杂的硬件控制逻辑。

在写字符设备驱动的时候,我们知道,系统层的ioctl调用最终会由驱动中的unlocked_ioctl进行处理。而基于V4L2的驱动本质上也是一个字符设备驱动,那么在v4l2中也是一样,最终也会由一个unlocked_ioctl函数来进行处理。但是这里我们一定要理清楚两个点:

  • 普通字符设备驱动没有使用框架(当然除字符设备驱动框架外),应用层ioctl经过内核转发之后直接调用到我们编写的设备驱动中的ublocked_iotcl函数;驱动暴露给应用层相应的ioctl 指令,应用层用它来进行具体的ioctl操作,我们可以自己随意编写ioctl 指令暴露给应用层。

  • V4L2框架是一个比较复杂的框架,其中的核心层帮我们管理了很多事情,应用层ioctl经过内核转发之后并不能直接调用到我们编写的设备驱动中的ublocked_iotcl函数,还要经过V4L2核心的处理;V4L2框架中的ioctl 指令是固定的(如上表),这也很好理解,V4L2是一个通用的video驱动框架,倘若ioctl 指令仍然和普通字符设备驱动程序一样,可以随意编写,那么会给应用编程带来沉重的负担,写应用程序的时候,我们还要根据不同的摄像头来查询对应的ioctl 指令,严重加重了应用层的工作。

V4L2框架其实就是总结出摄像头驱动的共性,在分析调用流程的时候,我们一定要清楚,哪一部分是V4L2核心层的工作,哪一部分是我们编写的驱动完成的工作。

v4l2核心源码位于kernel/drivers/media/v4l2-core目录下,我使用的内核源码是6.1版本的,不同版本的内核源码在V4L2的实现上一些细节上会有区别,比如ioctl会区分是否需要特殊处理。我们暂时只从宏观上讨论调用流程,不去管这些差异。

在开始进行学习的时候,我们先逐步分析,整个系统的调用流程是什么样的,逐步解析每个结构体的作用,等到我们完整的建立起整个v4l2框架的认识之后,我们再来分析一个具体的驱动程序,来看看每个模块是怎么串联起来的。

在开始学习的时候,因为结构体太多,一定是会被绕晕的,这时候不要灰心,多往前面翻一翻,对照着结构体的定义来看,笔者也是在不断学习的过程中,希望能一起努力,一句掌握v4l2子系统。

调用流程分析

当应用应用层执行

ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0

或者类似的ioctl指令的时候,经内核处理后,首先进入v4l2核心层,在v4l2核心层有一些操作函数集,其定义位于kernel/drivers/media/v4l2-core目录下v4l2-dev.c中,在这个操作函数集中有v4l2_ioctl 、read、 write、 mmap等函数;这些函数是v4l2核心层已经写好的,不需要我们更改。

核心层会调用操作函数集中的v4l2_ioctl函数指针所指向的函数

可以看到,这个v4l2_fops本质上还是struct file_operations类型的结构体;下面我们进一步看看看v4l2_ioctl的具体处理内容:

这个处理函数也是v4l2框架写好的,首先是第一行,

struct video_device *vdev = video_devdata(filp);

这行代码的作用很重要,在v4l2核心层操作集的函数中几乎都要用到它;它的作用是根据内核传递的struct file指针filp找到具体的struct video_device,这个video_device我们这里暂且理解为对具体摄像头的描述,那么这一步就是在v4l2核心层中找到具体我们要操作的哪一个摄像头。

这一步也很好理解,我们在编写字符设备驱动程序的时候,在open函数中,通常会将字符设备指针保存在file->private_data中。在其他函数中就可以通过struct file* file来找到对应的字符设备。这里也是一样的。我们可以通过file来找到video_device。

随后会对设备进行一些检查,比如设备是否注册了unlocked_ioctl函数,设备是否注册到了内核中,然后执行

ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);

这一步是v4l2核心层 v4l2_ioctl 最关键的一步,vdev是前面找到的摄像头设备,vdev->fops说明这个设备里面也有操作函数集,vdev->fops->unlocked_ioctl是调用摄像头设备函数集fops中的unlocked_ioctl

可以得出,核心层在这里的工作非常简单,就是在核心层的操作函数集中转发对应的ioctl给驱动;同理对应的open read write mmap等函数也是一样,在核心层的操作函数集中转发对应的操作函数给驱动

所以核心层目前的工作就是将应用层的ioctl转发给具体的设备

那么核心层到这一步,工作好像完成了,因为下面会跳转到设备的操作函数集去执行相应的操作了,但是我们前面提到的,V4L2封装的各种ioctl指令是怎么使用的呢?我们需要在struct video_device的操作函数集中对每种ioctl进行判断吗?

我们不得不去研究一下struct video_device这个结构体。这个结构体也是我们在编写V4L2驱动的时候最重要的一个结构体

struct video_device结构体的内容非常多,我们只关注与上述分析流程相关的内容,其他内容会在后面逐步解析

struct video_device的定义位于内核源码中 kernel/include/media 目录下的v4l2-dev.h文件中

前面在v4l2核心层中执行的

等函数就位于 struct video_device 中的 fops 中,而这个fops是struct v4l2_file_operations类型的,

我们再进一步看 struct v4l2_file_operations的详细内容:

拆解到这里,我们已经到达流程的终点了,v4l2核心层的open read write 等系统调用最终都会调用到这里面相对应的函数。这里面的函数就是我们编写驱动的时候需要去实现的函数。但是实际情况并没有这么简单

iotcl驱动程序示例分析

这里不妨在内核源码中找一个对应的驱动程序示例来看看

在内核源码目录kernel/drivers/media/usb/airspy 下 airspy.c 中,可以看到对设备的定义:

airspy.c定义了一个设备airspy_template,并且填充了.name .release .fops .ioctl_ops等信息,前面我们说到struct video_device结构体里面有很多属性,为什么这里就填写了这么几个呢?这就是v4l2架构带来的好处,对于很多共性的东西,v4l2框架帮我们进行初始化了,v4l2还提供了很多辅助函数,帮助我们快速编写驱动代码,避免“重复造轮子”

我们需要关注的是 airspy_template 中的 fops 与 ioctl_ops操作函数集,为什么会有两个操作函数集?一个是我们熟知的、刚才分析的fops,还有一个ioctl_ops是什么?虽然从名字上能猜出来是和ioctl相关的,但是在fops中不是已经有unlocked_ioctl了吗,为什么这里还要一个ioctl_ops?

这就要提到我们在一开始提到的,V4L2的ioctl指令是固定的,这种固定方法的实现就需要用到这两个操作函数集

我们先来看看这个驱动例程是怎么实现fops的

绿框内是例程填充的信息,这些信息全部都是v4l2提供的辅助函数,也就是v4l2写好的函数。

我们可以简单地区分:

  • v4l2_开头:这类函数属于 V4L2 核心层(V4L2 Core)。它们负责处理那些不涉及具体图像数据的“控制流”。比如管理设备节点、解析 ioctl 指令、处理事件订阅等。

  • vb2_开头:这类函数属于Videobuf2 (VB2)子系统。这是 V4L2 框架中最复杂、最精华的部分。凡是和“视频数据流(Streaming)”、“内存分配”、“缓冲区映射”相关的底层通用逻辑,都在这里。

重点需要关注的是video_ioctl2这个函数,这个函数也是由内核提供的辅助函数,我们具体看它是如何实现的,这个函数定义在内核源码 kernel/drivers/media/v4l2-core目录中的 v4l2-ioctl.c,从名字能看出这个文件主要用于处理v4l2框架中的ioctl事件。

这里只是做了一些空户空间数据的拷贝,处理完后会跳转到参数中的:__video_do_ioctl继续执行,下面我们继续分析__video_do_ioctl函数。先来看前几行

我将其中需要重点关注的地方标注出来了,其中

非常关键!这个指针指向了一个v4l2_ioctl_info结构体,那v4l2_ioctl_info是什么?

上图是v4l2_ioctl_info结构体的定义,可以看到里面就是简单存储了一些属性以及函数指针。

现在我们再来看看这个info指针到底指向哪里,在源码中:

可以看到

首先是cmd,当应用层调用ioctl(fd, VIDIOC_QUERYCAP, &cap)时,这里的cmd就是VIDIOC_QUERYCAP这个宏的值。 在 Linux 系统中,ioctl的命令码并不是随便写的一个数字,而是一个经过严格设计的 32 位无符号整数。其位数代表着一些特殊的含义。每一个cmd都有一个唯一的编号(其实就是cmd的后八位)。

_IOC_NR(cmd)作用是根据cmd得到指令的编号。

v4l2_ioctls[] 是存放着各种 v4l2_ioctl_info 的数组,我们从里面取出用户传入的 cmd 所对应的v4l2_ioctl_info,将info指向它;

下面我们再来看info会做什么

会继续调用info里面的函数func

到此,ioctl的调用流程就又走到了终点;

其调用流程如下:

现在我们要分析的问题就变成了分析v4l2_ioctls[]数组,因为我们最终调用的函数是从v4l2_ioctls[]数组中取出的元素的func函数指针。下面是这个数组的定义,也是在v4l2-ioctl.c文件中:

我们再回顾一下struct v4l2_ioctl_info结构体:

可以看到v4l2_ioctls[]的元素都是由一个个IOCTL_INFO宏组成,这个IOCTL_INFO宏主要负责将,宏中的参数组合成struct v4l2_ioctl_info结构体,具体定义如下:

可以看到struct v4l2_ioctl_info结构体中的信息也是由v4l2-ioctl.c预先填充的好的,特别是启动的func函数指针,最终会指向如v4l_querycap,v4l_enum_fmt等。

相当于:

struct v4l2_ioctl_info { .ioctl = VIDIOC_QUERYCAP .flags = 0 .name = "VIDIOC_QUERYCAP", .func = v4l_querycap, .debug = v4l_print_querycap, };

而v4l_querycap,v4l_enum_fmt这些函数也是v4l2框架预先写好的,所以V4L2框架其实已经将所有的ioctl的cmd执行函数,也就是v4l2_ioctl_info里面的func,全部都封装好了,用数组统一管理起来,这里我们也能猜到,在v4l2框架封装好的这些函数里面,会调用我们自己写的ioctl函数,我们以v4l_querycap为例,看看他具体干了什么:

在v4l_querycap,会调用传递给这个函数的const struct v4l2_ioctl_ops *ops参数中的函数(准确说是函数指针指向的函数),这个参数好像似曾相识?没错,他就是在struct video_device结构体中的.ioctl_ops函数;

我们进入到这个函数集中,看看有没有这个被调用的函数:

显然第一个函数就是,这里我们同样能看到vb2开头的和v4l2开头的函数,这些前面说了是辅助函数。而airspy开头的就是这个例程编写的函数。那么这个airspy_querycap显然就是这个例程编写的函数。到此为止,我们的ioctl调用流程终于结束了,现在还剩下一个问题,那就是这个ops是由谁传递给v4l2_querycap的?

还记得前面的__video_do_ioctl吗?

正是在这个函数中,将设备的ioctl_ops传递给了核心层的v4l2_querycap;

好了,现在我们对整个摄像头驱动的ioctl调用流程有了一个整体的印象。我们用一副图来总结:

buffer相关ioctl

我们在写V4L2应用层程序的时候,最重要的步骤其实是与buffer相关的,也就是应用层:

ioctl(fd, VIDIOC_REQBUFS, &req); ioctl(fd, VIDIOC_QUERYBUF, &buf); ioctl(fd, VIDIOC_QBUF, &buf); ioctl(fd, VIDIOC_DQBUF, &buf);

应用层用这些cmd指令来申请buffer并且将buffer入队或者出队,但是这些buffer在底层的管理,应用层是不需要关心的。

这些buffer的管理显然是在驱动底层实现的。先介绍几个与buffer相关的结构体;

在struct video_device中,有一个很重要的成员叫struct vb2_queue *queue;这个queue就是用来管理buffer的,它的详细层级结构如下:

在vb2_queue中struct vb2_buffer *bufs[VB2_MAX_FRAME]就是用来存放我们申请的buffer的数组,当我们调用ioctl VIDIOC_REQBUFS向驱动申请N个buffer时,驱动程序分配n(n<=N)个vb2_buffer结构体,用数组进行管理。

而对于struct vb2_buffer结构体,他的内部又进一步包含struct vb2_plane planes[VB2_MAX_PLANES]结构体,这是因为一个相机可能有多个平面的数据(对于多平面相机,他只是相机的一种,我们这里不过多讨论),每个平面的数据又是通过结构体struct vb2_plane进行管理的,这个数组就是用来存储多个平面的信息的,如果相机只有一个平面,那么这个数组中就只包含一个平面的信息。平面的数量同样记录在这个结构体中的unsigned int num_planes中。

struct vb2_planevoid *mem_priv就是保存数据的地方,一般会指向vb2_vmalloc_buf由它来管理最终的buffer。

这里结构体的嵌套有点多,但其实并不复杂,从应用层的角度来说,struct vb2_buffer就对应着我们申请的buffer,只不过相机可能有多个平面(struct vb2_plane)的数据,每个平面的数据都需要单独进行管理,然后我们最终申请到的内存空间也需要有个结构体来管理(vb2_vmalloc_buf

现在我们申请到了buffer(struct vb2_buffer),我们把他放在struct video_device 中的 struct vb2_queue 成员中,下面就是对这些buffer的具体操作了,比如入队,出队以及硬件是怎么往里面填入数据的?

这里就要关注struct vb2_queue 中的几个操作函数了:

简单介绍一下这几个结构体的作用:const struct vb2_ops是与硬件相关的回调函数,我们编写驱动的工作就在这里,另外两个结构体中的操作函数基本上不需要我们关心,直接使用内核提供的辅助函数即可,或者在初始化的时候,内核也会帮我们填充这两个结构体。

vb2_mem_ops中的是与内存分配相关的函数,我们在申请buffer的时候,buffer的大小属性等都是由他管理。

vb2_buf_ops主要负责用户空间的struct v4l2_buffer和内核vb2_buffer间的通信

怎样编写驱动程序

ioctl调用流程可以说是比较复杂的一个流程了,上面我们将整个流程梳理了一遍,可以看到,v4l2框架帮我们做了很多事情

v4l2核心层会将应用层的 read write等指令下发给struct video_device中的fops操作函数集合,最终调用到这个集合中的函数指针指向的函数,而这个fops中函数指针指向的函数可以完全由v4l2框架提供,不需要我们编写。

其中最复杂的ioctl操作函数

  1. 应用层的ioctl操作函数首先会被v4l2核心层下发给,struct video_device中的fops操作函数.unlocked_ioctl
  2. unlocked_ioctl会进一步调用__video_do_ioctl函数;
  3. __video_do_ioctl又会根据ioctl指令找到他在v4l2的静态v4l2_ioctl_info数组中被管理的位置;
  4. 随后调用v4l2_ioctl_info中绑定给cmd指令的函数,
  5. 再这个函数中再调用struct video_device中的const struct v4l2_ioctl_ops *ioctl_ops 函数集中的函数

那么我们编写驱动的时候,目标也就非常明确了,重点是编写struct video_device中的const struct v4l2_ioctl_ops *ioctl_ops 中指向的函数

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/19 9:18:08

Java 业务测试全方案:测试方法 + 特殊场景 + 实战案例

Java 业务测试核心目标&#xff1a;验证业务逻辑正确性、边界安全性、异常容错性、数据一致性&#xff0c;覆盖正常流程 所有异常 / 边界场景&#xff0c;避免线上业务故障。我会按测试方法分类&#xff0c;直接给你可落地的测试点 真实业务案例&#xff0c;覆盖后端最常用的…

作者头像 李华
网站建设 2026/5/19 9:17:02

观察Taotoken用量看板如何帮助个人开发者优化模型调用策略

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 观察Taotoken用量看板如何帮助个人开发者优化模型调用策略 对于独立开发者和小型项目团队而言&#xff0c;在集成大模型能力时&…

作者头像 李华
网站建设 2026/5/19 9:17:02

温故知新:Java 线程创建方式与特点总结

目录 写在前面&#xff1a; 1、继承Thread类 特点: 类代码&#xff1a; 主程序代码: 2、实现Runnable接口 特点&#xff1a; 类代码&#xff1a; 主程序代码&#xff1a; 3、实现Callable接口 特点&#xff1a; FutureTask常用方法 类代码&#xff1a; 主程序代码…

作者头像 李华
网站建设 2026/5/19 9:12:30

5MB奇迹:WenQuanYi Micro Hei超轻量中文字体实战秘籍

5MB奇迹&#xff1a;WenQuanYi Micro Hei超轻量中文字体实战秘籍 【免费下载链接】fonts-wqy-microhei Debian package for WenQuanYi Micro Hei (mirror of https://anonscm.debian.org/git/pkg-fonts/fonts-wqy-microhei.git) 项目地址: https://gitcode.com/gh_mirrors/fo…

作者头像 李华
网站建设 2026/5/19 9:11:16

3分钟掌握Windows窗口置顶技巧:AlwaysOnTop让你的工作流效率翻倍

3分钟掌握Windows窗口置顶技巧&#xff1a;AlwaysOnTop让你的工作流效率翻倍 【免费下载链接】AlwaysOnTop Make a Windows application always run on top 项目地址: https://gitcode.com/gh_mirrors/al/AlwaysOnTop 你是否经常在多个窗口间频繁切换&#xff0c;导致工…

作者头像 李华
网站建设 2026/5/19 9:04:56

NCMDump:免费解锁网易云音乐NCM格式的完整指南

NCMDump&#xff1a;免费解锁网易云音乐NCM格式的完整指南 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 你是否曾经下载了网易云音乐的歌曲&#xff0c;却发现只能在特定客户端播放&#xff1f;NCMDump是一款开源工具&#xff0c;…

作者头像 李华