3.虚拟驱动vivi调用过程彻底分析

一、xawtv所涉及的vivi驱动的系统调用

    在Ubuntu系统中,当我们接上usb摄像头设备时,系统会自动给我们安装对应的usb设备驱动程序。
我们现在要使用自己编译的vivi驱动,该怎么办呢?
1.先安装系统自带的vivi驱动和它所依赖的所有驱动:sudo modprobe vivi ;
2.卸载原有的vivi驱动 : sudo rmmod vivi ;
3.装载自己的驱动 :sudo insmod ./vivi.ko ;
然后 ls /dev/video* ,可以看到有一个video设备节点 /dev/video0 ,即对应的是vivi虚拟出来的视频设备。
我们可以直接阅读xawtv源码,从main函数开始一路分析它调用vivi驱动的过程,但是这个过程会非常漫长,因为它除了调用vivi驱动之外
,还会做许多其他的准备工作。我们可以通过strace 这个命令来跟踪调用过程。

使用方法 :执行 strace -o xawtv.txt xawtv ,生成了调用过程xawtv.txt。
搜索 /dev/video0,得到如下:
open("/dev/video0", O_RDWR|O_LARGEFILE) = 4
ioctl(4, VIDIOC_QUERYCAP or VT_OPENQRY, 0x95b8998) = -1 EINVAL (Invalid argument)
close(4)                                = 0
open("/dev/video0", O_RDWR|O_LARGEFILE) = 4
.....
发现打开了两次,open成功之后得到file_fd =4,后面有一大堆ioctl,把所有的ioctl列举出来,即可得到ioctl的过程:
open("/dev/video0", O_RDWR|O_LARGEFILE) = 4
ioctl(4, VIDIOC_QUERYCAP or VT_OPENQRY, 0xbff6c704) = 0
ioctl(4, VIDIOC_G_FMT or VT_SENDSIG, 0xbff6c638) = 0
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbff6c5ac) = 0
ioctl(4, 0xc02c564a, 0xbff6c518)        = -1 EINVAL (Invalid argument)
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbff6c5ac) = 0
ioctl(4, 0xc02c564a, 0xbff6c518)        = -1 EINVAL (Invalid argument)
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbff6c5ac) = 0
ioctl(4, 0xc02c564a, 0xbff6c518)        = -1 EINVAL (Invalid argument)
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbff6c5ac) = 0
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbff6c5ac) = 0
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbff6c5ac) = 0
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbff6c5ac) = -1 EINVAL (Invalid argument)
ioctl(4, VIDIOC_QUERYCAP or VT_OPENQRY, 0xbff6c544) = 0
ioctl(4, VIDIOC_G_INPUT, 0xbff6c3ec)    = 0
ioctl(4, VIDIOC_ENUMINPUT, 0xbff6c3ec)  = 0


。。。。

xawtv涉及的vivi驱动的系统调用:
1.open
2.ioctl(4, VIDIOC_QUERYCAP   列举性能
3.ioctl(4, VIDIOC_G_FMT
4.ioctl(4, VIDIOC_ENUM_FMT   枚举出所支持的格式,可以看出这里支持多种格式
5.ioctl(4, VIDIOC_QUERYCAP
6.ioctl(4, VIDIOC_G_INPUT    获得当前的输入源
7.ioctl(4, VIDIOC_ENUMINPUT  枚举输入源,可能有多个输入源
8.ioctl(4, VIDIOC_QUERYCTRL  查询属性,比如亮度、对比度等等
9.ioctl(4, VIDIOC_QUERYCAP   
10.ioctl(4, VIDIOC_ENUMINPUT
11.ioctl(4, VIDIOC_ENUMSTD   列举制式
12.ioctl(4, VIDIOC_ENUM_FMT  列举格式
13.ioctl(4, VIDIOC_G_PARM    获得参数
14.ioctl(4, MATROXFB_TVOQUERYCTRL or VIDIOC_QUERYCTRL  查询属性
15.ioctl(4, VIDIOC_G_STD     获得当前使用的制式
16.ioctl(4, VIDIOC_G_INPUT   当前输入
17.ioctl(4, MATROXFB_G_TVOCTRL or VIDIOC_G_CTRL        获得属性
18.ioctl(4, VIDIOC_TRY_FMT   尝试某种格式
19.ioctl(4, VIDIOC_S_FMT     尝试成功就使用某种格式
20.ioctl(4, VIDIOC_REQBUFS   请求系统分配缓冲区域
for(i=0;i<count;i++)
{
21.ioctl(4, VIDIOC_QUERYBUF  查询分配的缓冲区
          mmap                      映射地址
22.ioctl(4, VIDIOC_QBUF      放入队列
}
23.ioctl(4, VIDIOC_STREAMON  启动摄像头
24.ioctl(4, MATROXFB_S_TVOCTRL or VIDIOC_S_CTRL 设置属性 亮度等等
25.ioctl(4, VIDIOC_S_INPUT   设置输入源
26.ioctl(4, VIDIOC_S_STD     设置制式
for()   //大循环处理数据
{
27.select(5, [4], NULL, NULL, {5, 0}) 采用阻塞方式查询有没有数据,在驱动程序会用到poll
28.ioctl(4, VIDIOC_DQBUF              把缓冲区从队列里取出
29.ioctl(4, VIDIOC_QBUF               放入队列
}
........

从以上分析可以看出,xawtv设计的vivi驱动的系统调用实在是太多了,可以删掉一些不必要的系统调用,找到最精简的版本。


二、vivi驱动必不可少的几个ioctl
在linux 2.6.31.14 版本的vivi.c 中,当应用程序调用v4l2框架里的ioctl时,最终调用到vivi.c里的vivi_ioctl_ops,它包含了以下的ioctl:
static const struct v4l2_ioctl_ops vivi_ioctl_ops = {
.vidioc_querycap      = vidioc_querycap,
.vidioc_enum_input    = vidioc_enum_input,
.vidioc_g_input       = vidioc_g_input,
.vidioc_s_input       = vidioc_s_input,
.vidioc_s_std         = vidioc_s_std,
.vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap     = vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,
.vidioc_reqbufs       = vidioc_reqbufs,
.vidioc_querybuf      = vidioc_querybuf,
.vidioc_qbuf          = vidioc_qbuf,
.vidioc_dqbuf         = vidioc_dqbuf,
.vidioc_queryctrl     = vidioc_queryctrl,
.vidioc_g_ctrl        = vidioc_g_ctrl,
.vidioc_s_ctrl        = vidioc_s_ctrl,
.vidioc_streamon      = vidioc_streamon,
.vidioc_streamoff     = vidioc_streamoff,
#ifdef CONFIG_VIDEO_V4L1_COMPAT
.vidiocgmbuf          = vidiocgmbuf,
#endif
};

它们的主要作用如下:
1.vidioc_querycap对应应用程序的VIDIOC_QUERYCAP,它的作用是告诉应用程序它是一个摄像头设备,是必不可少的。

2.vidioc_enum_input对应应用程序的VIDIOC_ENUMINPUT,列举输入源,及即使只有输入源也不影响使用,不是必须的

3.vidioc_g_input   得到当前使用的输入源,不是必需的
   vidioc_s_input   设置使用哪个输入源,不是必须的

4.vidioc_s_std,    用于设置tv制式,不是必须的
  tvnorms              = V4L2_STD_525_60,这些对应模拟信号制式,可以去掉
  current_norm         = V4L2_STD_NTSC_M,

5.vidioc_g_fmt_vid_cap,
  vidioc_try_fmt_vid_cap,
  vidioc_s_fmt_vid_cap,
  用于获得、尝试、设置摄像头的数据格式,很重要,不可省略

6.vidioc_reqbufs
  vidioc_querybuf
  vidioc_qbuf 
  vidioc_dqbuf
  用于申请、查询、入队列、出队列,不可省略

7.vidioc_queryctrl,
  vidioc_g_ctrl,
  vidioc_s_ctrl,
  对应界面查询、获得、设置亮度、对比度等等信息,可以省略

8.vidioc_streamon,
  vidioc_streamoff,
  启动、停止摄像头,不可省略

9.vidiocgmbuf, 兼容v4l1驱动框架,可以省略

总结:v4l2_ioctl_ops 中对于vivi驱动必不可少的ioctl有以下11个:
/*告诉应用程序它是一个摄像头设备,是必不可少的*/
.vidioc_querycap      = vidioc_querycap,
/*用于列举、获得、尝试、设置摄像头的数据格式,很重要,不可省略*/
.vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap     = vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,
/*申请、查询、入队列、出队列,不可省略*/
.vidioc_reqbufs       = vidioc_reqbufs,
.vidioc_querybuf      = vidioc_querybuf,
.vidioc_qbuf          = vidioc_qbuf,
.vidioc_dqbuf         = vidioc_dqbuf,
/*启动、停止摄像头,不可省略*/
.vidioc_streamon      = vidioc_streamon,
.vidioc_streamoff     = vidioc_streamoff,

三、深入分析数据的获取过程

在驱动程序vivi.c里缓冲区操作主要涉及以下几个ioctl:
1.请求分配缓冲区 APP:ioctl(4, VIDIOC_REQBUFS  ------> Drv : vidioc_reqbufs
  vidioc_reqbufs这个函数最终会调用到videobuf-core.c 里的vidioc_reqbufs函数。它含有两个参数(队列,v4l2_requestbuffers)
  这个队列在open函数里用videobuf_queue_vmalloc_init初始化 。注意:这个函数只是分配缓冲区的头部信息,并没有分配实际的缓存,只有用到了缓存才分配。

2.查询缓冲区     APP:ioctl(4, VIDIOC_QUERYBUF ------> Drv :vidioc_querybuf
  vidioc_querybuf 这个函数最终会调用到videobuf-core.c 里的videobuf_status函数。它主要获得的是所分配缓冲区的状态信息,如数据格式和大小,每一行的数据长度

3.映射           mmap
当应用程序调用mmap 函数时,最终会调用到vivi.c的fops里的vivi_mmap,它调用的是videobuf-core.c 里的videobuf_mmap_mapper函数
在这里才分配缓冲区,参数里含有大小。它调用videobuf-vmalloc.c里的__videobuf_mmap_mapper分配,在vmalloc_user(pages)再给缓冲区分配空间。

4.把缓冲区放入队列 APP:ioctl(4, VIDIOC_QBUF     ------> Drv :vidioc_qbuf
它调用到vivi.c 里的vidioc_qbuf,最终调用到videobuf-core.c里的videobuf_qbuf,它把缓冲区放入队列的尾部list_add_tail(&buf->stream, &q->stream);调驱动程序提供的入队列函数q->ops->buf_queue(q, buf)做一些初始化工作;

5.启动摄像头             APP:ioctl(4, VIDIOC_STREAMON ------> Drv :vidioc_streamon
它最终调用到videobuf-core.c里的videobuf_streamon函数,先做一些启动处理,然后启动摄像头

6.用select函数查询是否有数据   APP:select(5, [4], NULL, NULL, {5, 0}) -------> Drv : poll
  它会调用到驱动程序提供的vivi_poll函数,它最终调用到videobuf-core.c里的videobuf_poll_stream函数,首先从队列的头部取出一个buf: buf = list_entry(q->stream.next,struct videobuf_buffer, stream);如果缓冲区里没有数据的话,就执行poll_wait(file, &buf->done, wait)在buf->done里休眠。在vivi.c里面有它的唤醒函数  wake_up(&buf->vb.done)。在vivi虚拟驱动里采用一个内核线程 vivi_thread ,使用vivi_sleep函数休眠,每隔一段时间使用vivi_thread_tick里的vivi_fillbuff来产生数据,并唤醒进程wake_up(&buf->vb.done)。

7.把缓冲区从队列里取出   APP:ioctl(4, VIDIOC_DQBUF    ------> Drv :vidioc_dqbuf
它最终调用到videobuf-core.c里的videobuf_dqbuf函数,获得数据已经就绪的缓冲区。在这个函数里首先调用retval = stream_next_buffer(q, &buf, nonblocking) 取出这个缓冲区,然后调用list_del(&buf->stream)把这个缓冲区从队列里删除,最后调用videobuf_status(q, b, buf, q->type)把这个缓冲区的状态返回给应用程序。

8.应用程序根据缓冲区的状态得到是哪个缓冲区有数据,就去相应的mmap函数读取相应的地址。

总结:从以上分析过程可以看出,应用程序调用某个ioctl,驱动程序必须提供相应的ioctl,它们实质上是调用系统提供的函数(例如videobuf-core.c和videobuf-vmalloc.c等)来实现相应的ioctl。这里的重点是,用select函数查询是否有数据,select函数在休眠等待有数据,在某段时间里没有数据会超时返回,因此vivi驱动里必定也要提供虚拟产生数据的函数,唤醒进程的函数。

四、怎么写自己的摄像头驱动程序?
1.分配一个video_device结构体
  video_device_alloc
2.设置video_device里的
  .fops 
  .ioctl_ops (包含上面11个重要的ioctl)
  如果要用内核的buf函数,就构造这个结构体videobuf_queue_ops
3.注册
  video_register_device



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值