在我的上一篇文章“ [Funkunux] Linux_2.6.22.6的Makefile分析 ”中,已经找到linux内核的第一条代码的位置是head.s,在head.s中,内核将bootloader中传给内核的参数进行解析,比对机器ID等参数,设置页表,开启MMU,然后跳转到/init/Main.c中的start_kernel()中进行一系列初始化。
以下是start_kernel函数的分析,重点分析命令行参数的解析函数parse_args:
博主使用的开发板是S3C2440,BOOTLOADER是U-BOOT,传入的命令行参数是#bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0#
我们可以在start_kernel中找到这一句:
parse_args("Booting kernel", static_command_line, __start___param, __stop___param - __start___param, &unknown_bootoption);
在parse_args中调用了:
parse_one(static_command_line, val, __start___param, __stop___param - __start___param, &unknown_bootoption);
在parse_args中调用了:
handle_unknown(param, val); //也就是parse_args中传入的函数指针unknown_bootoption
我们搜索unknown_bootoption,发现其调用了obsolete_checksetup函数:
static int __init obsolete_checksetup(char *line)
{
struct obs_kernel_param *p;
int had_early_param = 0;
p = __setup_start;
do {
int n = strlen(p->str);
if (!strncmp(line, p->str, n)) {
if (p->early) {
/* Already done in parse_early_param?
* (Needs exact match on param part).
* Keep iterating, as we can have early
* params and __setups of same names 8( */
if (line[n] == '\0' || line[n] == '=')
had_early_param = 1;
} else if (!p->setup_func) {
printk(KERN_WARNING "Parameter %s is obsolete,"
" ignored\n", p->str);
return 1;
} else if (p->setup_func(line + n))
return 1;
}
p++;
} while (p < __setup_end);
return had_early_param;
}
我们注意到了一个结构体struct obs_kernel_param:
struct obs_kernel_param {
const char *str;
int (*setup_func)(char *);
int early;
};
以及该结构体的两个指针地址: __setup_start 和 __setup_end:
我们可以在vmlinux.lds中发现这两个地址:
__setup_start = .;
*(.init.setup)
__setup_end = .;
可知这两个地址中包含的是被设为.init.setup段的内容,搜索.init.setup可以找到两个宏:
#define __setup(str, fn) \
__setup_param(str, fn, fn, 0)
#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 }
并且可以找到:
static int __init root_dev_setup(char *line)
{
strlcpy(saved_root_name, line, sizeof(saved_root_name));
return 1;
}
__setup("root=", root_dev_setup);
static int __init init_setup(char *str)
{
unsigned int i;
execute_command = str;
/*
* In case LILO is going to boot us with default command line,
* it prepends "auto" before the whole cmdline which makes
* the shell think it should execute a script with such name.
* So we ignore all arguments entered _before_ init=... [MJ]
*/
for (i = 1; i < MAX_INIT_ARGS; i++)
argv_init[i] = NULL;
return 1;
}
__setup("init=", init_setup);
static int __init console_setup(char *str)
{
char name[sizeof(console_cmdline[0].name)];
char *s, *options;
int idx;
/*
* Decode str into name, index, options.
*/
if (str[0] >= '0' && str[0] <= '9') {
strcpy(name, "ttyS");
strncpy(name + 4, str, sizeof(name) - 5);
} else {
strncpy(name, str, sizeof(name) - 1);
}
name[sizeof(name) - 1] = 0;
if ((options = strchr(str, ',')) != NULL)
*(options++) = 0;
#ifdef __sparc__
if (!strcmp(str, "ttya"))
strcpy(name, "ttyS0");
if (!strcmp(str, "ttyb"))
strcpy(name, "ttyS1");
#endif
for (s = name; *s; s++)
if ((*s >= '0' && *s <= '9') || *s == ',')
break;
idx = simple_strtoul(s, NULL, 10);
*s = 0;
add_preferred_console(name, idx, options);
return 1;
}
__setup("console=", console_setup);
显然,通过__setup(name,fn);这个宏,定义了三个结构体变量,并链接进 .init.setup 段中,early均为0
而 obsolete_checksetup 这个函数的工作就是遍历 .init.setup 段,根据bootloader传入的命令行参数,搜索结构体 obs_kernel_param 中 str 的变量符合"root="、"init="、"console="等字符串的,调用对应的 setup_func 函数。至于如何解析命令行参数,是在 parse_args 中做的工作,大家可以自行分析。
按照调用关系,可整理出如下内容:
start_kernel()
-> parse_args("Booting kernel", static_command_line, __start___param,__stop___param - __start___param,&unknown_bootoption);
-> parse_one(static_command_line, val, __start___param, __stop___param - __start___param, &unknown_bootoption);
-> if (handle_unknown)
{
DEBUGP("Unknown argument: calling %p\n", handle_unknown);
return handle_unknown(param, val);
-> unknown_bootoption(param, val);
-> obsolete_checksetup(param);
-> root_dev_setup(val) //val="/dev/mtdblock3"
{
strlcpy(saved_root_name, val, sizeof(saved_root_name));
return 1;
}
-> console_setup(val) //val="ttySAC0"
-> add_preferred_console(char *name, int idx, char *options) //name="ttySAC"; idx=0; options=0;
-> console_cmdline[0].name= name;
console_cmdline[0].options= options;
console_cmdline[0].idx= idx;
-> init_setup(val) //val="/linuxrc"
-> execute_command = val;
for (i = 1; i < 32; i++) //static char * argv_init[32+2] = { "init", NULL, };
argv_init[i] = NULL;
}
显然,调用完parse_args后:
saved_root_name="/dev/mtdblock3";
console_cmdline[0].name= name;
console_cmdline[0].options= 0;
console_cmdline[0].idx= 0;
execute_command ="/linuxrc";
往后,start_kernel()会用到这些参数,至于如何使用,请看我下一篇文章。