注:本文为 “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)
{