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


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

  • 相关阅读:
    js、php 判断用户终端 、浏览器类型
    网站安装 https 证书
    PHP请求远程地址设置超时时间
    js实现复制文本内容到剪切板
    微信公众号授权获取用户信息
    生成微信公众号二维码(用户扫码关注公众号)
    域名dns 查询
    服务端 安装配置 svn
    自动生成文档
    python tkinter 布局
  • 原文地址:https://www.cnblogs.com/andyfly/p/11332997.html
Copyright © 2020-2023  润新知