概述
在咱们工作中,编译驱动,加载驱动时最常见的事。但是内核是如何加载驱动的,有些事编译到内核里面,有些事编译成ko,有些还能放到root下,让系统自动加载。
总的说来,在Linux下可以通过两种方式加载驱动程序:静态加载和动态加载。
静态加载就是把驱动程序直接编译进内核,系统启动后可以直接调用。动态加载利用了Linux的module特性,可以在系统启动后用insmod命令添加模块(.ko),在不需要的时候用rmmod命令卸载模块。
驱动加载
静态加载过程
将模块的程序编译到Linux内核中,也就是咱们在编译内核时选择Y的模块,静态由do_initcall函数加载。先来看看initcall在哪里:
核心进程(/init/main.c)
start_kernel()//内核启动的入口,负责初始化调度,中断,内存,最后启动1号进程 和2号进程
rest_init()
kernel_init()//这是谁? linux 1号进程,top一下系统就能找到1号进程了。
do_basic_setup()
do_initcalls()
do_initcalls中会定义的各个模块加载顺序,加载顺序分为16个等级,加载时按照16个等级依次加载内核驱动。关于每个等级的定义参考/include/linux/init.h。举个例子,在2.6.24的内核 中:gianfar_device使用的是arch_initcall,而gianfar_driver使用的是module_init,因为 arch_initcall的优先级大于module_init,所以gianfar设备驱动的device先于driver在总线上添加。init.h 截取如下:
#define pure_initcall(fn) __define_initcall(fn, 0)
#define core_initcall(fn) __define_initcall(fn, 1)
#define core_initcall_sync(fn) __define_initcall(fn, 1s)
#define postcore_initcall(fn) __define_initcall(fn, 2)
#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
#define arch_initcall(fn) __define_initcall(fn, 3)
#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
#define subsys_initcall(fn) __define_initcall(fn, 4)
#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
#define fs_initcall(fn) __define_initcall(fn, 5)
#define fs_initcall_sync(fn) __define_initcall(fn, 5s)
#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
#define device_initcall(fn) __define_initcall(fn, 6)
#define device_initcall_sync(fn) __define_initcall(fn, 6s)
#define late_initcall(fn) __define_initcall(fn, 7)
#define late_initcall_sync(fn) __define_initcall(fn, 7s)
动态加载过程
将模块的程序编译成KO,也就是咱们在编译内核时选择M的模块,通过insmod
、modprobe 或者udev动态加载到内核中。 insmod加绝对路径/××.ko,而modprobe ××即可,不用加.ko或.o后缀,也不用加路径;最重要的一点是:modprobe同时会加载当前模块所依赖的其它模块。
来看看insmod都做了什么:
Insmod
insmod_main
bb_init_module
init_module
sys_init_module(进入系统调用)
sys_init_module()系统调用会调用module_init指定的函数进行模块的初始化,至此ko加载完成。
驱动匹配
硬件固件设计
以PCI为例,所有PCI硬件上必须设计一系列寄存器,PCI通过这些寄存器获取硬件信息,称为PCI配置空间,PCI配置空间格式如下:
PCI标准规定每个设备的配置寄存器组最多可以有256字节的连续空间,其中开头的64字节的用途和格式是标准的,成为配置寄存器组的“头部”,这样的头部又有两种,“0型”头部用于一般的PCI设备,“1型”头部用于PCI桥,无论是“0型”还是“1型”,其开头的16个字节的用途和格式是共同的。其中Device ID 和 Vendor ID 位于前4个字节。
在系统中运行LSPCI -NN,会得到类似如下输出:
Ethernet controller [0200]: Intel Corporation 82545EM Gigabit Ethernet Controller (Copper) [8086:100f] (rev 01)
其中的8086是vendor ID,100f是Device ID。所以咱们常用的LSCPI实质上就是去硬件上读取硬件的寄存器值,并且显示出来。
软件驱动设计
每一个硬件设备都有Verdon ID, Device ID, SubVendor ID等信息。所以每一个设备驱动程序,必须说明自己能够为哪些Verdon ID, DevieceID, SubVendor ID的设备提供服务。以PCI设备为例,它是通过一个pci_device_id的数据结构来实现这个功能的。例如:RTL8139驱动的pci_device_id定义为:
Verdon ID可以理解为厂商ID,8086 代表Intel,1249代表三星等,Device ID可以理解为产品ID,每款产品的ID不同。上面的信息说明,Verdon ID为0x10EC, Device ID为0x8139, 0x8138的PCI设备(SubVendor ID和SubDeviceID为PCI_ANY_ID,表示不限制。),都可以使用这个驱动程序(8139too)。
下面是内核设备与驱动匹配的代码:
自动加载过程
自动加载属于动态加载,加载的是ko文件,许多同学也没搞清楚,系统启动之后ko在什么时候,通过什么程序自动加载上的。
构建自动加载环境
编译内核时,通过make modules_install INSTALL_MOD_PATH=XXX,会将所有选项为M的模块编译为KO, 并且发布到xxx/lib/modules/uname-r/下面。在模块安装的时候,depmod会根据模块中的rtl8139_pci_tbl的信息,生成下面的信息,保存到/lib/modules/uname-r/modules.alias文件中,其内容如下:
alias pci:v000010ECd00008138sv*sd*bc*sc*i* 8139too alias pci:v000010ECd00008139sv*sd*bc*sc*i* 8139too ......
v后面的000010EC说明其Vendor ID为10EC,d后面的00008138说明Device ID为8139,而sv,和sd为SubVendor ID和SubDevice ID,后面的星号表示任意匹配。
另外在/lib/modules/uname-r/modules.dep文件中还保存这模块之间的依赖关系,其内容如下:
8139too.ko:mii.ko
modules.dep由depmod工具生成,在使用 modprobe xxx加载驱动时, modprobe需要借助modules.dep文件来分析模块之间的依赖关系,先加载依赖的ko,再加载真正需要加载的ko。
PCI扫描自动加载驱动
在内核启动过程中,总线驱动程序会会总线协议进行总线枚举(总线驱动程序总是集成在内核之中,不能够按模块方式加载,你可以通过make menuconfig进入Busoptions,这里面的各种总线,你只能够选择Y或N,而不能选择M.),并且为每一个设备建立一个设备对象。每一个总线对象有一个kset对象,每一个设备对象嵌入了一个kobject对象,kobject连接在kset对象上,这样总线和总线之间,总线和设备设备之间就组织成一颗树状结构。当总线驱动程序为扫描到的设备建立设备对象时,会初始化kobject对象,并把它连接到设备树中,同时会调用kobject_uevent()把这个(添加新设备的)事件,以及相关信息(包括设备的VendorID,DeviceID等信息)通过netlink发送到用户态中。
来看看这个过程:
subsys_initcall (前面讲到过驱动的16级加载机制,subsys_initcall处于16级中的第8级,arch\x86\pci\legacy.c 参照内核4.19)
pci_legacy_init
pcibios_scan_root
pci_scan_root_bus
pci_scan_slot
pci_scan_single_device
pci_device_add
device_add
device_add有两个流程:
- bus_probe_device 匹配内核中已有驱动
- kobject_uevent 发消息到udev,让udev去/lib/modules/下面加载驱动
在用户态的udevd检测到这个事件,就可以根据这些信息,打开/lib/modules/uname-r/modules.alias文件,根据
alias pci:v000010ECd00008138sv*sd*bc*sc*i* 8139too
得知这个新扫描到的设备驱动模块为8139too。于是modprobe就知道要加载8139too这个模块了,同时modprobe根据 modules.dep文件发现,8139too依赖于mii.ko,如果mii.ko没有加载,modprobe就先加载mii.ko,接着再加载 8139too.ko。