分类: LINUX
2015-03-19 14:08:02
这里我们先搞清楚一个问题,在kernel配置选项Boot options中有一个Default kernel command string参数项,而在u-boot参数中也有一个bootargs参数项,他们都是供内核启动用的,那他们又有什么区别呢,内核启动时到底是用哪一个呢?两种参数项分别如下图所示(kernel中的参数指定是从开发板Flash分区上挂载文件系统,u-boot中的参数指定的是从
NFS挂载文件系统):
实际上,内核中的参数项是内核默认提供的,在内核配置时去指定,而u-boot提供的
则在u-boot启动时传递到内核中取代内核提供的参数。所以当u-boot没有提供bootargs参数时,内核启动就是用内核配置时指定的参数,当u-boot提供了bootargs参数时就使用u-boot的参数。
前面在U-Boot启动过程中讲到,在do_boot_linux函数中会会调用“theKernel (0, bd->bi_arch_number, bd->bi_boot_params)”启动内核,其theKernel指向内核存放内存地址,这个用来实现程序跳转到内核存在的内存地址处,开始内核的执行。bd->bi_arch_number就是前面board_init函数设置的机器类型ID,在内核引导阶段的__lookup_processor_type函数已经用到。而bd->bi_boot_params就是标记列表的开始地址,预先存在该地址的存放一个tag列表, tag列表将在setup_arch函数中进行初步的处理。
setup_arch函数定义在arch/arm/kernel/setup.c中,其部分代码如下:
void __init setup_arch(char **cmdline_p)
{
struct tag *tags = (struct tag *)&init_tags;
struct machine_desc *mdesc;
char *from = default_command_line;
setup_processor();/*进行处理器的相关的一些设置*/
mdesc = setup_machine(machine_arch_type);/*获得开发板machine_desc结构*/
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) /*定义了Bootloader的传入参数的地址*/
tags = phys_to_virt(mdesc->boot_params);/*这个地址就是tag列表地址*/phys_to_virt时将已经映射的物理内存的地址转换为虚拟地址:
/*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)
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) /*如果已在内核中定义了meminfo结构*/
squash_mem_tags(tags); ;/*忽略内存tag*/
save_atags(tags);
parse_tags(tags);} /*解释每个tag*/}
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;
/* parse_early_param needs a boot_command_line */
strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);
/* populate cmd_line too for later use, preserving boot_command_line */
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
*cmdline_p = cmd_line;
parse_early_param();
paging_init(&meminfo, mdesc);/*重新初始化页表*/
request_standard_resources(&meminfo, mdesc);
#ifdef CONFIG_SMP
smp_init_cpus();
#endif
cpu_init();
tcm_init();
init_arch_irq = mdesc->init_irq;
system_timer = mdesc->timer;
init_machine = mdesc->init_machine;
……
early_trap_init();
}
setup_processor()被用来进行处理器的相关的一些设置,它会调用引导阶段的__lookup_processor_type函数从处理器内核描述符表中找到匹配的描述符(proc_init_list结构),并初始化一些处理器变量。setup_machine用机器编号ID(在解压函数decompress_kernel 中被赋值)作为参数返回机器描述符,它会调用引导阶段的__lookup_machine_type函数来来获得开发板的机器描述符(machine_desc结构)。从机器描述符中获得内核参数的物理地址,赋值给tags 变量。然后调用parse_tags()函数分析内核参数链表,把各个参数值传递给全局变量。这样内核就收到了u-boot传递的参数。
在arch/arm/mach-s3c6410/ mach- smdk6410.c中有如下定义,启动参数地址为(S3C_SDRAM_PA + 0x100),即为0x50000100
MACHINE_START(SMDK6410, "SMDK6410")
……
.boot_params = S3C_SDRAM_PA + 0x100,
由上面的代码可以看出,由machine_desc结构我们可以确定Bootloader的传入参数的地址,由于开启了mmu,在使用时,我们需要调用phys_to_virt()函数将其转化为虚拟地址。
parse_tags()函数用来处理每个标记。文件arch/arm/kernel/setup.c对每种标记都定义了相关的处理函数。比如对内存标记、命令行标记,使用如下代码为它们指定了处理函数为parse_tag_mem32()、parse_tag_cmdline()。
__tagtable(ATAG_MEM, parse_tag_mem32);
__tagtable(ATAG_CMDLINE, parse_tag_cmdline);
其中parse_tag_mem32()函数根据内存标记定义内存的起始地址、长度,在全局变量meminfo中增加内存的描述信息。以后内核就可以通过meminfo结构了解开发板的内存信息。parse_tag_cmdline()只是简单的将命令行标记的内容复制到字符串default_command_line中保存下来,后面会进一步处理。
parse_cmdline( )扫描命令行参数,对其中一些参数进行先期的处理,比如对参数mem、initrd,使用如下代码为它们指定了处理函数为early_mem()、early_ initrd()。
__early_param("mem=", early_mem);
__early_param("initrd=", early_initrd);
"mem="用来强制限制linux系统所能使用的内存总量。比如"mem=60M"使得系统只能使用60M的内存,即使是tag中指明了64M内存。本文中没有设置这样类命令行参数。
命令行的处理没有就此结束,在setup_arch函数函数之外还进行了一系列的后继处理,比如在start_kernel函数中调用了如下代码:
setup_arch(&command_line);
setup_command_line(command_line);
……
parse_early_param();
parse_args("Booting kernel", static_command_line, __start___param,
__stop___param - __start___param,&unknown_bootoption); //解析由BOOT传递的启动参数
比如对于命令“console=ttySAC0”,它的处理过程就是通过parse_args函数调用传入的unknown_bootoption函数,最后调用由下面代码指定的console_setup处理函数:
__setup(“console=”, console_setup)
“console=”用来指定要使用的控制台名称、序号、参数。比如“console= ttySAC0,115200”,表示要使用的控制台名称为ttySAC,序号为0(即第一个串口),波特率为115200。经过console_setup函数处处理之后,会在全局结构console_cmdline中保存这些信息,然后在后面console_init函数初始化控制台时会根据这些信息选择要使用的控制台。