首页 > 系统相关 >Linux中的initcall以及module_init

Linux中的initcall以及module_init

时间:2022-10-29 19:32:53浏览次数:47  
标签:__ level module init initcall fn define


背景

Linux内核是如何确保各子系统按序加载的?

initcall调用流程

start_kernel 
-->arch_call_rest_init
-->rest_init
-->kthread_create(kernel_init)
-->kernel_init_freeable
-->do_basic_setup
-->driver_init //init driver model
-->devices_init
-->of_core_init
-->hypervisor_init
-->platform_bus_init
-->container_dev_init
-->do_initcalls //initcall

通过以上的调用栈可以找到最终的实际的 initcall 数据段处理函数 ​​do_initcalls​​,只需要解析这些数据段即可按顺序逐句调入到个注册的函数内。

在生成vmlinux的链接阶段,编译器为initcall创建了特定的section,每一类initcall对应一组section,然后遍历执行initcall section中的initcalls。

initcall_levels结构中规定了启动的顺序。在实际执行时,内核必须知道xxx_initcall section所在的位置,而在include/asm-generic/vmlinux.lds.h中将__initcallX_start和**.initcall*.init**链接到了一起,这样的话,do_initcalls()遍历不同ID的initcall时,initcallX_start便可以找到data section中对应的.initcall entry,然后循环遍历里面的各个initcalls。

//include/asm-generic/vmlinux.lds.h
#define INIT_CALLS_LEVEL(level) \
VMLINUX_SYMBOL(__initcall##level##_start) = .; \
*(.initcall##level##.init) \
*(.initcall##level##s.init) \

#define INIT_CALLS \
VMLINUX_SYMBOL(__initcall_start) = .; \
*(.initcallearly.init) \
INIT_CALLS_LEVEL(0) \
INIT_CALLS_LEVEL(1) \
INIT_CALLS_LEVEL(2) \
INIT_CALLS_LEVEL(3) \
INIT_CALLS_LEVEL(4) \
INIT_CALLS_LEVEL(5) \
INIT_CALLS_LEVEL(rootfs) \
INIT_CALLS_LEVEL(6) \
INIT_CALLS_LEVEL(7) \
VMLINUX_SYMBOL(__initcall_end) = .;

static initcall_entry_t *initcall_levels[] __initdata = {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};

static const char *initcall_level_names[] __initdata = {
"pure",
"core",
"postcore",
"arch",
"subsys",
"fs",
"device",
"late",
};

static void __init do_initcalls(void)
{
int level;

for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level);
}

static void __init do_initcall_level(int level)
{
initcall_entry_t *fn;

strcpy(initcall_command_line, saved_command_line);
parse_args(initcall_level_names[level],
┊ initcall_command_line, __start___param,
┊ __stop___param - __start___param,
┊ level, level,
┊ NULL, &repair_env_string);

trace_initcall_level(initcall_level_names[level]);
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(initcall_from_entry(fn));
}

这个init段位于lds汇编文件中,在内核编译阶段就已经确立好了,在不同的驱动/子系统的源码结构下通过不同的 __initcall 定义的函数入口都会被 gcc 插入到不同名称的数据段之下。

.init.data : {
INIT_DATA
INIT_SETUP(16)
INIT_CALLS
CON_INITCALL
INIT_RAM_FS
*(.init.rodata.* .init.bss) /* from the EFI stub */
}

各个函数如下,于是实际的数据段映射关系是:

pure_initcall(fn)->.initcall0.init ;

core_initcall(fn)->.initcall1.init 。

我们可以看到有各种类型的initcall,从名字大概可以猜测得出每一个类型的 initcall 所起的作用,每一种类型都是通过 ​​__define_initcall​​ 做的定义,唯一的区别就是第二个参数不一样,这个参数一定代表着等级,他们决定着内核在启动过程中启动顺序,这就给我们各个功能部件提供了一个启动的关系表,让被依赖的子系统首先启动,这样可以确保 Linux 的所有子系统正确完成初始化。

#define __define_initcall(fn, id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn; \
LTO_REFERENCE_INITCALL(__initcall_##fn##id)

#define early_initcall(fn) __define_initcall(fn, early)

#define pure_initcall(fn) __define_initcall(fn, 0)

#define core_initcall(fn) __define_initcall(fn, 1)
#define core_initcall_sync(fn) __define_initcall(fn, 1s)
#define postcore_initcall(fn) __define_initcall(fn, 2)
#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
#define arch_initcall(fn) __define_initcall(fn, 3)
#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
#define subsys_initcall(fn) __define_initcall(fn, 4)
#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
#define fs_initcall(fn) __define_initcall(fn, 5)
#define fs_initcall_sync(fn) __define_initcall(fn, 5s)
#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
#define device_initcall(fn) __define_initcall(fn, 6)
#define device_initcall_sync(fn) __define_initcall(fn, 6s)
#define late_initcall(fn) __define_initcall(fn, 7)
#define late_initcall_sync(fn) __define_initcall(fn, 7s)

内核驱动中的module_init函数

我们在驱动中经常会使用到module_init(xxx_init)来设置这个驱动的入口函数,module_init函数的定义如下:

#define module_init(x)  __initcall(x);

#define __initcall(fn) device_initcall(fn)

这里我们说的module_init是指驱动已经打包进了内核。device_initcall是众多宏定义中的一个,因此所有的设备驱动排在第6的加载顺序。

insmod操作

对于insmod加载驱动操作,它的调用栈如下。

Linux中的initcall以及module_init_1024程序员节

do_init_module

对于最后的do_init_module函数,是调用do_one_initcall(mod->init)函数来实现的。

do_one_initcall用__init_or_module(___init)进行了修饰。__init的定义如下:

//include/linux/init.h
#define __init __section(".init.text") __cold __latent_entropy __noinitretpoline

也就是说对于do_one_initcall函数,在预编译的时候也为其空出了一块数据段".init.text"。而我们在定义驱动init函数的时候也得使用__init定义这个入口函数对吧。对于exit函数也是一样的,预留出了.exit.data段。


Linux中的initcall以及module_init_#define_02


标签:__,level,module,init,initcall,fn,define
From: https://blog.51cto.com/qmiller/5806474

相关文章