UVC简介
UVC(USB Video Class)
是一个用于通过USB接口传输视频的标准协议,广泛应用于各种摄像头设备(例如网络摄像头、内置摄像头、视频会议摄像头等)。UVC标准定义了USB摄像头的通信方式,使其在不同设备和操作系统中具有通用性和兼容性。
工作原理
UVC协议在USB设备中定义了标准化的控制请求和数据传输方式。主要包括两个部分:
- 控制请求:用来控制摄像头的各种参数,如分辨率、帧率、曝光、白平衡等。
- 视频流传输:通过USB的Bulk、Isochronous(等时)传输模式传输视频数据。
驱动栈
USB 摄像头驱动在 Linux 内核中不同层次的作用和交互方式
-
USB 物理相机设备:底层是实际的 USB 摄像头设备,通过 USB 物理连接与系统进行数据通信。
-
USB 主机控制器驱动程序:这是特定于硬件的驱动程序,负责与特定 USB 主控芯片通信。它实现了 USB 传输的底层功能,并管理 USB 总线,提供端口检测、设备连接管理等功能。
-
UVC 驱动程序:UVC(USB Video Class)驱动识别到 USB 摄像头后,将建立与 USB 控制器的连接,并开始通过 USB 协议传输视频数据。UVC 驱动向上提供接口,使得应用可以通过标准的 V4L2 接口来访问摄像头。
-
V4L2 接口:V4L2(Video for Linux 2)是 Linux 内核中的通用视频采集接口。UVC 驱动向 V4L2 层注册设备,V4L2 将设备抽象为标准字符设备,使用户空间的应用程序可以通过 /dev/videoX 的设备节点访问摄像头。应用程序可以使用 V4L2 提供的标准 API 获取和配置视频流,从而简化了对不同视频设备的管理。
-
用户空间应用程序:在用户空间,应用程序可以通过 V4L2 接口打开视频设备文件,调用 ioctl 来控制摄像头、捕获图像、获取视频数据流等。
LINUX&UVC驱动的初始化
udv的代码在drivers/media/udev/uvc_driver.c
中
struct uvc_driver uvc_driver = {
.driver = {
.name = "uvcvideo",
.probe = uvc_probe,
.disconnect = uvc_disconnect,
.suspend = uvc_suspend,
.resume = uvc_resume,
.reset_resume = uvc_reset_resume,
.id_table = uvc_ids,
.supports_autosuspend = 1,
},
};
static int __init uvc_init(void)
{
int ret;
uvc_debugfs_init();
ret = usb_register(&uvc_driver.driver);
if (ret < 0) {
uvc_debugfs_cleanup();
return ret;
}
printk(KERN_INFO DRIVER_DESC " (" DRIVER_VERSION ")\n");
return 0;
}
static void __exit uvc_cleanup(void)
{
usb_deregister(&uvc_driver.driver);
uvc_debugfs_cleanup();
}
module_init(uvc_init);
module_exit(uvc_cleanup);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");
MODULE_VERSION(DRIVER_VERSION);
其入口函数module_init指定为uvc_init,这就相当于应用于的main函数
内核初始化机制
内核提供了多个宏定义,用于定义不同的初始化函数,按照执行顺序,内核中的初始化函数类型大致如下:
pure_initcall
最早执行的初始化函数,用于非常底层、基础的内核模块。
通常是一些核心系统功能的初始化,如内核调度、内存管理等。
core_initcall
初始化最基础的内核组件,例如文件系统、进程管理等核心功能。
这些函数的执行保证了后续功能能够使用内核的核心功能。
postcore_initcall
在核心初始化完成之后运行,负责内核的核心模块和基础服务的初始化。
通常包括内存子系统、调度器等。
arch_initcall
用于体系结构特定的初始化,例如 x86、ARM、MIPS 等不同的处理器架构。
每种架构都有自己的初始化需求,该函数会针对当前处理器进行特定配置。
subsys_initcall
用于初始化子系统,如块设备、网络、输入设备等。
这一阶段确保系统的基本 I/O 和设备管理组件已经加载。
fs_initcall
专门用于文件系统的初始化。
加载文件系统模块,并确保根文件系统可以正常挂载。
device_initcall
用于初始化设备驱动,加载内核中与硬件直接交互的模块。
这是硬件驱动初始化的主要阶段,系统中的各种驱动模块大部分在此阶段初始化。
late_initcall
最后执行的初始化函数,确保其他模块都已经加载完成。
适合一些对依赖性较强的模块,或对加载顺序要求不高的模块。
内核是如何调用这些初始化函数的?
这些初始化函数在编译时被放置到不同的内核段中(如 .init 段),每个段代表不同的初始化阶段。内核在启动过程中,按照顺序依次调用这些段中的函数,实现有序初始化。调用过程大致如下:
1、内核定义了多个初始化段,分别存储不同阶段的初始化函数。
2、启动时,内核在各阶段扫描相应的段,并依次调用其中的函数指针。
3、每个阶段的初始化完成后,进入下一个阶段,直到所有初始化函数都执行完毕。
module_init
和 module_exit
对于动态加载的模块,Linux 内核还提供了 module_init
和 module_exit
两个宏:
module_init
:定义模块的入口初始化函数,当模块加载时被调用。
module_exit
:定义模块的退出函数,当模块卸载时调用,用于资源清理。
#define ___define_initcall(fn, id, __sec) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(#__sec ".init"))) = fn
这个宏 ___define_initcall
用于将一个函数标记为内核初始化函数,并指定其执行的顺序和初始化阶段。它定义了一个特殊的函数指针,指向指定的初始化函数,并放置在内核的一个特定的初始化段中。在 Linux 内核中,这种方式允许系统在启动时根据初始化阶段按顺序调用各个初始化函数。其中一个宏定义
__attribute__((__section__(#__sec ".init")))
:将 __initcall_fnid
放置在内核指定的初始化段(例如 core.init 或 postcore.init)中。#__sec
会把 __sec
作为字符串拼接,.init
表示该段用于初始化。
总结
入坑内核学习,小白一枚,有问题请指正,后面继续更uvc内核代码学习