《kernel源码分析(二)内核启动流程分析》
1.内核启动参数
当uboot启动内核时,调用的是armlinux.c中的theKernel (0, bd->bi_arch_number, bd->bi_boot_params),传入了两个参数。
bi_arch_number:机器码(以2410为例,可以通过sourceinsight中的全局搜索。在smdk2410.c中:gd->bd->bi_arch_number = MACH_TYPE_SMDK2410; #define MACH_TYPE_SMDK2410 193,所以uboot会将193传给kernel。193会存入R1寄存器中)
bd->bi_boot_params:启动参数(同一个文件中 gd->bd->bi_boot_params = 0x30000100;)
所以接下来分析内核启动第一个运行的文件/arch/arm/kernel/head.S
2.heas.S分析
mrc p15, 0, r9, c0, c0 @ get processor id
//获取CPU处理器并对比,如果没有这个CPU。就跳转到error_p bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)? beq __error_p @ yes, error 'p'
//检测是不是支持该单板,参数由uboot传入 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
lookup_machine_type分析:
/*
* Look in <asm/procinfo.h> and arch/arm/kernel/arch.[ch] for
* more information about the __proc_info and __arch_info structures.
*/
.align 2
3: .long __proc_info_begin
.long __proc_info_end
4: .long .
.long __arch_info_begin
.long __arch_info_end
1 __lookup_machine_type: 2 adr r3, 4b //r3=4b的地址 3 ldmia r3, {r4, r5, r6} 4 sub r3, r3, r4 @ get offset between virt&phys 5 add r5, r5, r3 @ convert virt addresses to 6 add r6, r6, r3 @ physical address space 7 1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type 8 teq r3, r1 @ matches loader number? 9 beq 2f @ found 10 add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc 11 cmp r5, r6 12 blo 1b 13 mov r5, #0 @ unknown machine 14 2: mov pc, lr 15 ENDPROC(__lookup_machine_type)
第二行:
adr r3,4b ---->r3=4b的地址(物理地址)
4b ---->4: .long . .long __arch_info_begin .long __arch_info_end
第三行:
r4=.(“.”代表虚拟地址,当内核编译的时候,这个段的地址) r5=__arch_info_begin r6=__arch_info_end
在vmlinux.lds中有相关定义:
__arch_info_begin = .; *(.arch.info.init) __arch_info_end = .;
查找.arch.info.init,可以发现在arch/arm/include/asm/mach/arch.h中定义:
#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在哪里使用,可以找到在相关单板有定义(mach-smdk2410.c)
MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch* to SMDK2410 */ /* Maintainer: Jonas Dietsche */ .phys_io = S3C2410_PA_UART, .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc, .boot_params = S3C2410_SDRAM_PA + 0x100, .map_io = smdk2410_map_io, .init_irq = s3c24xx_init_irq, .init_machine = smdk2410_init, .timer = &s3c24xx_timer, MACHINE_END
跟uboot命令那样,对宏进行分析
#define MACHINE_START(_type,_name) \ static const struct machine_desc __mach_desc_SMDK2410 \ __used \ __attribute__((__section__(".arch.info.init"))) = { \ //将这个段的属性设置在.arch.info.init中 .nr = MACH_TYPE_SMDK2410, \ .name = "SMDK2410", /* Maintainer: Jonas Dietsche */
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.map_io = smdk2410_map_io,
.init_irq = s3c24xx_init_irq,
.init_machine = smdk2410_init,
.timer = &s3c24xx_timer, };
所以是被定义成machine_desc类型的结构体变量
struct machine_desc { /* * Note! The first four elements are used * by assembler code in head.S, head-common.S */ unsigned int nr; /* architecture number */ 机器ID 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(SMDK2410, "SMDK2410")这一段的内容进行编译,并且强制将这段放在".arch.info.init"中。uboot通过theKernel (0, bd->bi_arch_number, bd->bi_boot_params)启动内核,会将bd->bi_arch_number和
bd->bi_boot_params两个参数传递给内核。然后内核将获取__arch_info_begin和__arch_info_end地址,在这两个地址之间(也就是".arch.info.init"段)进行对比,查看该内核是否支持该单板。
head.S总结:
由于head.S源码相对较长,就不全部贴出一一分析。
head.S中主要做的事情:
1. 判断是否支持该cpu
2. 判断是否支持这个单板
3. 创建页表
4. 使能mmu
5. 跳转到start_kernel
在head.S中已经对uboot传入的参数bd->bi_arch_number进行的对比使用,接下来分析bd->bi_boot_params参数。
3.bi_boot_params解析
在/init/mian.c中,setup_arch(&command_line)和setup_command_line(command_line),这两个函数就是对bi_boot_params参数进行解析。
4.内核挂载根文件系统
start_kernel---->
parse_early_param---->
parse_early_options---->
do_early_param //从__setup_start到__setup_end,进行对比。
unknown_bootoption---->
obsolete_checksetup //从__setup_start到__setup_end,进行对比。
rest_init--->
kernel_init---->
prepare_namespace---->
mount_root //挂载根文件系统
init_post //执行以下的应用程序
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
其中__setup_start和__setup_end,就是和上面分析的一样。
__setup_start = .; *(.init.setup) __setup_end = .;
do_mounts.c中
static int __init root_dev_setup(char *line) { strlcpy(saved_root_name, line, sizeof(saved_root_name)); return 1; } __setup("root=", root_dev_setup);
init.h中
#define __setup_param(str, unique_id, fn, early) \ static const char __setup_str_##unique_id[] __initconst \ __aligned(1) = str; \ static struct obs_kernel_param __setup_##unique_id \ __used __section(.init.setup) \ __attribute__((aligned((sizeof(long))))) \ = { __setup_str_##unique_id, fn, early } #define __setup(str, fn) \ __setup_param(str, fn, fn, 0)
分析过程和上面一样。这边就不再重复分析了。其中一个early参数和其他不一样。后面再详细分析。