首页 > 系统服务 > 详细

Linux内核源码阅读记录一之分析存储在不同段中的函数调用过程

时间:2019-08-10 22:47:16      阅读:97      评论:0      收藏:0      [点我收藏+]

在写驱动的过程中,对于入口函数与出口函数我们会用一句话来修饰他们: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 %s\n", 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的定义,它被定义在include\linux\init.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循环而调用到在其他地方所定义的段。

Linux内核源码阅读记录一之分析存储在不同段中的函数调用过程

原文:https://www.cnblogs.com/andyfly/p/11332997.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!