Linux 链接脚本分析

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

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


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

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) 段

arch\arm\kernel\setup.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 (include\asm-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\n",
				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"))) 
<span style="white-space:pre">	</span>= 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了~






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值