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,}; 内核连接时,所有的 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%08xn",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,0) #define early_param(str,fn) / __setup_param(str,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,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,early } 举个例子: __setup("init=",init_setup); __setup_param("init=",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,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,id) static initcall_t __initcall_##fn##id __attribute_used__ __attribute__((__section__(".initcall" level ".init"))) = fn #define pure_initcall(fn) __define_initcall("0",0) #define core_initcall(fn) __define_initcall("1",1) #define core_initcall_sync(fn) __define_initcall("1s",1s) #define postcore_initcall(fn) __define_initcall("2",2) #define postcore_initcall_sync(fn) __define_initcall("2s",2s) #define arch_initcall(fn) __define_initcall("3",3) #define arch_initcall_sync(fn) __define_initcall("3s",3s) #define subsys_initcall(fn) __define_initcall("4",4) #define subsys_initcall_sync(fn) __define_initcall("4s",4s) #define fs_initcall(fn) __define_initcall("5",5) #define fs_initcall_sync(fn) __define_initcall("5s",5s) #define rootfs_initcall(fn) __define_initcall("rootfs",rootfs) #define device_initcall(fn) __define_initcall("6",6) #define device_initcall_sync(fn) __define_initcall("6s",6s) #define late_initcall(fn) __define_initcall("7",7) #define late_initcall_sync(fn) __define_initcall("7s",7s) 以 device_initcall(mac_hid_init) 为例: __define_initcall("6",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了。 -- (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |