关键词:
linux驱动加载流程分析
内核是如何加载驱动的,有些是编译到内核里面,有些事编译成ko,让系统自动加载。总的说来,在Linux下可以通过两种方式加载驱动程序:静态加载和动态加载。
静态加载就是把驱动程序直接编译进内核,系统启动后可以直接调用。动态加载利用了Linux的module特性,可以在系统启动后用insmod命令添加模块(.ko),在不需要的时候用rmmod命令卸载模块。
1. 驱动加载
1.1 静态加载过程
将模块的程序编译到Linux内核中,也就是在编译内核时选择Y的模块,静态由do_initcall函数加载。先来看看initcall在哪里:
kernel 3.10.108
start_kernel(void) // init/main.c 内核启动的入口, 负责初始化调度,中断和内存, 最后启动1号进程和2号进程
-> rest_init(void)
-> kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
|
V
kernel_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 v30
do_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通信机制,将事件从内核空间传递给用户空间。
linux驱动加载流程分析(代码片段)
linux驱动加载流程分析内核是如何加载驱动的,有些是编译到内核里面,有些事编译成ko,让系统自动加载。总的说来,在Linux下可以通过两种方式加载驱动程序:静态加载和动态加载。静态加载就是把驱动程... 查看详情
linuxalsa驱动之三:pcm创建流程源码分析(基于linux5.18)(代码片段)
1、基本概念及逻辑关系 如上图,通过上一节声卡的学习我们已经知道PCM是声卡的一个子设备,或者表示一个PCM实例。 每个声卡最多可以包含4个pcm的实例,每个pcm实例对应一个pcm设备文件。pcm实例... 查看详情
camera[1]驱动v4l2分析(代码片段)
//获取内核版本godv@godv-OptiPlex-7070:~$uname-r4.15.0-142-generic 这里是一个linux内核的查询网站https://lxr.missinglinkelectronics.com/linux/insmod xxx.ko //驱动加载命令modprobe xxx //依赖加载 modprobe-r xxx //卸载驱 查看详情
multidex加载流程分析(代码片段)
MultiDex加载流程分析只介绍主要流程,multiDex源码已经上传到https://github.com/AlexSmille/google-android-support-source-analyze,包含了BaseDexClassloader等一些android源码。1.判断当前是否需要动态加载类在5.0以上,会自动加载一个apk中所有... 查看详情
linux驱动开发笔记:基于ubuntu的helloworld驱动源码编写makefile编写以及驱动编译加载流程测试(代码片段)
前言 前面学习了驱动的基础框架,上一篇编译了gcc7.3.0,那么为了方便很好的熟悉流程,本篇,将使用ubuntu18.04,直接编译ubuntu18.04的驱动,然后做好本篇文章的相关实战测试。 Ubuntu虚拟机准备步骤一:安装虚拟机 本... 查看详情
系统启动流程以及内核管理(代码片段)
...作用:进程管理,内存管理,设备管理,网络管理,硬件驱动,安全机制启动流程:1,POST加电自检作用:对电脑的各种硬件进行检测,看是否正常ROM(主要):加载BIOS保存电脑的输出程序,但是它是保存在内存之中RAM:保存电... 查看详情
简单的linux驱动程序以及如何加载/卸载驱动(代码片段)
今天记录一下简单的Linux驱动程序怎么写以及如何加载/卸载驱动以hello.c为例:hello.c #ifndef__KERNEL__#define__KERNEL__#endif#ifndefMODULE#defineMODULE#endif#include<linux/kernel.h>#include<linux/module.h>#include< 查看详情
一张图掌握linux字符设备驱动架构!建议收藏(代码片段)
目录一.Linux中字符设备驱动简介二.字符设备驱动快速入门(超简单demo)1.demo2.代码编译3.加载驱动模块4.创建设备节点文件5.APP设备文件操作6.卸载驱动模块三.字符设备驱动开发流程介绍:1.驱动模块加载和卸载的入口... 查看详情
linux驱动开发:字符设备驱动开发(代码片段)
文章目录Linux驱动开发:字符设备驱动开发一、字符设备简介二、驱动调用原理三、驱动加载与卸载四、字符设备注册与注销五、设备驱动编写流程六、添加驱动信息Linux驱动开发:字符设备驱动开发一、字符设备简介字... 查看详情
linux驱动开发:字符设备驱动开发(代码片段)
文章目录Linux驱动开发:字符设备驱动开发一、字符设备简介二、驱动调用原理三、驱动加载与卸载四、字符设备注册与注销五、设备驱动编写流程六、添加驱动信息Linux驱动开发:字符设备驱动开发一、字符设备简介字... 查看详情
linux:主机usb设备驱动简析(代码片段)
...U盘)固件基础5.LinuxUSB子系统初始化6.LinuxUSB主机控制器(HCD)驱动6.1USB主机控制器驱动初始化6.2USB主机控制器设备对象注册和驱动加载7.LinuxUSB设备驱动加载过程7.1`HUB类设备`驱动加载过程7.2`非HUB类设备`驱动加载过程7.2.1... 查看详情
linux虚拟pinctrldemo驱动(代码片段)
...解Linux的Pinctrl子系统,这里编写一个虚拟的PinctrlDemo驱动。下面是编写的内容文件列表:下面是在加载驱动virt_pinctrl_client.ko和virt_pinctrl_driver.ko驱动后的运行效果:下面主要是分析virt_pinctrl_driver.c,virt_pinctrl_cli 查看详情
面向对象地分析linux内核设备驱动——用面向对象思想分析vfbframebuffer设备驱动(代码片段)
用面向对象思想分析vfbFramebuffer设备驱动- 内核版本LinuxKernel2.6.34,与Robert.Love的《LinuxKernelDevelopment》(第三版)所讲述的内核版本一样- 源代码下载路径:https://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.34.tar.bz 查看详情
使用spi机制加载mysql驱动源码分析(代码片段)
...加载driver主要原因是为了实现解耦和可插拔JDK提供数据库驱动的规范(即Driver接口) 查看详情
v4l2学习流程(代码片段)
...程参考资料关键资料,插图让人一下子就理解了Linux摄像头驱动1——vividLinux摄像头驱动2——UVC重写uvc比较完整注释版本从更大的角度去看V4L2框架,不局限在摄像头V4L2框架概述排版不错的笔记USB摄像头驱动框架分析从零写USB摄像头... 查看详情
linux——linux驱动之总线设备驱动注册流程分析及详细操作步骤(上)
目录0引言1Linux总线、设备、驱动注册流程分析2设备注册0引言 总线、设备、驱动这个嵌入式里面经常提及的词汇,那么Linux中总线设备驱动注册流程是什么样的?具体注册是如何操作的?本篇博文带你详细了解... 查看详情
gic驱动程序分析(代码片段)
第五章GIC驱动程序分析1.回顾GIC中断处理流程1.1一级中断控制器处理流程1.2多级中断控制器处理流程2.GIC中的重要函数和结构体3.GIC初始化过程2.1内核支持多种GIC2.2在设备树里指定GIC2.3gic_of_init分析3.申请GIC中断3.1在设备树里指定... 查看详情
spring源码跟踪,bean加载流程分析。(代码片段)
...加班砍了一天假,利用周日的时间宅家撸了一次Spring加载流程,把加载步骤列了出来。不才,还望大佬指教。一.基于注解进入容器@Testpublicvoidtest03()AnnotationConfigApplicationContextcontext=newAnnotationConfigApplicationContext(Sp... 查看详情