linux内核各个子系统和内置模块都有自己的初始化函数,比如init_tracepoints,但是看不到这个函数是如何被调用的,只有__initcall(init_tracepoints)这样一条语句。这里的__initcall被宏定义为device_initcall,类似还有early_initcallpure_initcallcore_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_tinitcall_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
/* initcalls are now grouped by functionality into separate
* subsections. Ordering inside the subsections is determined
* by link order.
* For backwards compatibility, initcall() puts the call in
* the device init subsection.
*
* The `id' arg to __define_initcall() is needed so that multiple initcalls
* can point at the same handler without causing duplicate-symbol build errors.
*/

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

/*
* Early initcalls run before initializing SMP.
*
* Only for built-in code, not modules.
*/
#define early_initcall(fn) __define_initcall(fn, early)

/*
* A "pure" initcall has no dependencies on anything else, and purely
* initializes variables that couldn't be statically initialized.
*
* This only exists for built-in code, not for modules.
* Keep main.c:initcall_level_names[] in sync.
*/
#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)

#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;
}