linux内核各个子系统和内置模块都有自己的初始化函数,比如init_tracepoints
,但是看不到这个函数是如何被调用的,只有__initcall(init_tracepoints)
这样一条语句。这里的__initcall
被宏定义为device_initcall
,类似还有early_initcall
、pure_initcall
、core_initcall
等等。这些就是initcall机制,使得各个初始化函数可以按一定先后顺序被调用执行。
参考内核版本:3.10.0-862.el7.x86_64
定义 在文件include/linux/init.h中可以看到各种initcall被宏定义为__define_initcall
,__define_initcall
接收两个参数,fn
是回调函数,id
有两个作用,一个是标示段及其优先级,另一个是使不同优先级的段中可以存在同一个回调函数而不出现符号冲突。
以__initcall(init_tracepoints)
为例,会被扩展为device_initcall(init_tracepoints)
,再次扩展为__define_initcall(init_tracepoints, 6)
,
这会定义出一个变量__initcall_init_tracepoints6
类型为static initcall_t
,initcall_t
的类型是typedef int (*initcall_t)(void)
,也就是回调函数的类型。
被放置于段.initcall6.init中。
被赋值为init_tracepoints
,也就是该回调函数的地址。
__used
用于使编译器不会优化掉该变量,同时使编译器不会警告该变量未被直接引用。
include/linux/init.h 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #define __define_initcall(fn, id) \ static initcall_t __initcall_##fn##id __used \ __attribute__((__section__(".initcall" #id ".init" ))) = fn #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, 1 s) #define postcore_initcall(fn) __define_initcall(fn, 2 ) #define postcore_initcall_sync(fn) __define_initcall(fn, 2 s) #define arch_initcall(fn) __define_initcall(fn, 3 ) #define arch_initcall_sync(fn) __define_initcall(fn, 3 s) #define subsys_initcall(fn) __define_initcall(fn, 4 ) #define subsys_initcall_sync(fn) __define_initcall(fn, 4 s) #define fs_initcall(fn) __define_initcall(fn, 5 ) #define fs_initcall_sync(fn) __define_initcall(fn, 5 s) #define rootfs_initcall(fn) __define_initcall(fn, rootfs) #define device_initcall(fn) __define_initcall(fn, 6 ) #define device_initcall_sync(fn) __define_initcall(fn, 6 s) #define late_initcall(fn) __define_initcall(fn, 7 ) #define late_initcall_sync(fn) __define_initcall(fn, 7 s) #define __initcall(fn) device_initcall(fn) #define __exitcall(fn) \ static exitcall_t __exitcall_##fn __exit_call = fn #define console_initcall(fn) \ static initcall_t __initcall_##fn \ __used __section(.con_initcall.init) = fn #define security_initcall(fn) \ static initcall_t __initcall_##fn \ __used __section(.security_initcall.init) = fn
链接 在链接器脚本头文件include/asm-generic/vmlinux.lds.h中可以看到对这些段的使用
最终的效果是,在内核的段.init.data中
定义了符号,函数指针数组__initcall_start[]
,存储了一组函数指针
所有目标文件.initcallearly.init段中的函数指针
通过宏INIT_CALLS_LEVEL(0)
,定义了符号,函数指针数组__initcall0_start[]
,按顺序存储了两组函数指针
所有目标文件.initcall0.init段中的函数指针
所有目标文件.initcall0s.init段中的函数指针
与上面的宏INIT_CALLS_LEVEL(0)
同样方式,分别处理1、2、3、4、5、rootfs、6、7这些initcall等级。
定义了符号,函数指针数组__initcall_end[]
,这个地址就是上面这些指针的末尾地址。
定义了符号,函数指针数组__con_initcall_start[]
,存储了一组函数指针
所有目标文件.con_initcall.init段中的函数指针
定义了符号,函数指针数组__con_initcall_end[]
,这个地址就是上面这组函数指针的末尾地址。
定义了符号,函数指针数组__security_initcall_start[]
,存储了一组函数地址
所有目标文件.security_initcall.init段中的函数指针
定义了符号,函数指针数组__security_initcall_end[]
,这个地址就是上面这组函数指针的末尾地址。
INIT_DATA_SECTION
宏将上面这些数据都放置到段.init.data,这个宏会在链接器脚本x86/kernel/vmlinux.lds.S中被调用
include/asm-generic/vmlinux.lds.h 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #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) = .; #define INIT_DATA_SECTION(initsetup_align) \ .init.data : AT(ADDR(.init.data) - LOAD_OFFSET) { \ INIT_DATA \ INIT_SETUP(initsetup_align) \ INIT_CALLS \ CON_INITCALL \ SECURITY_INITCALL \ INIT_RAM_FS \ }
运行 按照时间顺序,这里只关注initcall相关,不记录其他内容
start_kernel
<- x86_64_start_reservations
<- x86_64_start_kernel
console_init
从__con_initcall_start
遍历到__con_initcall_end
,执行每个回调函数。
security_init
do_security_initcalls
从__security_initcall_start
遍历到__security_initcall_end
,执行每个回调函数。
rest_init
,创建出1号进程,执行kernel_init
kernel_init_freeable
先等待kthreadd
进程创建,也就是其task_struct
指针变量kthreadd_task
被赋值完成。
do_pre_smp_initcalls
从__initcall_start
遍历到__initcall0_start
,对每个回调函数指针,依次调用do_one_initcall
执行初始化函数
do_basic_setup
do_initcalls
遍历initcall_levels
数组,按顺序依次调用do_initcall_level
从一个level的函数指针数组起始位置,对每个回调函数指针,依次调用do_one_initcall
执行初始化函数,直到下一个数组的起始位置
可以看到这个数组里缺少了__initcallrootfs_start
。由于其实际处于5和6之间,因此也会在5之后6之前被执行到。
那些带有sync后缀的initcall宏修饰的回调函数,目的是等待本等级的所有回调函数执行完成后,才会被调用,例如执行一些清理操作。 比如late_initcall_sync
修饰的函数,会在所有late_initcall
修饰的函数执行完成后被调用。这是由INIT_CALLS_LEVEL
宏的实现决定的。
init/main.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 extern initcall_t __initcall_start[];extern initcall_t __initcall0_start[];extern initcall_t __initcall1_start[];extern initcall_t __initcall2_start[];extern initcall_t __initcall3_start[];extern initcall_t __initcall4_start[];extern initcall_t __initcall5_start[];extern initcall_t __initcall6_start[];extern initcall_t __initcall7_start[];extern initcall_t __initcall_end[];static initcall_t *initcall_levels[] __initdata = { __initcall0_start, __initcall1_start, __initcall2_start, __initcall3_start, __initcall4_start, __initcall5_start, __initcall6_start, __initcall7_start, __initcall_end, };
init/main.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 int __init_or_module do_one_initcall (initcall_t fn) { int count = preempt_count(); int ret; if (initcall_blacklisted(fn)) return -EPERM; if (initcall_debug) ret = do_one_initcall_debug(fn); else ret = fn(); msgbuf[0 ] = 0 ; if (preempt_count() != count) { sprintf (msgbuf, "preemption imbalance " ); preempt_count() = count; } if (irqs_disabled()) { strlcat(msgbuf, "disabled interrupts " , sizeof (msgbuf)); local_irq_enable(); } WARN(msgbuf[0 ], "initcall %pF returned with %s\n" , fn, msgbuf); return ret; }