Linux 链接脚本分析

来源:互联网 发布:保险网络增员话术 编辑:程序博客网 时间:2024/04/30 22:58
  在前面学习的过程中,看代码时遇到 arch_initcall(xxx) 等函数总是处于愣神的状态,对于最基础的 module_init(xxx) 也只是拿来用用,不知道幕后的东西,了解 MACHINE_START 创建了一个 machine_desc ,却不知道 machine_desc->map_io 等函数时何时调用的。

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


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

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. SECTIONS  
  2. {  
  3.     .init : {            /* Init code and data        */  
  4.             INIT_TEXT  
  5.         _einittext = .;  
  6.         __proc_info_begin = .;  
  7.             *(.proc.info.init)  
  8.         __proc_info_end = .;  
  9.         __arch_info_begin = .;  
  10.             *(.arch.info.init)  
  11.         __arch_info_end = .;  
  12.         __tagtable_begin = .;  
  13.             *(.taglist.init)  
  14.         __tagtable_end = .;  
  15.         . = ALIGN(16);  
  16.         __setup_start = .;  
  17.             *(.init.setup)  
  18.         __setup_end = .;  
  19.         __early_begin = .;  
  20.             *(.early_param.init)  
  21.         __early_end = .;  
  22.         __initcall_start = .;  
  23.             INITCALLS  
  24.         __initcall_end = .;          
  25. }  
    内核的文件就是这样组织的,但是具体每个段放的什么东西,怎么放进去,何时取出来我们不知道,下面一个一个分析。


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

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

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. struct proc_info_list {  
  2.     unsigned int        cpu_val;  
  3.     unsigned int        cpu_mask;  
  4.     unsigned long       __cpu_mm_mmu_flags; /* used by head.S */  
  5.     unsigned long       __cpu_io_mmu_flags; /* used by head.S */  
  6.     unsigned long       __cpu_flush;        /* used by head.S */  
  7.     const char      *arch_name;  
  8.     const char      *elf_name;  
  9.     unsigned int        elf_hwcap;  
  10.     const char      *cpu_name;  
  11.     struct processor    *proc;  
  12.     struct cpu_tlb_fns  *tlb;  
  13.     struct cpu_user_fns *user;  
  14.     struct cpu_cache_fns    *cache;  
  15. };  

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

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1.     .section ".proc.info.init", #alloc, #execinstr  
  2.   
  3.     .type   __arm920_proc_info,#object  
  4. __arm920_proc_info:  
  5.     .long   0x41009200  
  6.     .long   0xff00fff0  
  7.     .long   PMD_TYPE_SECT | \  
  8.         PMD_SECT_BUFFERABLE | \  
  9.         PMD_SECT_CACHEABLE | \  
  10.         PMD_BIT4 | \  
  11.         PMD_SECT_AP_WRITE | \  
  12.         PMD_SECT_AP_READ  
  13.     .long   PMD_TYPE_SECT | \  
  14.         PMD_BIT4 | \  
  15.         PMD_SECT_AP_WRITE | \  
  16.         PMD_SECT_AP_READ  
  17.     b   __arm920_setup  
  18.     .long   cpu_arch_name  
  19.     .long   cpu_elf_name  
  20.     .long   HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB  
  21.     .long   cpu_arm920_name  
  22.     .long   arm920_processor_functions  
  23.     .long   v4wbi_tlb_fns  
  24.     .long   v4wb_user_fns  
  25. #ifndef CONFIG_CPU_DCACHE_WRITETHROUGH  
  26.     .long   arm920_cache_fns  
  27. #else  
  28.     .long   v4wt_cache_fns  
  29. #endif  
  30.     .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 定义。

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. #define MACHINE_START(_type,_name)                      \     
  2. static const struct machine_desc __mach_desc_##_type    \    
  3. __used                                                  \    
  4. __attribute__((__section__(".arch.info.init")))= {      \    
  5.         .nr             = MACH_TYPE_##_type,            \    
  6.         .name           = _name,    
  7.      
  8. #define MACHINE_END                                     \     
  9. };   
举个例子:
[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. MACHINE_START(HALIBUT,"Halibut Board (QCT SURF7200A)")    
  2.     .boot_params      = 0x10000100,    
  3.     .map_io           = halibut_map_io,    
  4.     .init_irq         = halibut_init_irq,    
  5.     .init_machine     = halibut_init,    
  6.     .timer            = &msm_timer,    
  7. MACHINE_END   
将宏展开:
[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. struct machine_desc __mach_desc_HALIBUT{    
  2. __used                                                              
  3. __attribute__((__section__(".arch.info.init")))= {    
  4.     .nr               = MACH_TYPE_HALIBUT,                  
  5.     .name             = "HalibutBoard (QCT SURF7200A)",    
  6.     .boot_params      = 0x10000100,    
  7.     .map_io           = halibut_map_io,    
  8.     .init_irq         = halibut_init_irq,    
  9.     .init_machine     = halibut_init,    
  10.     .timer            = &msm_timer,    
  11. };   
    内核连接时,所有的 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 等函数的调用时机:

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. start_kernel  
  2.     setup_arch(&command_line);  
  3.         init_arch_irq = mdesc->init_irq;  
  4.         system_timer = mdesc->timer;  
  5.         init_machine = mdesc->init_machine;  
  6.           
  7.         paging_init(mdesc)  
  8.             devicemaps_init(mdesc);  
  9.                 mdesc->map_io()  
  10.     init_IRQ()  
  11.         init_arch_irq();  
  12.     time_init()  
  13.         system_timer->init();  
  14.     rest_init();  
  15.         kernel_init  
  16.             do_basic_setup()  
  17.                 do_initcalls()  
  18.                     init_machine()  

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. static int __init customize_machine(void)  
  2. {  
  3.     /* customizes platform devices, or adds new ones */  
  4.     if (init_machine)  
  5.         init_machine();  
  6.     return 0;  
  7. }  
  8. 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 结尾。

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. struct tag {  
  2.       
  3.     struct tag_header {  
  4.         u32 size;       /* 表示tag数据结构的联合u实质存放的数据的大小*/  
  5.         u32 tag;        /* 表示标记的类型 */  
  6.     }hdr;  
  7.       
  8.     union {  
  9.         struct tag_core           core;  
  10.         struct tag_mem32      mem;  
  11.         struct tag_videotext   videotext;  
  12.         struct tag_ramdisk     ramdisk;  
  13.         struct tag_initrd  initrd;  
  14.         struct tag_serialnr       serialnr;  
  15.         struct tag_revision      revision;  
  16.         struct tag_videolfb     videolfb;  
  17.         struct tag_cmdline     cmdline;  
  18.         /* 
  19.         * Acorn specific 
  20.         */  
  21.         struct tag_acorn  acorn;  
  22.         /* 
  23.         * DC21285 specific 
  24.         */  
  25.         struct tag_memclk      memclk;  
  26.     } u;  
  27. };  
[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. setup_start_tag (bd);   /* 设置ATAG_CORE标志 */  
  2. setup_memory_tags (bd);                     /* 设置内存标记 */  
  3. setup_commandline_tag (bd, commandline);    /* 设置命令行标记 */  
  4. ...  
  5. setup_end_tag (bd);     /* 设置ATAG_NONE标志 *  
在内核中,使用 __tagtable 来将处理 tag 的函数放到 *(.taglist.init) 段

arch\arm\kernel\setup.c 

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. __tagtable(ATAG_CORE, parse_tag_core);  
  2. __tagtable(ATAG_MEM, parse_tag_mem32);  
  3. __tagtable(ATAG_VIDEOTEXT, parse_tag_videotext);  
  4. __tagtable(ATAG_CMDLINE, parse_tag_cmdline);  
  5. __tagtable(ATAG_REVISION, parse_tag_revision);  
宏定义在 setup.h (include\asm-arm)
[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. struct tagtable {  
  2.     __u32 tag;  
  3.     int (*parse)(const struct tag *);  
  4. };  
  5.   
  6. #define __tag __used __attribute__((__section__(".taglist.init")))  
  7. #define __tagtable(tag, fn) \  
  8. static struct tagtable __tagtable_##fn __tag = { tag, fn }  
  9.   
  10. 以 __tagtable(ATAG_CORE, parse_tag_core)为例,很简单~  
  11. static struct tagtable __tagtable_parse_tag_core __used __attribute__((__section__(".taglist.init"))) = {  
  12.     ATAG_CORE,  
  13.     parse_tag_core  
  14. }  
    在内核启动过程中,会使用 parse_tags 来处理 tag ,它最终会调用到 parse_tag ,取出 __tagtable_begin 和 __tagtable_end 之间的每一个 tagtable ,比较它们的类型,如果相同则调用 tagtable 里的处理函数来处理这个tag 。

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. if (mdesc->boot_params)  
  2.         tags = phys_to_virt(mdesc->boot_params);  
  3.     parse_tags(tags);  
  4.           
  5. static void __init parse_tags(const struct tag *t)  
  6. {  
  7.     for (; t->hdr.size; t = tag_next(t))  
  8.         if (!parse_tag(t))  
  9.             printk(KERN_WARNING  
  10.                 "Ignoring unrecognised tag 0x%08x\n",  
  11.                 t->hdr.tag);  
  12. }  
[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. static int __init parse_tag(const struct tag *tag)  
  2. {  
  3.     extern struct tagtable __tagtable_begin, __tagtable_end;  
  4.     struct tagtable *t;  
  5.   
  6.     for (t = &__tagtable_begin; t < &__tagtable_end; t++)  
  7.         if (tag->hdr.tag == t->tag) {  
  8.             t->parse(tag);  
  9.             break;  
  10.         }  
  11.   
  12.     return t < &__tagtable_end;  
  13. }  


4、*(.init.setup)
[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. #define __setup(str, fn)                    \  
  2.     __setup_param(str, fn, fn, 0)  
  3.   
  4. #define early_param(str, fn)                    /  
  5.     __setup_param(str, fn, fn, 1)  
  6.           
  7. struct obs_kernel_param {  
  8.     const char *str;  
  9.     int (*setup_func)(char *);  
  10.     int early;  
  11. };  
  12.   
  13. #define __initdata  __attribute__ ((__section__ (".init.data")))  
  14. #define __setup_param(str, unique_id, fn, early)            \  
  15.     static char __setup_str_##unique_id[] __initdata = str; \  
  16.     static struct obs_kernel_param __setup_##unique_id  \  
  17.         __attribute_used__              \  
  18.         __attribute__((__section__(".init.setup"))) \  
  19.         __attribute__((aligned((sizeof(long)))))    \  
  20.         = { __setup_str_##unique_id, fn, early }  
举个例子:

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. __setup("init=", init_setup);  
  2. __setup_param("init=", init_setup, init_setup, 0)  
  3.   
  4. static char __setup_str_init_setup[] __attribute__ ((__section__ (".init.data"))) = "init=";  
  5. static struct obs_kernel_param __setup_init_setup     
  6.     __attribute_used__                
  7.     __attribute__((__section__(".init.setup")))   
  8.     __attribute__((aligned((sizeof(long)))))      
  9.     = { __setup_str_init_setup, init_setup, 0 }  
  10.           
    parse_early_param 处理 early_param 定义的参数,parse_args 处理 __setup 定义的参数

5、*(.early_param.init)

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. struct early_params {  
  2.     const char *arg;  
  3.     void (*fn)(char **p);  
  4. };  
  5.   
  6. #define __early_param(name,fn)                  \  
  7. static struct early_params __early_##fn __used          \  
  8. __attribute__((__section__(".early_param.init"))) = { name, fn }  
例如:
[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. __early_param("initrd=", early_initrd);  
  2. static struct early_params __early_early_initrd __used  __attribute__((__section__(".early_param.init"))) =   
  3. {  
  4.     "initrd=",   
  5.     early_initrd   
  6. }  
    parse_cmdline 处理 __early_param 定义的参数

6、INITCALLS

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. #define INITCALLS                                        
  2.     *(.initcallearly.init)                        \          
  3.     VMLINUX_SYMBOL(__early_initcall_end) = .;     \          
  4.     *(.initcall0.init)                            \          
  5.     *(.initcall0s.init)                           \          
  6.     *(.initcall1.init)                            \          
  7.     *(.initcall1s.init)                           \          
  8.     *(.initcall2.init)                            \          
  9.     *(.initcall2s.init)                           \          
  10.     *(.initcall3.init)                            \          
  11.     *(.initcall3s.init)                           \          
  12.     *(.initcall4.init)                            \          
  13.     *(.initcall4s.init)                           \          
  14.     *(.initcall5.init)                            \          
  15.     *(.initcall5s.init)                           \          
  16.     *(.initcallrootfs.init)                       \          
  17.     *(.initcall6.init)                            \          
  18.     *(.initcall6s.init)                           \          
  19.     *(.initcall7.init)                            \          
  20.     *(.initcall7s.init)                           \  
[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. typedef int (*initcall_t)(void);  
  2. #define __define_initcall(level,fn,id) \  
  3.     static initcall_t __initcall_##fn##id __attribute_used__ \  
  4.     __attribute__((__section__(".initcall" level ".init"))) = fn  
[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. #define pure_initcall(fn)               __define_initcall("0",fn,0)  
  2. #define core_initcall(fn)               __define_initcall("1",fn,1)  
  3. #define core_initcall_sync(fn)          __define_initcall("1s",fn,1s)  
  4. #define postcore_initcall(fn)           __define_initcall("2",fn,2)  
  5. #define postcore_initcall_sync(fn)      __define_initcall("2s",fn,2s)  
  6. #define arch_initcall(fn)               __define_initcall("3",fn,3)  
  7. #define arch_initcall_sync(fn)          __define_initcall("3s",fn,3s)  
  8. #define subsys_initcall(fn)             __define_initcall("4",fn,4)  
  9. #define subsys_initcall_sync(fn)        __define_initcall("4s",fn,4s)  
  10. #define fs_initcall(fn)                 __define_initcall("5",fn,5)  
  11. #define fs_initcall_sync(fn)            __define_initcall("5s",fn,5s)  
  12. #define rootfs_initcall(fn)             __define_initcall("rootfs",fn,rootfs)  
  13. #define device_initcall(fn)             __define_initcall("6",fn,6)  
  14. #define device_initcall_sync(fn)        __define_initcall("6s",fn,6s)  
  15. #define late_initcall(fn)               __define_initcall("7",fn,7)  
  16. #define late_initcall_sync(fn)          __define_initcall("7s",fn,7s)  
以 device_initcall(mac_hid_init) 为例:
[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. __define_initcall("6",fn,6)  
  2.   
  3. static initcall_t __initcall_mac_hid_init6 __attribute_used__ __attribute__((__section__(".initcall" 6 ".init")))   
  4. <span style="white-space:pre">    </span>= mac_hid_init  
看来一下我们熟悉的 module_init(xxx_init) 
[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. #define module_init(x)     __initcall(x);  
  2. #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()

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. static void __init do_initcalls(void)  
  2. {  
  3.     initcall_t *call;  
  4.       
  5.     for (call = __early_initcall_end; call < __initcall_end; call++)  
  6.         do_one_initcall(*call);  
  7.       
  8.     /* Make sure there is no pending stuff from the initcall sequence */  
  9.     flush_scheduled_work();  
  10. }  
    也就是说 INITCALLS 段里的东西会在内核启动时按顺序逐个调用,以后遇到 xxx_initcall 就不用懵B了~
0 0
原创粉丝点击