zoukankan      html  css  js  c++  java
  • Linux 链接脚本分析

    作者:答疑助手lizuobin

    原文:

    https://blog.csdn.net/lizuobin2/article/details/51779064

    在前面学习的过程中,看代码时遇到 arch_initcall(xxx) 等函数总是愣的,对于最基础的module_init(xxx) 也只是拿来用用,不知道幕后的原理,知道 MACHINE_START 是创建了一个machine_desc ,却不知道machine_desc->map_io 等函数何时被调用。

    这篇文章,就来搞定它们,再遇到它们时,拒绝懵比!

    友情提示:全文9100字,涉及代码较多,请建立source insight工程跟着linux源码阅读。建议收藏哦。

    首先,来看链接脚本的缩略版:

    SECTIONS
    {
        .init : {            /* Init code and data        */
                INIT_TEXT
            _einittext = .;
            __proc_info_begin = .;
                *(.proc.info.init)
            __proc_info_end = .;
            __arch_info_begin = .;
                *(.arch.info.init)
            __arch_info_end = .;
            __tagtable_begin = .;
                *(.taglist.init)
            __tagtable_end = .;
            . = ALIGN(16);
            __setup_start = .;
                *(.init.setup)
            __setup_end = .;
            __early_begin = .;
                *(.early_param.init)
            __early_end = .;
            __initcall_start = .;
                INITCALLS
            __initcall_end = .;        
    }
    

    内核的文件就是这样组织的,但是具体每个段放的什么东西,怎么放进去,何时取出来我们不知道,下面一个一个分析。

    1、*(.proc.info.init) 段

    内核中,定义了若干个proc_info_list 结构,它的结构原形在include/asm-arm/procinfo.h 中,表示它所支持的CPU。

    struct proc_info_list {
      unsigned int    cpu_val;
      unsigned int    cpu_mask;
      unsigned long    __cpu_mm_mmu_flags;  /* used by head.S */
      unsigned long    __cpu_io_mmu_flags;  /* used by head.S */
      unsigned long    __cpu_flush;    /* used by head.S */
      const char    *arch_name;
      const char    *elf_name;
      unsigned int    elf_hwcap;
      const char    *cpu_name;
      struct processor  *proc;
      struct cpu_tlb_fns  *tlb;
      struct cpu_user_fns  *user;
      struct cpu_cache_fns  *cache;
    };
    

    对于ARM架构的CPU,这些结构体的源码在arch/arm/mm/目录下,比如 proc-arm920.S , proc_info_list 结构被定义在 ".proc.info.init" 段,在连接内核时,这些结构体被组织在一起,开始地址 __proc_info_begin ,结束地址 _proc_info_end 。

    .section ".proc.info.init", #alloc, #execinstr
      .type  __arm920_proc_info,#object
    __arm920_proc_info:
      .long  0x41009200
      .long  0xff00fff0
      .long   PMD_TYPE_SECT | 
        PMD_SECT_BUFFERABLE | 
        PMD_SECT_CACHEABLE | 
        PMD_BIT4 | 
        PMD_SECT_AP_WRITE | 
        PMD_SECT_AP_READ
      .long   PMD_TYPE_SECT | 
        PMD_BIT4 | 
        PMD_SECT_AP_WRITE | 
        PMD_SECT_AP_READ
      b  __arm920_setup
      .long  cpu_arch_name
      .long  cpu_elf_name
      .long  HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
      .long  cpu_arm920_name
      .long  arm920_processor_functions
      .long  v4wbi_tlb_fns
      .long  v4wb_user_fns
    #ifndef CONFIG_CPU_DCACHE_WRITETHROUGH
      .long  arm920_cache_fns
    #else
      .long  v4wt_cache_fns
    #endif
      .size  __arm920_proc_info, . - __arm920_proc_info
    
    

    在内核启动时,首先读取出芯片ID,然后就在__proc_info_begin 和 _proc_info_end 取出 proc_info_list ,看内核是否支持这个CPU。

    2、*(.arch.info.init) 段

    *(.arch.info.init) 段,存放的是内核所支持的单板信息如机器ID、其实IO物理地址等,它由 MACHINE_START、MACHINE_END 定义。

    #define MACHINE_START(_type,_name)                         
    static const struct machine_desc __mach_desc_##_type      
    __used                                                    
    __attribute__((__section__(".arch.info.init")))= {        
            .nr             = MACH_TYPE_##_type,              
            .name           = _name,  
       
    #define MACHINE_END                                        
    };
    
    

    举个例子:

    MACHINE_START(HALIBUT,"Halibut Board (QCT SURF7200A)")  
      .boot_params      = 0x10000100,  
      .map_io           = halibut_map_io,  
      .init_irq         = halibut_init_irq,  
      .init_machine     = halibut_init,  
      .timer            = &msm_timer,  
    MACHINE_END
    

    将宏展开:

    struct machine_desc __mach_desc_HALIBUT{  
    __used                                                            
    __attribute__((__section__(".arch.info.init")))= {  
      .nr               = MACH_TYPE_HALIBUT,                
      .name             = "HalibutBoard (QCT SURF7200A)",  
      .boot_params      = 0x10000100,  
      .map_io           = halibut_map_io,  
      .init_irq         = halibut_init_irq,  
      .init_machine     = halibut_init,  
      .timer            = &msm_timer,  
    };
    

    内核连接时,所有的 machine_desc 结构都会位于 ".arch.info.init" 段,不同的 machine_desc 结构体用于不同的单板,u-boot 调用内核时,会在 r1 寄存器中给出开发板的标记(机器ID),在__lookup_machine_type 函数中,将取出 ".arch.info.init" 段中的每一个 machine_desc 结构,比较 r1 与 machine_desc->nr 判断内核是否支持该单板。

    顺便看一下 map_io 等函数的调用时机:

    start_kernel
      setup_arch(&command_line);
        init_arch_irq = mdesc->init_irq;
        system_timer = mdesc->timer;
        init_machine = mdesc->init_machine;
        paging_init(mdesc)
          devicemaps_init(mdesc);
            mdesc->map_io()
    
    init_IRQ()
       init_arch_irq();
    time_init()
     system_timer->init();
    rest_init();
     kernel_init
       do_basic_setup()
         do_initcalls()
           init_machine()
    
    static int __init customize_machine(void)
    {
      /* customizes platform devices, or adds new ones */
      if (init_machine)
        init_machine();
      return 0;
    }
    arch_initcall(customize_machine);
    

    先后顺序:

    start_kernel -》setup_arch -》 map_io -》 init_irq -》 timer -》 init_machine

    map_io 函数中 会对内核进行分区还有时钟、串口的初始化,移植内核时需注意!(传入的机器ID不同,调用的初始化函数自然不同)

    3、*(.taglist.init)

    *(.taglist.init) 段存放的是 uboot 传递到内核的 tag 的处理函数。在 uboot 中,定义了一个 tag 结构体,里面存放要传递给内核的信息,Uboot 将 tag 依次排放在和内核约定的地点,如s3c2440是 0x30000100 处,排放顺序是有要求的,必须以 ATAG_CORE 标记的 tag 开头,以 ATAG_NONE 为标记的 tag 结尾。

    struct tag {
    
      struct tag_header {
        u32 size;       /* 表示tag数据结构的联合u实质存放的数据的大小*/
        u32 tag;        /* 表示标记的类型 */
      }hdr;
    
      union {
        struct tag_core           core;
        struct tag_mem32      mem;
        struct tag_videotext   videotext;
        struct tag_ramdisk     ramdisk;
        struct tag_initrd  initrd;
        struct tag_serialnr       serialnr;
        struct tag_revision      revision;
        struct tag_videolfb     videolfb;
        struct tag_cmdline     cmdline;
        /*
        * Acorn specific
        */
        struct tag_acorn  acorn;
        /*
        * DC21285 specific
        */
        struct tag_memclk      memclk;
      } u;
    };
    
    
    
    setup_start_tag (bd);    /*设置ATAG_CORE标志*/
    setup_memory_tags (bd);                     /*设置内存标记*/
    setup_commandline_tag (bd, commandline);  /*设置命令行标记*/
    ...
    setup_end_tag (bd);      /*设置ATAG_NONE标志 */
    

    在内核中,使用 __tagtable 来将处理 tag 的函数放到 *(.taglist.init) 段

    archarmkernelsetup.c

    __tagtable(ATAG_CORE, parse_tag_core);
    __tagtable(ATAG_MEM, parse_tag_mem32);
    __tagtable(ATAG_VIDEOTEXT, parse_tag_videotext);
    __tagtable(ATAG_CMDLINE, parse_tag_cmdline);
    __tagtable(ATAG_REVISION, parse_tag_revision);
    
    

    宏定义在 setup.h (includeasm-arm)

    struct tagtable {
      __u32 tag;
      int (*parse)(const struct tag *);
    };
    
    
    #define __tag __used __attribute__((__section__(".taglist.init")))
    #define __tagtable(tag, fn) 
    static struct tagtable __tagtable_##fn __tag = { tag, fn }
    
    

    以 __tagtable(ATAG_CORE, parse_tag_core)为例

    
    static struct tagtable __tagtable_parse_tag_core __used __attribute__((__section__(".taglist.init"))) = {
      ATAG_CORE,
      parse_tag_core
    }
    

    在内核启动过程中,会使用 parse_tags 来处理 tag ,它最终会调用到 parse_tag ,取出 __tagtable_begin 和 __tagtable_end 之间的每一个 tagtable ,比较它们的类型,如果相同则调用 tagtable 里的处理函数来处理这个tag 。

    if (mdesc->boot_params)
        tags = phys_to_virt(mdesc->boot_params);
      parse_tags(tags);
    
    static void __init parse_tags(const struct tag *t)
    {
      for (; t->hdr.size; t = tag_next(t))
        if (!parse_tag(t))
          printk(KERN_WARNING
            "Ignoring unrecognised tag 0x%08x
    ",
            t->hdr.tag);
    }
    
    static int __init parse_tag(const struct tag *tag)
    {
      extern struct tagtable __tagtable_begin, __tagtable_end;
      struct tagtable *t;
    
    
      for (t = &__tagtable_begin; t < &__tagtable_end; t++)
        if (tag->hdr.tag == t->tag) {
          t->parse(tag);
          break;
        }
    
      return t < &__tagtable_end;
    }
    

    4、*(.init.setup)

    
    #define __setup(str, fn)          
      __setup_param(str, fn, fn, 0)
    
    #define early_param(str, fn)                    /
        __setup_param(str, fn, fn, 1)
    
    struct obs_kernel_param {
      const char *str;
      int (*setup_func)(char *);
      int early;
    };
    
    
    #define __initdata  __attribute__ ((__section__ (".init.data")))
    #define __setup_param(str, unique_id, fn, early)      
      static char __setup_str_##unique_id[] __initdata = str;  
      static struct obs_kernel_param __setup_##unique_id  
        __attribute_used__        
        __attribute__((__section__(".init.setup")))  
        __attribute__((aligned((sizeof(long)))))  
        = { __setup_str_##unique_id, fn, early }
    

    举个例子:

    __setup("init=", init_setup);
    __setup_param("init=", init_setup, init_setup, 0)
    
    static char __setup_str_init_setup[] __attribute__ ((__section__ (".init.data"))) = "init=";
    static struct obs_kernel_param __setup_init_setup  
      __attribute_used__        
      __attribute__((__section__(".init.setup")))  
      __attribute__((aligned((sizeof(long)))))  
      = { __setup_str_init_setup, init_setup, 0 }
    
    

    parse_early_param 处理 early_param 定义的参数,parse_args 处理 __setup 定义的参数

    5、*(.early_param.init)

    struct early_params {
      const char *arg;
      void (*fn)(char **p);
    };
    
    #define __early_param(name,fn)          
    static struct early_params __early_##fn __used      
    __attribute__((__section__(".early_param.init"))) = { name, fn }
    

    例如:

    __early_param("initrd=", early_initrd);
    static struct early_params __early_early_initrd __used  __attribute__((__section__(".early_param.init"))) = 
    {
      "initrd=", 
      early_initrd 
    }
    

    parse_cmdline 处理 __early_param 定义的参数

    6、INITCALLS

    #define INITCALLS                                      
      *(.initcallearly.init)                                
      VMLINUX_SYMBOL(__early_initcall_end) = .;             
      *(.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)                           
    
    
    
    typedef int (*initcall_t)(void);
    #define __define_initcall(level,fn,id) 
      static initcall_t __initcall_##fn##id __attribute_used__ 
      __attribute__((__section__(".initcall" level ".init"))) = fn
    
    
    #define pure_initcall(fn)              __define_initcall("0",fn,0)
    #define core_initcall(fn)              __define_initcall("1",fn,1)
    #define core_initcall_sync(fn)          __define_initcall("1s",fn,1s)
    #define postcore_initcall(fn)           __define_initcall("2",fn,2)
    #define postcore_initcall_sync(fn)     __define_initcall("2s",fn,2s)
    #define arch_initcall(fn)             __define_initcall("3",fn,3)
    #define arch_initcall_sync(fn)         __define_initcall("3s",fn,3s)
    #define subsys_initcall(fn)             __define_initcall("4",fn,4)
    #define subsys_initcall_sync(fn)      __define_initcall("4s",fn,4s)
    #define fs_initcall(fn)                 __define_initcall("5",fn,5)
    #define fs_initcall_sync(fn)            __define_initcall("5s",fn,5s)
    #define rootfs_initcall(fn)            __define_initcall("rootfs",fn,rootfs)
    #define device_initcall(fn)             __define_initcall("6",fn,6)
    #define device_initcall_sync(fn)      __define_initcall("6s",fn,6s)
    #define late_initcall(fn)             __define_initcall("7",fn,7)
    #define late_initcall_sync(fn)          __define_initcall("7s",fn,7s)
    

    以 device_initcall(mac_hid_init) 为例:

    __define_initcall("6",fn,6)
    static initcall_t __initcall_mac_hid_init6 __attribute_used__ __attribute__((__section__(".initcall" 6 ".init"))) 
     = mac_hid_init
    
    

    再看我们熟悉的 module_init(xxx_init)

    #define module_init(x)     __initcall(x);
    #define __initcall(fn) device_initcall(fn)
    

    看来 module_init(xxx_init) 在 “.initcall6.init” 段 创建函数指针 指向 xxx_init

    那么 INITCALLS 里的函数在哪里调用?

    start_kernel
    
        rest_init()
    
            kernel_init
    
                do_basic_setup()
    
                    do_initcalls()
    
    static void __init do_initcalls(void)
    {
        initcall_t *call;
        
      for (call = __early_initcall_end; call < __initcall_end; call++)
        do_one_initcall(*call);
    
      /* Make sure there is no pending stuff from the initcall sequence */
        flush_scheduled_work();
    }
    
    

    也就是说 INITCALLS 段里的东西会在内核启动时按顺序逐个调用,以后遇到 xxx_initcall 就不会懵B了。

                                               --
  • 相关阅读:
    网上订餐系统后台代码bug记录与解决
    vector二维数组
    力扣-树-练习题(一)
    优先队列

    并查集
    C++进制转换函数
    平衡二叉树(AVL树)定义与基本操作
    二叉查找树练习题
    树的遍历
  • 原文地址:https://www.cnblogs.com/weidongshan/p/11125104.html
Copyright © 2011-2022 走看看