学习笔记 --- LINUX内核启动第二阶段分析(不考虑自解压过程)

上篇文章中分析了Linux内核从head.s启动:

	.section ".text.head", "ax"
ENTRY(stext)
	setmode	PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode
						@ and irqs disabled
	mrc	p15, 0, r9, c0, c0		@ get processor id
	bl	__lookup_processor_type		@ r5=procinfo r9=cpuid
	movs	r10, r5				@ invalid processor (r5=0)?
	beq	__error_p			@ yes, error 'p'
	bl	__lookup_machine_type		@ r5=machinfo
	movs	r8, r5				@ invalid machine (r5=0)?
	beq	__error_a			@ yes, error 'a'
	bl	__vet_atags
	bl	__create_page_tables

1 开始设置ARM模式为管理模式,禁止中断,然后获取处理器ID

2 调用__lookup_processor_type 判断内核是否支持该处理器,这里略,不讲!

3 处理器ID验证完后调用  __lookup_machine_type  判断内核是否支持该机器类型:

__lookup_machine_type:
	adr	r3, 4b
	ldmia	r3, {r4, r5, r6}
	sub	r3, r3, r4			@ get offset between virt&phys
	add	r5, r5, r3			@ convert virt addresses to
	add	r6, r6, r3			@ physical address space
1:	ldr	r3, [r5, #MACHINFO_TYPE]	@ get machine type
	teq	r3, r1				@ matches loader number?
	beq	2f				@ found
	add	r5, r5, #SIZEOF_MACHINE_DESC	@ next machine_desc
	cmp	r5, r6
	blo	1b
	mov	r5, #0				@ unknown machine
2:	mov	pc, lr
ENDPROC(__lookup_machine_type)
第二行4b的位置是:

4:	.long	.
	.long	__arch_info_begin
	.long	__arch_info_end
所以2,3行执行之后:

r4 = .  r5=__arch_info_begin  r6=__arch_info_end
4,5,6行都有解释,目的就是为了计算出__arch_info_begin 和__arch_info_end在SDRAM里面实际存放地址,这两个东西是链接文件里面定义的arch.info.init段的起始和结束标志:

__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
而arch.info.init段存放的是一个结构体,从代码可以看出:

/*
 * Set of macros to define architecture features.  This is built into
 * a table by the linker.
 */
#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				\
};

该结构体格式为:

struct machine_desc {
	/*
	 * Note! The first four elements are used
	 * by assembler code in head.S, head-common.S
	 */
	unsigned int		nr;		/* architecture number	*/
	unsigned int		phys_io;	/* start of physical io	*/
	unsigned int		io_pg_offst;	/* byte offset for io 
						 * page tabe entry	*/

	const char		*name;		/* architecture name	*/
	unsigned long		boot_params;	/* tagged list		*/

	unsigned int		video_start;	/* start of video RAM	*/
	unsigned int		video_end;	/* end of video RAM	*/

	unsigned int		reserve_lp0 :1;	/* never has lp0	*/
	unsigned int		reserve_lp1 :1;	/* never has lp1	*/
	unsigned int		reserve_lp2 :1;	/* never has lp2	*/
	unsigned int		soft_reboot :1;	/* soft reboot		*/
	void			(*fixup)(struct machine_desc *,
					 struct tag *, char **,
					 struct meminfo *);
	void			(*map_io)(void);/* IO mapping function	*/
	void			(*init_irq)(void);
	struct sys_timer	*timer;		/* system tick timer	*/
	void			(*init_machine)(void);
};
定义的实体:

MACHINE_START(S3C2440, "SMDK2440")
	/* Maintainer: Ben Dooks <ben@fluff.org> */
	.phys_io	= S3C2410_PA_UART,
	.io_pg_offst	= (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
	.boot_params	= S3C2410_SDRAM_PA + 0x100,     //就是0x30000100

	.init_irq	= s3c24xx_init_irq,
	.map_io		= smdk2440_map_io,
	.init_machine	= smdk2440_machine_init,
	.timer		= &s3c24xx_timer,
MACHINE_END

还有很多很多不同的单板都定义了这个,可以知道,这个arch.info.init段里面放的就是linux内核支持的所有单板的参数信息,再回到汇编,7-10行就是判断每个单板的机器类型(读到寄存器r3)是否和uboot传入的单板参数(存在寄存器r1)匹配,如果匹配则内核支持该单板。

4 接着执行__vet_atags :

__vet_atags:
	tst	r2, #0x3			@ aligned?
	bne	1f

	ldr	r5, [r2, #0]			@ is first tag ATAG_CORE?
	cmp	r5, #ATAG_CORE_SIZE
	cmpne	r5, #ATAG_CORE_SIZE_EMPTY
	bne	1f
	ldr	r5, [r2, #4]
	ldr	r6, =ATAG_CORE
	cmp	r5, r6
	bne	1f

	mov	pc, lr				@ atag pointer is ok

1:	mov	r2, #0
	mov	pc, lr
ENDPROC(__vet_atags)
这个函数就是去验证uboot传入的TAG参数区域的ATAG_CORE_SIZE与ATAG_CORE是否和内核的匹配,这段TAG参数起始地址存在寄存器r2,读出来再比较这两项是否一致,如果一致则通过。

5 接下来建立页表

6 使能MMU

最后跳转到 start_kernel 进入C语言代码段,开始打印版本信息,然后执行下面这段代码,主要作用就是解析uboot传入的参数:

void __init setup_arch(char **cmdline_p)
{
	struct tag *tags = (struct tag *)&init_tags;
	struct machine_desc *mdesc;
	char *from = default_command_line;

	unwind_init();

	setup_processor();
	mdesc = setup_machine(machine_arch_type); //设置机器类型,返回machine_desc结构体,这个结构体通过MACHINE_START设置好了
	machine_name = mdesc->name;
	if (mdesc->soft_reboot)
		reboot_setup("s");

	if (__atags_pointer)
		tags = phys_to_virt(__atags_pointer);
	else if (mdesc->boot_params)  //就是结构体里面的S3C2410_SDRAM_PA + 0x100 = 0x30000100,这个地址存放uboot设置好的TAG参数
		tags = phys_to_virt(mdesc->boot_params); //转为虚拟地址,返回tags,tags指向uboot存放的TAG参数起始地址

	/*
	 * If we have the old style parameters, convert them to
	 * a tag list.
	 */
	if (tags->hdr.tag != ATAG_CORE) 
		convert_to_tag_list(tags);
	if (tags->hdr.tag != ATAG_CORE)   //如果TAG参数不匹配,则使用内核默认的参数
		tags = (struct tag *)&init_tags;

	if (mdesc->fixup)
		mdesc->fixup(mdesc, tags, &from, &meminfo);

	if (tags->hdr.tag == ATAG_CORE) {
		if (meminfo.nr_banks != 0)
			squash_mem_tags(tags);
		save_atags(tags);
		parse_tags(tags);
	}

	init_mm.start_code = (unsigned long) _text;
	init_mm.end_code   = (unsigned long) _etext;
	init_mm.end_data   = (unsigned long) _edata;
	init_mm.brk	   = (unsigned long) _end;

	memcpy(boot_command_line, from, COMMAND_LINE_SIZE);
	boot_command_line[COMMAND_LINE_SIZE-1] = '\0';  
	parse_cmdline(cmdline_p, from);  //解析命令行,就是解析uboot传入的bootargs环境变量,它为TAG参数的一部分
	paging_init(mdesc);
	request_standard_resources(&meminfo, mdesc);

#ifdef CONFIG_SMP
	smp_init_cpus();
#endif

	cpu_init();
	tcm_init();

	/*
	 * Set up various architecture-specific pointers
	 */
	init_arch_irq = mdesc->init_irq;  //把结构体里的参数都放在独自的变量里单独表示
	system_timer = mdesc->timer;
	init_machine = mdesc->init_machine;  

#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
	conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
	conswitchp = &dummy_con;
#endif
#endif
	early_trap_init();
}
上面执行完后,返回bootargs参数到cmdline_p,然后再执行下面函数,将命令行地址存放在全局指针saved_command_line里面:

static void __init setup_command_line(char *command_line)
{
	saved_command_line = alloc_bootmem(strlen (boot_command_line)+1);
	static_command_line = alloc_bootmem(strlen (command_line)+1);
	strcpy (saved_command_line, boot_command_line);
	strcpy (static_command_line, command_line);
}
然后下面会用unknown_bootoption函数来解析这个命令行,比如文件系统挂载到哪个地方,使用串口几作为监控端等;

接下来是一大堆初始化,执行到最后rest_init:

kernel_init ---- prepare_namespace ---- mount_root (挂接根文件系统)

挂接好之后 init_post:

static noinline int init_post(void)
	__releases(kernel_lock)
{
	/* need to finish all async __init code before freeing the memory */
	async_synchronize_full();
	free_initmem();
	unlock_kernel();
	mark_rodata_ro();
	system_state = SYSTEM_RUNNING;
	numa_default_policy();

	if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)  //打开终端文件 ----用于标准printf打印
		printk(KERN_WARNING "Warning: unable to open an initial console.\n");

	(void) sys_dup(0);  //复制终端文件 用于 标准scanf输入
	(void) sys_dup(0);  //复制终端文件 用于 标准error打印

	current->signal->flags |= SIGNAL_UNKILLABLE;

	if (ramdisk_execute_command) {    
		run_init_process(ramdisk_execute_command);
		printk(KERN_WARNING "Failed to execute %s\n",
				ramdisk_execute_command);
	}

	/*
	 * We try each of these until one succeeds.
	 *
	 * The Bourne shell can be used instead of init if we are
	 * trying to recover a really broken machine.
	 */
	if (execute_command) {   //
		run_init_process(execute_command);  //执行uboot传入的“init=xxx”指定的应用脚本
		printk(KERN_WARNING "Failed to execute %s.  Attempting "
					"defaults...\n", execute_command);
	}
	run_init_process("/sbin/init");  //如果上面uboot没有指定,那么往下执行默认路径的脚本
	run_init_process("/etc/init");
	run_init_process("/bin/init");
	run_init_process("/bin/sh");

	panic("No init found.  Try passing init= option to kernel.");
}
上面挂接文件系统mount_root ,挂接到哪里是由环境变量bootargs里面的“root=” 类参数决定的,是通过unknown_bootoption函数解析出来的。

假设uboot传进来参数为  “root=/dev/mtdblock3” 则表示挂载到mtd设备的第4个分区,至于这个分区对应哪里是在nandflash的驱动里面指定的:

static struct mtd_partition friendly_arm_default_nand_part[] = {
	[0] = {
		.name	= "uboot",
		.size	= 0x00040000,
		.offset	= 0,
	},
	[1] = {
		.name	= "param",
		.offset = 0x00040000,
		.size	= 0x00020000,
	},
	[2] = {
		.name	= "Kernel",
		.offset = 0x00060000,
		.size	= 0x00500000,
	},
	[3] = {                 
		.name	= "root",
		.offset = 0x00560000,
		.size	= 1024 * 1024 * 1024, 
	},
	[4] = {
		.name	= "nand",
		.offset = 0x00000000,
		.size	= 1024 * 1024 * 1024, //
	}
};

上面可以知道第4个分区就是root根文件系统分区,这个在一直nand驱动的时候要和uboot传进的参数对应起来。















  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值