驱动加载流程
模块化加载设计,在增加或减少模块时,不需要手动修改主流程,每个模块都有独立的入口,符合开闭原则.
驱动加载中常用的入口函数module_init() 宏如下,从这里分析内核驱动加载顺序,将入口函数分为0-7个优先顺序,并将其编译进init.text 段.主要涉及以下文件。
- init/main.c
- System.map
- include/linux/init.h
- include/linux/module.h
- include/asm-generic/vmlinux.lds.h
1
2
3
4
5
6int dummy_init(void)
{
// ... 省略
return 0;
}
module_init(dummy_init);kenrel 启动 init/main.c 中,start_kernel() 调用do_basic_setup()完成init call 调用。
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// inlcude/linux/init.h
typedef int (*initcall_t)(void);
typedef initcall_t initcall_entry_t;
//init/main.c
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 void __init do_basic_setup(void)
{
// ... 省略
do_initcalls();
}
static void __init do_initcalls(void)
{
// ... 省略
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) {
do_initcall_level(level, command_line);
}
// ... 省略
}
static void __init do_initcall_level(int level, char *command_line)
{
// ... 省略
initcall_entry_t *fn;
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(initcall_from_entry(fn));
}
int __init_or_module do_one_initcall(initcall_t fn)
{
// ...省略
do_trace_initcall_start(fn);
ret = fn();
do_trace_initcall_finish(fn, ret);
// ... 省略
}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// include/asm-generic/vmlinux.lds.h
#define INIT_CALLS_LEVEL(level) \
__initcall##level##_start = .; \
KEEP(*(.initcall##level##.init)) \
KEEP(*(.initcall##level##s.init)) \
#define INIT_CALLS \
__initcall_start = .; \
KEEP(*(.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) \
__initcall_end = .;
// arch/arm/kernel/vmlinux.lds.S
... 省略
.init.pv_table : {
__pv_table_begin = .;
*(.pv_table)
__pv_table_end = .;
}
INIT_DATA_SECTION(16)
.exit.data : {
ARM_EXIT_KEEP(EXIT_DATA)
}
... 省略
// include/linux/module.h
#define ___define_initcall(fn, id, __sec) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(#__sec ".init"))) = fn;
#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
#define core_initcall(fn) __define_initcall(fn, 1)
#define postcore_initcall(fn) __define_initcall(fn, 2)
#define arch_initcall(fn) __define_initcall(fn, 3)
总结
分析System.map 文件,initcall_levels[ ] 数组存放每个等级的入口地址,简化为二维数组更容易理解,代码循环执行每一个入口函数
1 | typedef int (*initcall_t)(void); |
1 | # System.map |