linux驱动加载流程分析
创始人
2024-04-20 15:17:59
0

linux驱动加载流程分析

内核是如何加载驱动的,有些是编译到内核里面,有些事编译成ko,让系统自动加载。总的说来,在Linux下可以通过两种方式加载驱动程序:静态加载和动态加载。

静态加载就是把驱动程序直接编译进内核,系统启动后可以直接调用。动态加载利用了Linux的module特性,可以在系统启动后用insmod命令添加模块(.ko),在不需要的时候用rmmod命令卸载模块。

1. 驱动加载

1.1 静态加载过程

将模块的程序编译到Linux内核中,也就是在编译内核时选择Y的模块,静态由do_initcall函数加载。先来看看initcall在哪里:

kernel 3.10.108start_kernel(void) // init/main.c 内核启动的入口, 负责初始化调度,中断和内存, 最后启动1号进程和2号进程-> rest_init(void)-> kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);|Vkernel_init() // linux 1号进程, top一下系统就能找到1号进程了-> kernel_init_freeable()-> do_basic_setup()-> do_initcalls()

do_initcalls 中会定义的各个模块加载顺序,加载顺序分为16个等级,加载时按照16个等级依次加载内核驱动。关于每个等级的定义参考 include/linux/init.h

举个例子 device使用的是 arch_initcall,而driver使用的是 module_init
因为 arch_initcall的优先级大于module_init,所以设备驱动的device先于driver在总线上添加。

1.2 动态加载过程

将模块的程序编译成ko,也就是在编译内核时选择M的模块,通过insmod、modprobe 或者udev动态加载到内核中。
insmod 绝对路径/xx.ko,而modprobe xx即可,不用加.ko或.o后缀,也不用加路径。
最重要的一点是:modprobe同时会加载当前模块所依赖的其它模块。

来看看insmod都做了什么:

https://git.kernel.org/pub/scm/utils/kernel/kmod/kmod.git
kmod v30do_insmod() // tools/insmod.c-> kmod_module_insert_module()-> finit_module()-> syscall(__NR_finit_module, fd, uargs, flags)  // 进入内核系统调用-> sys_init_module() -> SYSCALL_DEFINE3(init_module, void __user *, umod,	unsigned long, len, const char __user *, uargs)-> copy_module_from_fd(fd, &info)-> load_module(&info, uargs, flags)-> mod = layout_and_allocate(info, flags)-> do_init_module(mod)-> do_one_initcall(mod->init)-> fn()       // 调用 mod->init() 函数

2. 驱动匹配

2.1 硬件固件设计

PCI标准规定每个设备的配置寄存器组最多可以有256字节的连续空间,其中开头的64字节的用途和格式是标准的,成为配置寄存器组的"头部",这样的头部又有两种,"0型"头部用于一般的PCI设备,“1型"头部用于PCI桥,无论是"0型"还是"1型”,其开头的16个字节的用途和格式是共同的。其中Device ID 和 Vendor ID 位于前4个字节。

所有PCI硬件上必须设计一系列寄存器,PCI通过这些寄存器获取硬件信息,称为PCI配置空间,PCI配置空间格式如下:

DW |    Byte3   |    Byte2   |     Byte1  |   Byte0    | Addr 
---+---------------------------------------------------+------ 
0  |         Device ID       |        Vendor ID        | 00 
---+---------------------------------------------------+------
1  |         Status          |         Command         | 04 
---+---------------------------------------------------+------ 
2  |               Class Code             |Revision ID | 08 
---+---------------------------------------------------+------ 
3  |    BIST    |Header Type |Latency Timer |Cache Line| OC
---+---------------------------------------------------+------ 
4  |                  Base Address 0                   | 10 
---+---------------------------------------------------+------
5  |                  Base Address 1                   | 14 
---+---------------------------------------------------+------
6  |                  Base Address 2                   | 18 
---+---------------------------------------------------+------ 
7  |                  Base Address 3                   | 1C 
---+---------------------------------------------------+------
8  |                  Base Address 4                   | 20 
---+---------------------------------------------------+------
9  |                  Base Address 5                   | 24 
---+---------------------------------------------------+------ 
10 |               CardBus CIS pointer                 | 28 
---+---------------------------------------------------+------
11 | Subsystem Device ID     |  Subsystem Vendor ID    | 2C
---+---------------------------------------------------+------
12 |           Expansion ROM Base Address              | 30 
---+---------------------------------------------------+------
13 |           Reserved(Capability List)               | 34 
---+---------------------------------------------------+------
14 |                      Reserved                     | 38 
---+---------------------------------------------------+------ 
15 | Max_Lat    | Min_Gnt    |   IRQ Pin  | IRQ Line   | 3C 
--------------------------------------------------------------

执行命令lspci -nn,会得到类似如下输出:

Ethernet controller [0200]: Intel Corporation 82545EM Gigabit Ethernet Controller (Copper) [8086:100f] (rev 01)

其中的8086是vendor ID,100f是Device ID。所以咱们常用的LSCPI实质上就是去硬件上读取硬件的寄存器值,并且显示出来。

2.2 软件驱动设计

每一个硬件设备都有Verdon ID, Device ID, SubVendor ID等信息。所以每一个设备驱动程序,必须说明自己能够为哪些Verdon ID, DevieceID, SubVendor ID的设备提供服务。以PCI设备为例,它是通过一个pci_device_id的数据结构来实现这个功能的。

例如:RTL8139驱动的pci_device_id定义为:

static DEFINE_PCI_DEVICE_TABLE(rtl8139_pci_tbl) = {{0x10ec, 0x8139, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },{0x10ec, 0x8138, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },{0x1113, 0x1211, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },{0x1500, 0x1360, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },{0x4033, 0x1360, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },{0x1186, 0x1300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },{0x1186, 0x1340, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },{0x13d1, 0xab06, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },{0x1259, 0xa117, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },{0x1259, 0xa11e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },{0x14ea, 0xab06, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },{0x14ea, 0xab07, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },{0x11db, 0x1234, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },{0x1432, 0x9130, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },{0x02ac, 0x1012, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },{0x018a, 0x0106, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },{0x126c, 0x1211, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },{0x1743, 0x8139, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },{0x021b, 0x8139, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },#ifdef CONFIG_SH_SECUREEDGE5410/* Bogus 8139 silicon reports 8129 without external PROM :-( */{0x10ec, 0x8129, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
#endif
#ifdef CONFIG_8139TOO_8129{0x10ec, 0x8129, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8129 },
#endif/* some crazy cards report invalid vendor ids like* 0x0001 here.  The other ids are valid and constant,* so we simply don't match on the main vendor id.*/{PCI_ANY_ID, 0x8139, 0x10ec, 0x8139, 0, 0, RTL8139 },{PCI_ANY_ID, 0x8139, 0x1186, 0x1300, 0, 0, RTL8139 },{PCI_ANY_ID, 0x8139, 0x13d1, 0xab06, 0, 0, RTL8139 },{0,}
};

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)。

下面是内核设备与驱动匹配的代码:

static inline const struct pci_device_id *
pci_match_one_device(const struct pci_device_id *id, const struct pci_dev *dev)
{if ((id->vendor == PCI_ANY_ID || id->vendor == dev->vendor) &&(id->device == PCI_ANY_ID || id->device == dev->device) &&(id->subvendor == PCI_ANY_ID || id->subvendor == dev->subsystem_vendor) &&(id->subdevice == PCI_ANY_ID || id->subdevice == dev->subsystem_device) &&!((id->class ^ dev->class) & id->class_mask))return id;return NULL;
}

3. 驱动自动加载过程

自动加载属于动态加载,加载的是ko文件,那么系统启动之后ko在什么时候,通过什么程序自动加载上的?

3.1 构建自动加载驱动

编译内核时,通过make modules install INSTALL_MOD_PATH=XXX,会将所有选项为M的模块编译为KO, 并且发布到/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。

3.2 PCI扫描自动加载驱动

在内核启动过程中,总线驱动程序会会总线协议进行总线枚举(总线驱动程序总是集成在内核之中,不能够按模块方式加载,可以通过make menuconfig进入Busoptions,这里面的各种总线,只能够选择Y或N,而不能选择M),并且为每一个设备建立一个设备对象。

每一个总线对象有一个kset对象,每一个设备对象嵌入了一个kobject对象,kobject连接在kset对象上,这样总线和总线之间,总线和设备设备之间就组织成一颗树状结构。

当总线驱动程序为扫描到的设备建立设备对象时,会初始化kobject对象,并把它连接到设备树中,同时会调用kobject_uevent()把这个(添加新设备的)事件,以及相关信息(包括设备的VendorID,DeviceID等信息)通过netlink发送到用户态中。

来看看这个过程:
subsys_initcall (前面讲到过驱动的16级加载机制,subsys_initcall处于16级中的第8级)

subsys_initcall(pci_subsys_init)  // arch\x86\pci\legacy.c -> pci_subsys_init(void)-> pci_legacy_init()-> pcibios_scan_root()-> pci_scan_bus_on_node()-> pci_scan_root_bus()-> pci_scan_child_bus()-> pci_scan_slot()-> pci_scan_single_device()-> pci_device_add()-> device_add()-> kobject_uevent(&dev->kobj, KOBJ_ADD)  // 发消息到udev,让udev去/lib/modules/下面加载驱动-> bus_probe_device(dev)                 // 匹配内核中已有驱动

device_add有两个流程:

  • a. bus_probe_device 匹配内核中已有驱动
  • b. kobject_uevent 通过netlink发送到用户态中在用户态的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。
         +-------------------------+|                         || Hotplug, Udev, etc...   | --> 执行相应动作|                         |+-------------------------+^              ^
User         |              |
--.--.--.--.-|.--.--.--.--.-|.--.--.--.--.--.--.--.--
Kernel       |              ||              |+----------+   +----------+| Netlink  |   |   Kmod   |+----------+   +----------+   +-----------+^             ^         |  Device   ||             |         +-----------++-------------+               | 发生事件|                      v+---------|------------------------------+|         |                              ||    +--------+                          ||    | Uevent |         Kobject          ||    +--------+                          |+----------------------------------------+

uevent的机制是比较简单的,设备模型中任何设备有事件需要上报时,会触发uevent提供的接口。uevent模块准备好上报事件的格式后,可以通过两个途径把事件上报到用户空间:一种是通过kmod模块,直接调用用户空间的可执行文件;另一种是通过netlink通信机制,将事件从内核空间传递给用户空间。

相关内容

热门资讯

鸿蒙怎样还原安卓系统,系统切换... 你有没有想过,鸿蒙系统竟然能还原安卓系统?这听起来是不是有点像魔法一样神奇?没错,今天就要带你一探究...
电脑安卓转苹果系统,系统迁移攻... 你有没有想过,有一天你的安卓手机突然变成了苹果的忠实粉丝,想要跳槽到iOS的阵营呢?这可不是什么天方...
安卓xp系统下载地址,轻松获取... 你有没有想过,手机系统也能穿越时空?没错,今天我要给你揭秘的就是这样一个神奇的存在——安卓XP系统。...
安卓系统怎么清理相册,安卓系统... 手机里的相册是不是越来越臃肿了?每次打开都感觉像是在翻山越岭,找一张照片都要费老鼻子劲。别急,今天就...
安卓系统安装ios转移,轻松实... 你有没有想过,手机系统之间的转换竟然也能如此神奇?没错,今天就要来聊聊安卓系统安装iOS转移这个话题...
安卓系统与ios系统的优势,系... 你有没有想过,为什么你的手机里装的是安卓系统而不是苹果的iOS系统呢?或者反过来,为什么你的朋友用的...
安卓系统游戏如何升级,轻松实现... 亲爱的安卓玩家们,你是否也和我一样,对安卓系统游戏升级这件事充满了好奇和期待呢?每次游戏更新,都仿佛...
安卓系统蛋仔派对,安卓系统下的... 你有没有发现,最近你的手机里多了一个超级好玩的游戏?没错,就是安卓系统上的“蛋仔派对”!这款游戏可是...
坚果3安卓原生系统,深度体验原... 你有没有听说过坚果3这款手机?它可是最近在数码圈里火得一塌糊涂呢!今天,我就要来给你详细介绍一下这款...
安卓子系统点不开,排查与解决指... 最近是不是你也遇到了安卓子系统点不开的烦恼?这可真是让人头疼啊!别急,今天就来给你详细解析一下这个问...
安卓系统经常误删文件,如何有效... 你有没有遇到过这种情况?手机里的文件突然不见了,找来找去,怎么也找不到。别急,这可能是安卓系统的小调...
安卓51系统如何破解,轻松解锁... 安卓51系统如何破解——探索未知的技术边界在数字化时代,手机已经成为我们生活中不可或缺的一部分。而安...
安卓系统怎么换回主题,安卓系统... 亲爱的手机控们,你是不是也和我一样,对安卓系统的主题换换换乐此不疲呢?不过,有时候换着换着,突然发现...
黑莓安卓系统 太垃圾,令人失望... 你有没有用过黑莓的安卓系统?别告诉我你没有,因为现在这个系统真的是太垃圾了!是的,你没听错,就是那个...
修改安卓系统权限代码,安卓系统... 你有没有想过,你的安卓手机里那些神秘的系统权限代码?它们就像隐藏在手机里的秘密通道,有时候让你觉得既...
虚拟大师安卓系统教程,教程详解... 你有没有想过,手机里的世界可以变得更加神奇?今天,就让我带你一起探索虚拟大师安卓系统的奥秘吧!想象你...
基于安卓系统个人博客,轻松构建... 你有没有想过,在这个信息爆炸的时代,拥有一片属于自己的网络小天地是多么酷的事情啊!想象每天都能在这里...
安卓怎么传到苹果系统,从安卓到... 你是不是也有过这样的烦恼:手机里存了好多好用的安卓应用,可是一换到苹果系统,就发现这些宝贝们都不见了...
安卓改系统字体app,安卓系统... 你有没有想过,手机上的字体也能变得个性十足?没错,就是那个安卓改系统字体app,它可是让手机界面焕然...
安卓系统重启密码错误,破解与预... 手机突然重启了,屏幕上竟然出现了密码输入的界面!这可怎么办?别急,让我来帮你一步步解决这个安卓系统重...