注:本文为 “Linux 设备和驱动加载” 相关合辑。
未整理去重。
如有内容异常,请看原文。
Linux设备和驱动加载的先后顺序
maopig 于 2012-03-20 22:50:04 发布
一、Linux驱动注册顺序控制
Linux 驱动先注册总线,总线上既可以先挂载设备(device),也可以先挂载驱动(driver)。那么,究竟如何控制这两者的先后顺序呢?
Linux系统使用两种方式来加载系统中的模块:动态加载和静态加载。
静态加载
静态加载是将所有模块的程序编译到Linux内核中,由do_initcall函数进行加载。核心进程(/init/main.c)的执行流程为:kernel_init -> do_basic_setup() -> do_initcalls()。在do_initcalls函数中,会将在__initcall_start和__initcall_end之间定义的各个模块依次加载。
那么,在__initcall_start和__initcall_end之间都包含哪些内容呢?通过查看/arch/powerpc/kernel/vmlinux.lds文件,可以找到.initcall.init段的定义如下:
.initcall.init : {
__initcall_start = .;
*(.(initcall0.init) *(.(initcall0s.init) *(.(initcall1.init) *(.(initcall1s.init) *(.(initcall2.init) *(.(initcall2s.init) *(.(initcall3.init) *(.(initcall3s.init) *(.(initcall4.init) *(.(initcall4s.init) *(.(initcall5.init) *(.(initcall5s.init) *(.(initcallrootfs.init) *(.(initcall6.init) *(.(initcall6s.init) *(.(initcall7.init) *(.(initcall7s.init)
__initcall_end = .;
}
从上述定义可以看出,在这两个宏之间依次排列了14个等级的宏。由于这些宏是按先后顺序链接的,所以这14个宏具有优先级,顺序为:0 > 1 > 1s > 2 > 2s …… > 7 > 7s。
这些宏具体有什么意义呢?这需要查看/include/linux/init.h文件,其中定义了具体的宏:
#define pure_initcall(fn) __define_initcall("0",fn,0)
#define core_initcall(fn) __define_initcall("1",fn,1)
#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
#define postcore_initcall(fn) __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
#define arch_initcall(fn) __define_initcall("3",fn,3)
#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
#define subsys_initcall(fn) __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
#define fs_initcall(fn) __define_initcall("5",fn,5)
#define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
#define late_initcall(fn) __define_initcall("7",fn,7)
#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
我们平时使用的module_init在静态编译时就相当于device_initcall。例如,在2.6.24的内核中,gianfar_device使用的是arch_initcall,而gianfar_driver使用的是module_init。由于arch_initcall的优先级大于module_init,所以gianfar设备驱动的device会先于driver在总线上添加。
二、系统初始化函数集(subsys_initcall)和初始化段应用
(一)系统初始化调用函数集分析(静态)
1. 函数定义
在Linux内核代码里,运用了subsys_initcall来进行各种子系统的初始化。以2.6.29内核为例,在<include/linux/init.h>下可以找到subsys_initcall的定义:
#define pure_initcall(fn) __define_initcall("0",fn,0)
#define core_initcall(fn) __define_initcall("1",fn,1)
#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
#define postcore_initcall(fn) __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
#define arch_initcall(fn) __define_initcall("3",fn,3)
#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
#define subsys_initcall(fn) __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
#define fs_initcall(fn) __define_initcall("5",fn,5)
#define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
#define late_initcall(fn) __define_initcall("7",fn,7)
#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
而__define_initcall又被定义为:
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
所以,subsys_initcall(fn)等价于__initcall_fn4,它将被链接器放于section .initcall4.init中。(__attribute__相关内容将在另一篇文章中介绍)
2. 初始化函数集的调用过程执行过程
系统启动后,在rest_init中会创建init内核线程,执行流程为:start_kernel -> rest_init -> init -> do_basic_setup -> do_initcalls。在do_initcalls中,会把.initcall.init中的函数依次执行一遍:
for (call = __initcall_start; call < __initcall_end; call++) {
// ......
result = (*call)();
// ........
}
这个__initcall_start是在文件<arch/xxx/kernel/vmlinux.lds.S>中定义的:
.initcall.init : AT(ADDR(.initcall.init) - LOAD_OFFSET) {
__initcall_start = .;
INITCALLS
__initcall_end = .;
}
INITCALLS被定义于<asm-generic/vmlinux.lds.h>:
#define INITCALLS \
*(.initcall0.init) \
*(.initcall0s.init) \
*(.initcall1.init) \
*(.initcall1s.init) \
*(.initcall2.init) \
*(.initcall2s.init) \
*(.initcall3.init) \
*(.initcall3s.init) \
*(.initcall4.init) \
*(.initcall4s.init) \
*(.initcall5.init) \
*(.initcall5s.init) \
*(.initcallrootfs.init) \
*(.initcall6.init) \
*(.initcall6s.init) \
*(.initcall7.init) \
*(.initcall7s.init)
(二)基于模块方式的初始化函数(动态)
1. 函数定义
subsys_initcall的静态调用方式的来龙去脉已阐述清楚,下面来看动态方式的初始化函数调用(模块方式)。在<include/linux/init.h>里,如果MODULE宏被定义,说明该函数将被编译进模块里,在系统启动后以模块方式被调用:
#define core_initcall(fn) module_init(fn)
#define postcore_initcall(fn) module_init(fn)
#define arch_initcall(fn) module_init(fn)
#define subsys_initcall(fn) module_init(fn)
#define fs_initcall(fn) module_init(fn)
#define device_initcall(fn) module_init(fn)
#define late_initcall(fn) module_init(fn)
这意味着对于驱动模块,使用subsys_initcall等价于使用module_init。
2. module_init分析
下面来看看module_init宏究竟做了什么:
#define module_init(initfn) \
static inline initcall_t __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));
typedef int (*initcall_t)(void);
在以模块方式编译一个模块的时候,会自动生成一个xxx.mod.c文件,在该文件里面定义一个struct module变量,并把init函数设置为上面的init_module()。而上面的这个init_module,被alias成模块的初始化函数(参考<gcc关键字:__attribute__, alias, visibility, hidden>)。
也就是说,模块装载的时候(insmod,modprobe),sys_init_module()系统调用会调用module_init指定的函数(对于编译成模块的情况)。
3. 模块的自动加载
内核在启动时已经检测到了系统的硬件设备,并把硬件设备信息通过sysfs内核虚拟文件系统导出。sysfs文件系统由系统初始化脚本挂载到/sys上。udev扫描sysfs文件系统,根据硬件设备信息生成热插拔(hotplug)事件,udev再读取这些事件,生成对应的硬件设备文件。由于没有实际的硬件插拔动作,所以这一过程被称为coldplug。
udev完成coldplug操作,需要下面三个程序:
udevtrigger:扫描sysfs文件系统,生成相应的硬件设备hotplug事件。udevd:作为deamon,记录hotplug事件,然后排队后再发送给udev,避免事件冲突(race conditions)。udevsettle:查看udev事件队列,等队列内事件全部处理完毕才退出。
要规定事件怎样处理就要编写规则文件,规则文件是udev的核心,没有规则文件,udev无法自动加载硬件设备的驱动模块。规则文件一般位于<etc/udev/rules.d>。
(三)初始化段的应用
这里给出一个简单的初始化段的使用例子,将a.c编译成一个动态库,其中,函数a()和函数c()放在两个不同的初始化段里,函数b()默认放置;编译main.c,链接到由a.c编译成的动态库,观察各函数的执行顺序。
// a.c
#include <stdio.h>
typedef int (*fn) (void);
int a(void)
{
printf("a\n");
return 0;
}
__attribute__((__section__(".init_array.2"))) static fn init_a = a;
int c(void)
{
printf("c\n");
return 0;
}
__attribute__((__section__(".init_array.1"))) static fn init_c = c;
int b()
{
printf("b\n");
return 0;
}
// main.c
#include <stdio.h>
int b();
int main()
{
printf("main\n");
b();
}
# mk.sh
gcc -fPIC -g -c a.c
gcc -shared -g -o liba.so a.o
cp liba.so /lib/ -fr
gcc main.c liba.so
ldconfig
./a.out
执行上述脚本后,输出结果为:
a
c
main
b
三、类Unix操作系统中驱动加载方式
在类Unix操作系统中,驱动加载方式一般分为动态加载和静态加载,下面分别对其进行详细论述。
(一)动态加载
动态加载是将驱动模块加载到内核中,但不能放入/lib/modules/下。
在2.4内核中,加载驱动命令为insmod,删除模块为rmmod;在2.6以上内核中,除了insmod与rmmod外,加载命令还有modprobe。
insmod与modprobe的不同之处在于:insmod需要指定驱动模块的绝对路径,如insmod 绝对路径/××.o;而modprobe只需指定模块名即可,不用加.ko或.o后缀,也不用加路径,最重要的一点是,modprobe同时会加载当前模块所依赖的其它模块。
使用lsmod可以查看当前加载到内核中的所有驱动模块,同时还能提供其它一些信息,比如其它模块是否在使用另一个模块。
(二)静态加载
1. 概念
在执行make menuconfig命令进行内核配置裁剪时,在窗口中可以选择是否将驱动编译入内核,还是放入/lib/modules/下相应内核版本目录中,或者不选。
2. 操作步骤
Linux设备一般分为字符设备、块设备和网络设备,每种设备在内核源代码目录树drivers/下都有对应的目录,其加载方法类似。以下以字符设备静态加载为例,假设驱动程序源代码名为ledc.c,具体操作步骤如下:
第一步:将ledc.c源程序放入内核源码drivers/char/下。
第二步:修改drivers/char/Config.in文件,具体修改方式如下:
按照打开文件中的格式添加即可,在文件的适当位置(这个位置决定其在make menuconfig窗口中所在位置)加入以下任一段代码:
tristate 'LedDriver' CONFIG_LEDC
if [ "$CONFIG_LEDC" = "y" ];then
bool ' Support for led on h9200 board' CONFIG_LEDC_CONSOLE
fi
说明:以上代码使用tristate来定义一个宏,表示此驱动可以直接编译至内核(用*选择),也可以编制至/lib/modules/下(用M选择),或者不编译(不选)。
bool 'LedDriver' CONFIG_LEDC
if [ "$CONFIG_LEDC" = "y" ];then
bool ' Support for led on h9200 board' CONFIG_LEDC_CONSOLE
fi
说明:以上代码使用bool来定义一个宏,表示此驱动只能直接编译至内核(用*选择)或者不编译(不选),不能编制至/lib/modules/下(用M选择)。
第三步:修改drivers/char/Makefile文件
在适当位置加入下面一行代码:
obj-$(CONFIG_LEDC) += ledc.o
或者在obj-y一行中加入ledc.o,如:
obj-y += ledc.o mem.o 后面不变;
经过以上的设置,就可以在执行make menuconfig命令后的窗口中的character devices --->中进行选择配置了。选择后重新编译即可。
Linux 驱动 module_init() 本质——不同驱动加载顺序对应不同的优先级
ffmxnjm 于 2017-05-12 11:44:43 发布
阶段一:内核中驱动加载优先级的定义
在kernel -3.18\include\linux\init.h中,Linux内核为不同驱动的加载顺序设定了不同的优先级,并定义了一系列宏:
#define pure_initcall(fn) __define_initcall("0",fn,1) //纯粹的初始化调用
#define core_initcall(fn) __define_initcall("1",fn,1) //主要的初始化调用
#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
#define postcore_initcall(fn) __define_initcall("2",fn,2) //相对次要的初始化调用
#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
#define arch_initcall(fn) __define_initcall("3",fn,3) //与硬件架构相关平台设备的初始化调用
#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
#define subsys_initcall(fn) __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
#define fs_initcall(fn) __define_initcall("5",fn,5)
#define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
#define late_initcall(fn) __define_initcall("7",fn,7) //晚的初始化调用
#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
#define module_init(x)__initcall(x);
#define__initcall(fn)device_initcall(fn)
在这些不同的优先级中,数字越小,优先级越高。同一等级优先级的驱动,加载顺序由链接过程决定,结果不确定,无法手动设置先后顺序;不同等级的驱动加载顺序是先高优先级,后低优先级。例如,late_initcall的初始化调用在module_init之后。
所有的__init函数存放在.init.text区段中,同时在.initcall.init中还保存了一份函数指针。在初始化时,内核会通过这些函数指针调用__init函数指针,并在整个初始化完成后,释放整个init区段(包括.init.text,.initcall.init等)。这些函数在内核初始化过程中的调用顺序只和.initcall.init中的函数指针顺序有关,与函数本身在.init.text区段中的顺序无关。
在2.6内核中,initcall.init区段又分成7个子区段,不同的区段,调用顺序不一样,数字越小的优先级越高。
在内核源代码中,platform设备的初始化(注册)使用arch_initcall()调用,它的initcall的level为3;而驱动的注册使用module_init()调用,即device_initcall(),它的initcall的level为6。
阶段二:Linux系统启动过程与initcalls机制
Linux系统启动过程复杂,既支持模块静态加载机制,也支持动态加载机制。模块动态加载机制为系统提供了极大的灵活性,驱动程序既可静态编译进内核,也可动态加载。Linux系统中对设备和子系统的初始化在最后进行,主要过程如下图所示:

进入子系统初始化时,在内核init进程中进行设备初始化,其中do_initcalls()函数调用是最为复杂、关键的机制。该函数完成了所有需要静态加载模块的初始化,需要进行静态加载的内核模块,需使用特定的宏进行处理。
do_initcalls()函数原型

核心部分在639 - 671之间,是一个函数指针调用,遍历_initcall_start`_initcall_end`范围,逐个调用函数指针。`_initcall_start`_initcall_end之间存放的内容如下图所示:

宏_init_begin、init.text、.init.data等,使用相关宏处理函数指针,可以将被处理的函数指针放在特定的指针范围内。例如,网络设备层初始化函数net_dev_init(),定义在net/core/dev.c中,通过subsys_initcall(net_dev_init)宏,将net_dev_init函数指针放在.initcall4.init段中,在do_initcalls()函数调用时,该函数就会被调用。
这种机制的设计初衷是为了实现一个通用的启动流程,使移植或扩展时,只需要对需要启动加载的模块进行宏处理即可。
gcc支持手动定位代码段位置,_attribute_是gcc的关键字,指示编译器给符号设置特定属性。编译完成后输入到链接器的是各个带有符号表的文件,链接器对各个文件中符号进行重定位,_attribute_在该阶段进行处理,将指定符号放在链接生成文件段中特定位置,包括代码段和数据段。具体段的生成由链接配置文件arch/xxx/vmlinux.ds.S进行配置。

在2.6.16内核中INITCALLS已直接被替换为:
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
这和图3中的结构是对应的。内核在include/linux/init.h文件中提供了各种宏定义用来处理特定函数指针和数据:
#define __define_initcall(level,fn) \
static initcall_t __initcall_##fn __attribute_used__ \
__attribute__((__section__(".initcall" level ".init"))) = fn
#define core_initcall(fn) __define_initcall("1",fn)
#define postcore_initcall(fn) __define_initcall("2",fn)
#define arch_initcall(fn) __define_initcall("3",fn)
#define subsys_initcall(fn) __define_initcall("4",fn)
#define fs_initcall(fn) __define_initcall("5",fn)
#define device_initcall(fn) __define_initcall("6",fn)
#define late_initcall(fn) __define_initcall("7",fn)
内核为满足不同初始化等级,设计了1 - 7共7个等级,不同等级初始化代码用对应的宏进行处理。还有其它一些宏,如模块加载宏module_init(),module_exit(),其处理方式略有不同。
总的来说,initcalls机制为内核开发者或驱动开发者提供了一个接口,方便将自己的函数添加到内核初始化列表中,在内核初始化最后阶段进行处理。
阶段三:模块配置与加载方式
当使用make menuconfig来配置内核时,将某个module配置为m时,MODULE这个宏就被定义;当配置为y时,则没有定义,具体实现在kernel的根Makefile(-DMODULE)里。
模块配置为m的情况
initcall_t的定义为:
typedef int (*initcall_t)(void);
它是一个接收参数为void,返回值为int类型的函数指针。当调用insmod将module加载进内核时,会去找init_module作为入口地址,通过alias将initfn变名为init_module,这样module就被加载了。以nvme.ko为例,通过objdump -t nvme.ko查看该模块的符号表,可发现init_module和nvme_init指向同一个偏移量。

模块编进内核的情况
当选择将模块编进内核,让它随内核启动而加载时,module_init最终会调用__define_initcall宏,这个宏的作用是将初始化函数放在.initcall6.init中。其位置可以在Vmlinux.lds.h和vmlinux.lds.S中找到相关定义。
系统启动时存放初始化数据的内存布局如下:
_init_begin -------------------
| .init.text | ---- __init
|-------------------|
| .init.data | ---- __initdata
_setup_start |-------------------|
| .init.setup | ---- __setup_param
__initcall_start |-------------------|
| .initcall1.init | ---- core_initcall
|-------------------|
| .initcall2.init | ---- postcore_initcall
|-------------------|
| .initcall3.init | ---- arch_initcall
|-------------------|
| .initcall4.init | ---- subsys_initcall
|-------------------|
| .initcall5.init | ---- fs_initcall
|-------------------|
| .initcall6.init | ---- device_initcall
|-------------------|
| .initcall7.init | ---- late_initcall
__initcall_end |-------------------|
| |
| ... ... ... |
| |
__init_end -------------------
各个initcall被调用的地方在kernel_init -> do_basic_setup -> do_initcalls里面:
static void __init do_initcalls(void)
{
initcall_t *call;
int count = preempt_count();
for (call = __initcall_start; call < __initcall_end; call++) {
ktime_t t0, t1, delta;
char *msg = NULL;
char msgbuf[40];
int result;
if (initcall_debug) {
printk("Calling initcall 0x%p", *call);
print_fn_descriptor_symbol(": %s()",
(unsigned long) *call);
printk("\n");
t0 = ktime_get();
}
result = (*call)();
...
}
linux驱动程序的加载顺序
田园诗人之园于 2022-08-16 23:58:00 发布
该文档的分析是基于 linux-4.14 的分析。
1 linux内核驱动加载宏
Linux内核为不同驱动的加载顺序对应不同的优先级,定义了一些宏:
这些宏位于include/linux/init.h文件中,最终驱动通过使用下面的这些不同的加载宏被放入到不同的段(.section)中,在内核初始化的时候,会调用do_initcalls去处理这些段。
1.1 linux内核驱动加载宏
149 /*
150 * initcalls are now grouped by functionality into separate
151 * subsections. Ordering inside the subsections is determined
152 * by link order.
153 * For backwards compatibility, initcall() puts the call in
154 * the device init subsection.
155 *
156 * The `id' arg to __define_initcall() is needed so that multiple initcalls
157 * can point at the same handler without causing duplicate-symbol build errors.
158 *
159 * Initcalls are run by placing pointers in initcall sections that the
160 * kernel iterates at runtime. The linker can do dead code / data elimination
161 * and remove that completely, so the initcall sections have to be marked
162 * as KEEP() in the linker script.
163 */
164
165 #define __define_initcall(fn, id) \
166 static initcall_t __initcall_##fn##id __used \
167 __attribute__((__section__(".initcall" #id ".init"))) = fn;
168
169 /*
170 * Early initcalls run before initializing SMP.
171 *
172 * Only for built-in code, not modules.
173 */
174 #define early_initcall(fn) __define_initcall(fn, early)
175
176 /*
177 * A "pure" initcall has no dependencies on anything else, and purely
178 * initializes variables that couldn't be statically initialized.
179 *
180 * This only exists for built-in code, not for modules.
181 * Keep main.c:initcall_level_names[] in sync.
182 */
183 #define pure_initcall(fn) __define_initcall(fn, 0)
184
185 #define core_initcall(fn) __define_initcall(fn, 1)
186 #define core_initcall_sync(fn) __define_initcall(fn, 1s)
187 #define postcore_initcall(fn) __define_initcall(fn, 2)
188 #define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
189 #define arch_initcall(fn) __define_initcall(fn, 3)
190 #define arch_initcall_sync(fn) __define_initcall(fn, 3s)
191 #define subsys_initcall(fn) __define_initcall(fn, 4)
192 #define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
193 #define fs_initcall(fn) __define_initcall(fn, 5)
194 #define fs_initcall_sync(fn) __define_initcall(fn, 5s)
195 #define rootfs_initcall(fn) __define_initcall(fn, rootfs)
196 #define device_initcall(fn) __define_initcall(fn, 6)
197 #define device_initcall_sync(fn) __define_initcall(fn, 6s)
198 #define late_initcall(fn) __define_initcall(fn, 7)
199 #define late_initcall_sync(fn) __define_initcall(fn, 7s)
200
201 #define __initcall(fn) device_initcall(fn)
202
203 #define __exitcall(fn) \
204 static exitcall_t __exitcall_##fn __exit_call = fn
205
206 #define console_initcall(fn) \
207 static initcall_t __initcall_##fn \
208 __used __section(.con_initcall.init) = fn
209
210 #define security_initcall(fn) \
211 static initcall_t __initcall_##fn \
212 __used __section(.security_initcall.init) = fn
1.2 __define_initcall
下面重点关注一下__define_initcall的实现:
165 #define __define_initcall(fn, id) \
166 static initcall_t __initcall_##fn##id __used \
167 __attribute__((__section__(".initcall" #id ".init"))) = fn;
通过__define_initcall(fn, id)宏,依据id将不同的fn放置于不同的段(.section)中。
__attribute__是用于设置函数属性、变量属性以及类型属性;__attribute__((section("section_name")))其作用是将特定的函数或数据放入指定名为"section_name"的段(.section)中;“#”的作用是将后面紧跟着的id转换为字符串;“##”则用于连接两个不同的字符。__attribute__((_used_))的作用是告诉编译器这个静态符号在编译的时候即使没有使用到也要保留这个符号;__attribute__((__section__(".initcall" #id ".init"))) = fn的作用即为将fn放入定义好的initcalln.init的段中,n代表的是id。
2 链接文件(.ld)中的定义
对于linux内核,链接器所需要处理的链接文件位于arch/arm/kernel/vmlinux.lds.S文件,其对应各段的定义位于include/asm-generic/vmlinux.lds.h文件。
arch/arm/kernel/vmlinux.lds.S文件的.init.data段标识了要将哪些段放入到.init.data段中。我们需要重点关注的是INIT_CALLS 段。
✗ 218 .init.data : {
✗ 219 INIT_DATA
✗ 220 INIT_SETUP(16)
✗ 221 INIT_CALLS
✗ 222 CON_INITCALL
✗ 223 SECURITY_INITCALL
✗ 224 INIT_RAM_FS
✗ 225 }
✗ 226 .exit.data : {
✗ 227 ARM_EXIT_KEEP(EXIT_DATA)
✗ 228 }
INIT_CALLS 的定义位于include/asm-generic/vmlinux.lds.h文件中,如下代码所示,INIT_CALL段也包含很多不同的段,现在拿INIT_CALLS_LEVEL 为例来说明,INIT_CALLS_LEVEL 段定义为__initcall##level##_start,将.initcall##level##.init以及.initcall##level##s.init放入到INIT_CALLS段中。
level对应于在1.2 __define_initcall定义的id
734 #define INIT_SETUP(initsetup_align) \
735 . = ALIGN(initsetup_align); \
736 VMLINUX_SYMBOL(__setup_start) = .; \
737 KEEP(*(.init.setup)) \
738 VMLINUX_SYMBOL(__setup_end) = .;
739
740 #define INIT_CALLS_LEVEL(level) \
741 VMLINUX_SYMBOL(__initcall##level##_start) = .; \
742 KEEP(*(.initcall##level##.init)) \
743 KEEP(*(.initcall##level##s.init)) \
744
745 #define INIT_CALLS \
746 VMLINUX_SYMBOL(__initcall_start) = .; \
747 KEEP(*(.initcallearly.init)) \
748 INIT_CALLS_LEVEL(0) \
749 INIT_CALLS_LEVEL(1) \
750 INIT_CALLS_LEVEL(2) \
751 INIT_CALLS_LEVEL(3) \
752 INIT_CALLS_LEVEL(4) \
753 INIT_CALLS_LEVEL(5) \
754 INIT_CALLS_LEVEL(rootfs) \
755 INIT_CALLS_LEVEL(6) \
756 INIT_CALLS_LEVEL(7) \
757 VMLINUX_SYMBOL(__initcall_end) = .;
758
759 #define CON_INITCALL \
760 VMLINUX_SYMBOL(__con_initcall_start) = .; \
761 KEEP(*(.con_initcall.init)) \
762 VMLINUX_SYMBOL(__con_initcall_end) = .;
763
764 #define SECURITY_INITCALL \
765 VMLINUX_SYMBOL(__security_initcall_start) = .; \
766 KEEP(*(.security_initcall.init)) \
767 VMLINUX_SYMBOL(__security_initcall_end) = .;
768
769 #ifdef CONFIG_BLK_DEV_INITRD
770 #define INIT_RAM_FS \
771 . = ALIGN(4); \
772 VMLINUX_SYMBOL(__initramfs_start) = .; \
773 KEEP(*(.init.ramfs)) \
774 . = ALIGN(8); \
775 KEEP(*(.init.ramfs.info))
776 #else
777 #define INIT_RAM_FS
778 #endif
3 linux 内核对 initcall 的处理流程
下面将分析从start_kernel开始一直到do_initcalls的调用关系。对驱动各模块的加载处理也是在do_initcalls中处理的。
3.1 调用关系
所哟的这些函数均位于init/main.c文件中
| - start_kernel ---(init/main.c)
| - rest_init ---(init/main.c)
| - kernel_init (pid = kernel_thread(kernel_init, NULL, CLONE_FS)) ---(init/main.c)
| - kernel_init_freeable ---(init/main.c)
| - do_basic_setup ---(init/main.c)
| - do_initcalls ---(init/main.c)
| - do_initcall_level
| - do_one_initcall
3.2 do_initcalls
如下所示,do_initcalls调用do_initcall_level进而再调用do_one_initcall去做更具体的处理。initcall_levels 数组中的各个成员均和链接文件(.ld)中的定义是一致的。所有的这些均包含在INITCALLS段中。
845 extern initcall_t __initcall_start[];
846 extern initcall_t __initcall0_start[];
847 extern initcall_t __initcall1_start[];
848 extern initcall_t __initcall2_start[];
849 extern initcall_t __initcall3_start[];
850 extern initcall_t __initcall4_start[];
851 extern initcall_t __initcall5_start[];
852 extern initcall_t __initcall6_start[];
853 extern initcall_t __initcall7_start[];
854 extern initcall_t __initcall_end[];
855
856 static initcall_t *initcall_levels[] __initdata = {
857 __initcall0_start,
858 __initcall1_start,
859 __initcall2_start,
860 __initcall3_start,
861 __initcall4_start,
862 __initcall5_start,
863 __initcall6_start,
864 __initcall7_start,
865 __initcall_end,
866 };
867
868 /* Keep these in sync with initcalls in include/linux/init.h */
869 static char *initcall_level_names[] __initdata = {
870 "early",
871 "core",
872 "postcore",
873 "arch",
874 "subsys",
875 "fs",
876 "device",
877 "late",
878 };
879
880 static void __init do_initcall_level(int level)
881 {
882 initcall_t *fn;
883
884 strcpy(initcall_command_line, saved_command_line);
885 parse_args(initcall_level_names[level],
886 initcall_command_line, __start___param,
887 __stop___param - __start___param,
888 level, level,
889 NULL, &repair_env_string);
890
891 for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
892 do_one_initcall(*fn);
893 }
894
895 static void __init do_initcalls(void)
896 {
897 int level;
898
899 for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
900 do_initcall_level(level);
901 }
4 具体样例分析
4.1 module_init分析
module_init的宏定义位于drivers/staging/lustre/lustre/include/lustre_compat.h文件中,而module_init会调用到late_initcall宏定义。而late_initcall宏定义参考1.1 linux内核驱动加载宏章的声明,late_initcall的
51 /*
52 * OBD need working random driver, thus all our
53 * initialization routines must be called after device
54 * driver initialization
55 */
56 #ifndef MODULE
57 #undef module_init
58 #define module_init(a) late_initcall(a)
59 #endif
4.2 builtin_platform_driver分析
builtin_platform_driver的宏定义位于include/linux/platform_device.h文件中,而builtin_platform_driver会调用到builtin_driver,
231 /* builtin_platform_driver() - Helper macro for builtin drivers that
232 * don't do anything special in driver init. This eliminates some
233 * boilerplate. Each driver may only use this macro once, and
234 * calling it replaces device_initcall(). Note this is meant to be
235 * a parallel of module_platform_driver() above, but w/o _exit stuff.
236 */
237 #define builtin_platform_driver(__platform_driver) \
238 builtin_driver(__platform_driver, platform_driver_register)
builtin_driver宏定义位于include/linux/device.h文件中,该宏定义会调用到device_initcall宏,而该宏的定义依然是需要参考1.1 linux内核驱动加载宏的声明部分。
1511 /**
1512 * builtin_driver() - Helper macro for drivers that don't do anything
1513 * special in init and have no exit. This eliminates some boilerplate.
1514 * Each driver may only use this macro once, and calling it replaces
1515 * device_initcall (or in some cases, the legacy __initcall). This is
1516 * meant to be a direct parallel of module_driver() above but without
1517 * the __exit stuff that is not used for builtin cases.
1518 *
1519 * @__driver: driver name
1520 * @__register: register function for this driver type
1521 * @...: Additional arguments to be passed to __register
1522 *
1523 * Use this macro to construct bus specific macros for registering
1524 * drivers, and do not use it on its own.
1525 */
1526 #define builtin_driver(__driver, __register, ...) \
1527 static int __init __driver##_init(void) \
1528 { \
1529 return __register(&(__driver) , ##__VA_ARGS__); \
1530 } \
1531 device_initcall(__driver##_init);
linux设备驱动——bus、device、driver加载顺序与匹配流程
贺二公子已于 2023-01-09 15:10:48 修改
1. 前言
最近回看了下Linux设备驱动相关知识,做了个总结。有些话需要说在前面:
- 文中有些内容为个人理解(上标H所标识内容),未必准确,有误请评论指正。
- 4.2节的内容主要目的是为了搞清楚driver和device在加载的过程中是如何通过bus相互匹配。
本文源码源自4.10.17版本linux内核。
2. 概念
Linux设备驱动有三个基本概念:总线、驱动以及设备。三者之间关系简单描述如下:
- 总线为外设或片内模组与核心处理器之间的通信总线,例如SPI、I2C、SDIO、USB等。
- 每个驱动、设备都需要挂载到一种总线上;
- 挂载到相同总线的驱动和设备可以通过该总线进行匹配,实现设备与对应的驱动之间的绑定。
2.1. 数据结构
以下为总线、驱动及设备在linux内核中对应的核心数据结构
// 总线数据结构 -- include/linux/device.h
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs; /* use dev_groups instead */
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev); // 探测驱动与设备是否匹配,匹配则完成绑定
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p; // 如下
struct lock_class_key lock_key;
};
// drivers/base/base.h
struct subsys_private {
struct kset subsys; // the struct kset that defines this subsystem
struct kset *devices_kset; // the subsystem's 'devices' directory
struct list_head interfaces;
struct mutex mutex;
struct kset *drivers_kset; // the list of drivers associated
struct klist klist_devices;
struct klist klist_drivers;
struct blocking_notifier_head bus_notifier;
unsigned int drivers_autoprobe:1;
struct bus_type *bus;
struct kset glue_dirs;
struct class *class;
};
// 驱动数据结构 -- include/linux/device.h
struct device_driver {
const char *name;
struct bus_type *bus; // 指向本驱动挂载的bus
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
enum probe_type probe_type;
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev); // 探测驱动与设备是否匹配,匹配则完成绑定
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p; // 如下
};
// drivers/base/base.h
struct driver_private {
struct kobject kobj;
struct klist klist_devices;
struct klist_node knode_bus;
struct module_kobject *mkobj;
struct device_driver *driver;
};
// 设备数据结构 -- include/linux/device.h
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name; /* initial name of the device */
const struct device_type *type;
struct mutex mutex; /* mutex to synchronize calls to
* its driver.
*/
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
void *driver_data; /* Driver data, set and get with
dev_set/get_drvdata */
struct dev_links_info links;
struct dev_pm_info power;
struct dev_pm_domain *pm_domain;
#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
struct irq_domain *msi_domain;
#endif
#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQ
struct list_head msi_list;
#endif
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */
unsigned long dma_pfn_offset;
struct device_dma_parameters *dma_parms;
struct list_head dma_pools; /* dma pools (if dma'ble) */
struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
#ifdef CONFIG_DMA_CMA
struct cma *cma_area; /* contiguous memory area for dma
allocations */
#endif
/* arch specific additions */
struct dev_archdata archdata;
struct device_node *of_node; /* associated device tree node */
struct fwnode_handle *fwnode; /* firmware device node */
dev_t devt; /* dev_t, creates the sysfs "dev" */
u32 id; /* device instance */
spinlock_t devres_lock;
struct list_head devres_head;
struct klist_node knode_class;
struct class *class;
const struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
struct iommu_group *iommu_group;
struct iommu_fwspec *iommu_fwspec;
bool offline_disabled:1;
bool offline:1;
};
2.2. probe函数
bus_type和device_driver中都包含probe()函数,两者关系可追踪到函数really_probe()中(该函数在后续的调用加载流程中会描述具体执行的位置,可先跳过,后续查看加载流程时返回此处查看),如下:
static int really_probe(struct device *dev, struct device_driver *drv)
{
int ret = -EPROBE_DEFER;
int local_trigger_count = atomic_read(&deferred_trigger_count);
bool test_remove = IS_ENABLED(CONFIG_DEBUG_TEST_DRIVER_REMOVE) &&
!drv->suppress_bind_attrs;
...
atomic_inc(&probe_count);
pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
drv->bus->name, __func__, drv->name, dev_name(dev));
WARN_ON(!list_empty(&dev->devres_head));
re_probe:
dev->driver = drv;
/* If using pinctrl, bind pins now before probing */
ret = pinctrl_bind_pins(dev);
if (ret)
goto pinctrl_bind_failed;
if (driver_sysfs_add(dev)) {
printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
__func__, dev_name(dev));
goto probe_failed;
}
if (dev->pm_domain && dev->pm_domain->activate) {
ret = dev->pm_domain->activate(dev);
if (ret)
goto probe_failed;
}
/*
* Ensure devices are listed in devices_kset in correct order
* It's important to move Dev to the end of devices_kset before
* calling .probe, because it could be recursive and parent Dev
* should always go first
*/
devices_kset_move_last(dev);
/***** 以下为重点 ****************/
if (dev->bus->probe) { // 若bus_type中定义了probe,则调用bus_type的probe
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) { // 若bus_type中未定义了probe,则调用bus_type的probe
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}
// 总结:在看过的有限几个总线源码里,bus_type.probe()最终依然会调用device_driver.probe()
// 个人理解 —— device_driver.probe()是必然会调用的,
// 添加bus_type.probe()是因为有的总线需要在device_driver.probe()之前做些额外的处理
/***** 重点已结束 ****************/
...
pinctrl_init_done(dev);
if (dev->pm_domain && dev->pm_domain->sync)
dev->pm_domain->sync(dev);
driver_bound(dev);
ret = 1;
pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name);
goto done;
probe_failed:
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_DRIVER_NOT_BOUND, dev);
pinctrl_bind_failed:
...
ret = 0;
done:
atomic_dec(&probe_count);
wake_up(&probe_waitqueue);
return ret;
}
3. bus、device、driver加载顺序
3.1. 加载方式
bus 加载方式
以SPI总线为例(drivers/spi/spi.c),初始化函数加载调用统一使用postcore_initcall()将总线的初始化接口spi_init()添加到内核的启动序列中,跟踪一下该函数:
postcore_initcall(spi_init) // drivers/spi/spi.c
-> #define postcore_initcall(fn) __define_initcall(fn, 2) // include/linux/init.h
driver 加载方式——动态加载、静态加载
驱动加载有两种方式:
- 静态加载:驱动随着linux内核的启动自动加载
- 动态加载:linux系统启动后,使用insmod命令加载
本节讨论的加载顺序为linux系统启动过程中的加载顺序,因此只考虑静态加载的情况。
驱动静态加载方式与总线类似,也是调用了统一的接口将驱动的初始化函数添加到内核的启动序列中。以基于SPI总线的驱动rtc-pcf2123(drivers/rtc/rtc-pcf2123.c)为例,驱动加载使用的统一接口为module_init()。下面代码可以示意驱动如何调用该函数,以及该函数如何将驱动初始化接口添加到内核启动序列中。
module_spi_driver(pcf2123_driver) // drivers/rtc/rtc-pcf2123.c
-> #define module_spi_driver(__spi_driver) \ // include/linux/spi/spi.h
module_driver(__spi_driver, spi_register_driver, \
spi_unregister_driver)
-> #define module_driver(__driver, __register, __unregister, ...) \ //include/linux/device.h
static int __init __driver##_init(void) \ // 对rtc-pcf2123驱动,这里定义了初始化函数pcf2123_driver_init()
{ \
return __register(&(__driver) , ##__VA_ARGS__); \ // 对rtc-pcf2123驱动,这里调用了接口spi_register_driver(&(pcf2123_driver))
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
-> #define module_init(x) __initcall(x); // include/linux/module.h
-> #define __initcall(fn) device_initcall(fn) // include/linux/init.h
-> #define device_initcall(fn) __define_initcall(fn, 6) // include/linux/init.h
device 加载方式——platform
linux内核使用devicetree描述具体硬件平台的设备参数,platform平台通过调用接口of_platform_default_populate_init(),将devicetree中的设备节点转换为内核可识别的platform设备数据结构platform_device,该结构中包含device结构。
linux内核启动后,调用arch_initcall_sync()接口将of_platform_default_populate_init()添加到启动序列中。跟踪该函数:
arch_initcall_sync(of_platform_default_populate_init) // drivers/of/platform.c
-> #define arch_initcall_sync(fn) __define_initcall(fn, 3s) // include/linux/init.h
3.2. 加载顺序
从bus、device、driver的加载方式可以看到,三者的初始化都调用了相同的接口__define_initcall(fn, id),函数定义及功能说明如下:
// include/linux/init.h
#define __define_initcall(fn, id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn;
/*************************/
// 功能说明
// 1. 定义名为__initcall_##fn##id的函数指针,指向fn函数
// 2. 将该函数指针存放在.initcall#id.init段中
// 3. .initcall#id.init段在内核初始化的过程中会被调用。
// 其中,id越小的.initcall#id.init段内的接口越早被调用
对该接口有兴趣,可以参照Reference2的详细说明。
由于bus、device、driver的id分别为2、3s、6,因此,初始化的顺序依次为:bus、device、driver。
4. device、driver匹配流程
此节主要想讨论device和driver是如何找到可以匹配的彼此。因此,分别从driver和device两个方向,简述从初始化到调用probe()完成driver与device的匹配的过程。
首先做个说明,从后续描述将会看到,driver和device都挂载在总线上,因此二者向找到相互匹配的彼此,都需要通过bus这一媒介。
4.1. 加载driver
driver还是以rtc-pcf2123驱动为例,从驱动的初始化函数pcf2123_driver_init()开始,调用流程如下:
pcf2123_driver_init()
-> spi_register_driver(&(pcf2123_driver)) // 前两步参照3.2节的代码说明吧
-> __spi_register_driver(THIS_MODULE, &(pcf2123_driver)) // include/linux/spi/spi.h
-> driver_register(&(&pcf2123_driver)->driver) // include/linux/spi/spi.c
-> bus_add_driver(&(&pcf2123_driver)->driver) // drivers/base/driver.c
// 重要地bus媒介在这里
-> driver_attach(&(&pcf2123_driver)->driver) // drivers/base/bus.c
-> __driver_attach(dev, &(&pcf2123_driver)->driver) // drivers/base/dd.c
// 该函数被循环调用,将&(&pcf2123_driver)->driver驱动
// 依次与spi总线上已挂载的每个设备dev进行匹配
-> driver_probe_device(&(&pcf2123_driver)->driver, dev) // drivers/base/dd.c
-> really_probe(dev, &(&pcf2123_driver)->driver) // drivers/base/dd.c
// 可以参照2.2节中的接口说明
/***************************/
// 到此,该驱动完成初始化;且若总线上已经挂在了可匹配的设备,则完成匹配
4.2. 加载device
由于积累有限,还无法完全理解platform平台。因此,草率地将 linux 设备分为两种(命名只是为了区分我的这两种分类,非官方命名):
- 总线设备:挂载与SPI、I2C等总线的设备
- platform设备:挂载与platform总线的设备
两种设备初始化使用不同的处理流程,但是最终都会调用同一个接口device_add(),下面先介绍两种设备从初始化到device_add()接口的调用流程。
总线设备device_add()前流程
以I2C设备为例,设备初始化从接口i2c_new_device()开始,接口调用流程如下:
i2c_new_device(i2c_adapter *adap) // drivers/i2c/i2c-core.c
-> device_register(struct device *dev) // drivers/base/core.c
-> device_add(dev) // drivers/base/core.c
platform设备device_add()前流程
platform设备就从初始化设备树节点的of_platform_default_populate_init()开始,接口调用流程如下:
of_platform_default_populate_init() // drivers/of/platform.c
-> of_platform_default_populate() // drivers/of/platform.c
-> of_platform_populate() // drivers/of/platform.c
-> of_platform_bus_create() // drivers/of/platform.c
// 遍历devicetree的设备节点,每个节点调用一次该函数
// 函数为自己及其children创建platform设备
-> of_platform_device_create_pdata() // drivers/of/platform.c
-> of_device_add() // drivers/of/device.c
-> device_add() // drivers/base/core.c
-> bus_add_device() // drivers/base/bus.c
// 完成设备向bus的添加
device与driver的匹配
上节device_add()调用bus_add_device()将设备添加到了bus的设备列表中。继续调用bus_probe_device(),已完成deice与driver的绑定。具体流程如下,设备从调用device_add()到probe()接口完成device与driver的匹配。
device_add() // drivers/base/core.c
-> bus_probe_device(dev) // drivers/base/bus.c
// 重要的bus媒介出现了
-> device_initial_probe(dev) // drivers/base/bus.c
// 说句实话,这里没有经过运行验证,感觉应该走这个函数对应的分支
-> __device_attach(dev, true) // drivers/base/dd.c
-> driver_probe_device(drv, dev) // drivers/base/dd.c
-> really_probe(dev, drv) // drivers/base/dd.c
// 可以参照2.2节中的接口说明
/***************************/
// 到此,该设备完成初始化;且若总线上已经挂载了可匹配的驱动,则完成匹配
via:
-
linux设备和驱动加载的先后顺序_linux驱动加载顺序-优快云博客
https://blog.youkuaiyun.com/maopig/article/details/7375933 -
linux 驱动 module_init()本质—>不同驱动加载顺序对应不同的优先级_module init 级别-优快云博客
https://blog.youkuaiyun.com/ffmxnjm/article/details/71713670 -
linux驱动程序的加载顺序_linux驱动加载顺序-优快云博客
https://blog.youkuaiyun.com/u014100559/article/details/126376616 -
linux设备驱动——bus、device、driver加载顺序与匹配流程_驱动设备匹配过程-优快云博客
https://blog.youkuaiyun.com/heli200482128/article/details/126974048
— -
学习笔记——《LINUX设备驱动程序(第三版)》Linux设备模型:内核添加、删除设备、驱动程序-优快云博客
https://blog.youkuaiyun.com/heli200482128/article/details/126956166 -
内核对设备树的处理(四)__device_node转换为platform_device_必须含有simple-bus-优快云博客
https://blog.youkuaiyun.com/huanting_123/article/details/90342535
1844

被折叠的 条评论
为什么被折叠?



