标题的三个宏对应了内置模块的初始化顺序。
一般情况下,驱动使用device_initcall
或者module_init
。而early_initcall
一般用更早,通常是驱动初始化前的一段时间,由初始化硬件子系统的部分所使用 。
在内核代码init/main.c,首先会有少量用于初始化特定架构的代码,这部分代码在arch/<arch>boot和arch/<arch>kernel中,初始化架构结束后,start_kernel
函数就会被调用,然后文件内的另一个函数do_basic_setup
就会被调用。
/*
* Ok, the machine is now initialized. None of the devices
* have been touched yet, but the CPU subsystem is up and
* running, and memory and process management works.
*
* Now we can finally start doing some real work..
*/
static void __init do_basic_setup(void)
{
cpuset_init_smp();
usermodehelper_init();
shmem_init();
driver_init();
init_irq_proc();
do_ctors();
usermodehelper_enable();
do_initcalls();
}
这个函数是以do_initcall()
为终结,我们将视线往上移动一点,
static initcall_t *initcall_levels[] __initdata = {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};
/* Keep these in sync with initcalls in include/linux/init.h */
static char *initcall_level_names[] __initdata = {
"early",
"core",
"postcore",
"arch",
"subsys",
"fs",
"device",
"late",
};
static void __init do_initcall_level(int level)
{
extern const struct kernel_param __start___param[], __stop___param[];
initcall_t *fn;
strcpy(static_command_line, saved_command_line);
parse_args(initcall_level_names[level],
static_command_line, __start___param,
__stop___param - __start___param,
level, level,
&repair_env_string);
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);
}
static void __init do_initcalls(void)
{
int level;
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level);
}
可以看见initcall_level_names
的名称和其对应的索引:early是0, core是1等等。这其中每一个__initcall*_start
都指向一个会被挨个调用的函数指针数组,这些函数指针实际上就是模块和内置的初始化函数,即使用module_init
和early_initcall
等指定的函数。
那么是什么决定哪一个函数调用哪一个__initcall*_start
数组呢?实际上是链接器使用module_init
和*_initcall
宏进行的。对于内置模块,这些宏将函数指针分配和给定的ELF段。
Example with module_init
想象一个内置模块已经被在.config中被配置为了y,module_init
会像include/linux/init.h中描述的那样被扩展
#define module_init(x) __initcall(x);
往下走
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn) __define_initcall(fn, 6)
我们发现module_init(my_func)就意味
__define_initcall(my_func,6)```, 也就是
#define __define_initcall(fn, id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn
所以,我们最终得到的是
static initcall_t __initcall_my_func6 __used
__attribute__((__section__(".initcall6.init"))) = my_func;
看起来很多GCC的玩意,但是实际上最终效果就是创建了一个新的符号__initcall_my_func6
,这个符号会放在名为.initcall6_init
的ELF段中。然后我们还看到,指针指向了函数my_func
。添加所有函数到这个这个段中最终创建了完整的函数指针数组,他们都存放于.initcall6.init
ELF段中。
Initialization example
代码中有这么一段
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);
我们用level 6为例,所有内置模块使用module_init
初始化。代码从__initcall6_start
开始,它的值是在.initcall6.init
段中第一个注册的函数指针地址,以__initcall7_start
为中终点,每次都以*fn的大小进行累加,类型为initcall_t
或者void*
,大小是32bit或者64bit,这个是由架构决定的。
do_one_initcall
会调用当前条目所指向的函数。
在一个特定的初始化段中,决定为什么一个初始化函数会比另外一个函数先调用的是在Makefile文件中,因为链接器会一个接一个的在各自的ELF init.section中链接__initcall__*
符号。
目前内核使用的就是这样的方案,如下
# GPIO must come after pinctrl as gpios may need to mux pins etc
obj-y += pinctrl/
obj-y += gpio/