在写驱动的过程中,对于入口函数与出口函数我们会用一句话来修饰他们:module_init与module_exit,那会什么经过修饰后,内核就能狗调用我们编写的入口函数与出口函数呢?下面就来分析内核调用module_init的过程(这里暂时分析编译进内核的模块,不涉及动态加载的模块),以这个过程为例子来了解内核对于不同段的函数的调用过程。
下面从内核的start_kernel函数开始分析,下面是调用过程:
start_kernel
rest_init
kernel_init
do_basic_setup
do_initcalls()
直接看到do_initcalls函数,看到第6行的for循环,它从__initcall_start开始到__initcall_end结束,调用在这区间内的函数,那么这些函数在哪里定义的呢?
1 static void __init do_initcalls(void)
2 {
3 initcall_t *call;
4 int count = preempt_count();
5
6 for (call = __initcall_start; call < __initcall_end; call++) {/* 调用__initcall_start到__initcall_end内的函数*/
7 ktime_t t0, t1, delta;
8 char *msg = NULL;
9 char msgbuf[40];
10 int result;
11
12 if (initcall_debug) {
13 printk('Calling initcall 0x%p', *call);
14 print_fn_descriptor_symbol(': %s()',
15 (unsigned long) *call);
16 printk('n');
17 t0 = ktime_get();
18 }
19
20 result = (*call)();
21
22 if (initcall_debug) {
23 t1 = ktime_get();
24 delta = ktime_sub(t1, t0);
25
26 printk('initcall 0x%p', *call);
27 print_fn_descriptor_symbol(': %s()',
28 (unsigned long) *call);
29 printk(' returned %d.n', result);
30
31 printk('initcall 0x%p ran for %Ld msecs: ',
32 *call, (unsigned long long)delta.tv64 >> 20);
33 print_fn_descriptor_symbol('%s()n',
34 (unsigned long) *call);
35 }
36
37 if (result && result != -ENODEV && initcall_debug) {
38 sprintf(msgbuf, 'error code %d', result);
39 msg = msgbuf;
40 }
41 if (preempt_count() != count) {
42 msg = 'preemption imbalance';
43 preempt_count() = count;
44 }
45 if (irqs_disabled()) {
46 msg = 'disabled interrupts';
47 local_irq_enable();
48 }
49 if (msg) {
50 printk(KERN_WARNING 'initcall at 0x%p', *call);
51 print_fn_descriptor_symbol(': %s()',
52 (unsigned long) *call);
53 printk(': returned with %sn', msg);
54 }
55 }
56
57 /* Make sure there is no pending stuff from the initcall sequence */
58 flush_scheduled_work();
59 }
继续往下看,我们搜索整个内核源码,发现找不到__initcall_start与__initcall_end的定义,其实这两个变量被定义在arch/arm/kernel/vmlinux.lds中,它是在内核编译的时候生成的,是整个内核源码的链接脚本,我们把关键部分代码抽出来。可以看到在__initcall_start与__initcall_end之间又被分成了许多段:从*(.initcall0.init) ~*(.initcall7s.init)。
1 .init : { /* Init code and data */
2 *(.init.text)
3 _einittext = .;
4 __proc_info_begin = .;
5 *(.proc.info.init)
6 __proc_info_end = .;
7 __arch_info_begin = .;
8 *(.arch.info.init)
9 __arch_info_end = .;
10 __tagtable_begin = .;
11 *(.taglist.init)
12 __tagtable_end = .;
13 . = ALIGN(16);
14 __setup_start = .;
15 *(.init.setup)
16 __setup_end = .;
17 __early_begin = .;
18 *(.early_param.init)
19 __early_end = .;
20 __initcall_start = .;
21 *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) *(.initcall7.init) *(.initcall7s.init)
22 __initcall_end = .;
23 __con_initcall_start = .;
24 *(.con_initcall.init)
25 __con_initcall_end = .;
26 __security_initcall_start = .;
27 *(.security_initcall.init)
28 __security_initcall_end = .;
29
30 . = ALIGN(32);
31 __initramfs_start = .;
32 usr/built-in.o(.init.ramfs)
33 __initramfs_end = .;
34
35 . = ALIGN(4096);
36 __per_cpu_start = .;
37 *(.data.percpu)
38 __per_cpu_end = .;
39
40 __init_begin = _stext;
41 *(.init.data)
42 . = ALIGN(4096);
43 __init_end = .;
44
45 }
分析到这里,我们回过头再继续看module_init的定义,它被定义在includelinuxinit.h中:
194 #define module_init(x) __initcall(x);
135 #define __initcall(fn) device_initcall(fn)
130 #define device_initcall(fn) __define_initcall('6',fn,6)
107 #define __define_initcall(level,fn,id)
108 static initcall_t __initcall_##fn##id __attribute_used__
109 __attribute__((__section__('.initcall' level '.init'))) = fn
76 typedef int (*initcall_t)(void);
根据以上定义,最终把module_init展开可以得到:这句话的意思就是只要调用module_init(x),就把x定义为initcall_t类型的函数,并且这个函数函数名为__initcall_x6,它被存放在.initcall6.init中,而这个段正好位于__initcall_start与__initcall_end之间。所以它被内核调用的时机就是在do_initcalls函数的for循环中。
#define module_init(x) static initcall_t __initcall_x6 __attribute_used__ __attribute__((__section__('.initcall' 6 '.init'))) = x
对于其他的段,也是类似的,内核会在某个地方利用for循环而调用到在其他地方所定义的段。