版权声明:本文为CSDN博主「程序猿Ricky的日常干货」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/rikeyone/article/details/79979887
————————————————
early_param的定义:
#define early_param(str, fn) \
__setup_param(str, fn, fn, 1)
__setup
的定义如下:
#define __setup(str, fn) \
__setup_param(str, fn, fn, 0)
这两个宏定义都会调用到更底层的一个宏__setup_param
:
#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 }
所以现在问题的关键变成了__setup_param
的实现机制。通过上面的定义可知,__setup_param
实际上就是会创建一个struct obs_kernel_param的一个变量,并把该变量存储到.init.setup
对应的section中。
我们进一步看一下struct obs_kernel_param该结构体:
struct obs_kernel_param {
const char *str; //用于匹配的key
int (*setup_func)(char *); //解析函数,传入的是key对应的value
int early; //表示是否优先解析,定义early为1的会优先解析
};
这个结构体的作用主要是用来解析cmdline的,也就是uboot传给kernel的bootargs参数。我们根据代码看一下kernel是如何解析的。
asmlinkage __visible void __init start_kernel(void)
{
......
setup_arch(&command_line); //解析dtb中的bootargs,并放置到boot_command_line中,并且会执行early param的解析
/*
* Set up the the initial canary ASAP:
*/
boot_init_stack_canary();
mm_init_cpumask(&init_mm);
setup_command_line(command_line); //简单的备份和拷贝boot_command_line
setup_nr_cpu_ids();
setup_per_cpu_areas();
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
build_all_zonelists(NULL, NULL);
page_alloc_init();
pr_notice("Kernel command line: %s\n", boot_command_line);
parse_early_param(); //执行early param的解析,实际查看代码可知,由于前面已经执行过一边,所以这里不会重复执行,会直接return
after_dashes = parse_args("Booting kernel",
static_command_line, __start___param,
__stop___param - __start___param,
-1, -1, NULL, &unknown_bootoption);//执行普通的非early类型的cmdline的解析
if (!IS_ERR_OR_NULL(after_dashes))
parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
NULL, set_init_arg);
......
}
void __init setup_arch(char **cmdline_p)
{
pr_info("Boot CPU: AArch64 Processor [%08x]\n", read_cpuid_id());
......
*cmdline_p = boot_command_line;
......
setup_machine_fdt(__fdt_pointer); //此函数中会搜索dtb中的chosen并解析bootargs参数,并放到boot_command_line中
parse_early_param(); //解析cmdline中的early param,从boot_command_line中获取bootargs参数
......
}
函数调用关系:
setup_machine_fdt(__fdt_pointer)–>early_init_dt_scan–>early_init_dt_scan_nodes–>of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
最终代码会调用到early_init_dt_scan_chosen
,它的功能是扫描dts节点中的chosen,并解析对应的bootargs参数。
int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,
int depth, void *data)
{
int l = 0;
const char *p = NULL;
char *cmdline = data;
pr_debug("search \"chosen\", depth: %d, uname: %s\n", depth, uname);
if (depth != 1 || !cmdline ||
(strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))
return 0;
early_init_dt_check_for_initrd(node);
/* Put CONFIG_CMDLINE in if forced or if data had nothing in it to start */
if (overwrite_incoming_cmdline || !cmdline[0])
strlcpy(cmdline, config_cmdline, COMMAND_LINE_SIZE);
/* Retrieve command line unless forcing */
if (read_dt_cmdline)
p = of_get_flat_dt_prop(node, "bootargs", &l);
if (p != NULL && l > 0) {
if (concat_cmdline) {
int cmdline_len;
int copy_len;
strlcat(cmdline, " ", COMMAND_LINE_SIZE);
cmdline_len = strlen(cmdline);
copy_len = COMMAND_LINE_SIZE - cmdline_len - 1;
copy_len = min((int)l, copy_len);
strncpy(cmdline + cmdline_len, p, copy_len);
cmdline[cmdline_len + copy_len] = '\0';
} else {
strlcpy(cmdline, p, min((int)l, COMMAND_LINE_SIZE));
}
}
pr_debug("Command line is: %s\n", (char*)data);
/* break now */
return 1;
}
一个典型的dts配置格式如下:
/ {
chosen {
bootargs = "rcupdate.rcu_expedited=1";
};
......
};
我们接下来看parse_early_param,用来解析bootargs中定义的early param。
void __init parse_early_param(void)
{
static int done __initdata;
static char tmp_cmdline[COMMAND_LINE_SIZE] __initdata;
if (done) //注意这里有个done flag,在一次启动过程中,该函数可能会被多次调用,但只会执行一次
return; //如果后面再次调用会检测到done flag,然后就直接return了
/* All fall through to do_early_param. */
strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE); //注意:这里需要把boot_command_line拷贝到tmp_cmdline
parse_early_options(tmp_cmdline); //因为后面的解析动作会破坏tmp_cmdline中的数据,所以才有了前面一步copy动作
done = 1;
}
static int __init do_early_param(char *param, char *val,
const char *unused, void *arg)
{
const struct obs_kernel_param *p;
for (p = __setup_start; p < __setup_end; p++) {
if ((p->early && parameq(param, p->str)) || //early是否置为1
(strcmp(param, "console") == 0 &&
strcmp(p->str, "earlycon") == 0)
) {
if (p->setup_func(val) != 0)
pr_warn("Malformed early option '%s'\n", param);
}
}
/* We accept everything at this stage. */
return 0;
}
void __init parse_early_options(char *cmdline)
{
parse_args("early options", cmdline, NULL, 0, 0, 0, NULL,
do_early_param);
}
do_early_param
会从__setup_start
到__setup_end
区域进行搜索,这个区域其实就是前面说的__section(.init.setup)
,并找到对应的obs_kernel_param
结构数组,轮询其中定义的成员,如果early被设置为1,则执行对应的setup_func
,而对于early没有设置为1的obs_kernel_param
数组成员,则留到后面去执行。
经过这个过程可以知道,想要在cmdline中去传参数来执行特定操作,必须要在kernel代码中定义对应的解析函数,也就是obs_kernel_param结构,此参数才有可能会被解析。
前面是解析的cmdline中early=1类型的param,那么early=0的param在哪里进行解析呢?
after_dashes = parse_args("Booting kernel",
static_command_line, __start___param,
__stop___param - __start___param,
-1, -1, NULL, &unknown_bootoption);//执行普通的非early类型的cmdline的解析
在start_kernel函数中,这里最终会调用到unknown_bootoption函数,用来解析cmdline param。该函数中会执行early=0类型param的解析动作:
static int __init unknown_bootoption(char *param, char *val,
const char *unused, void *arg)
{
repair_env_string(param, val, unused, NULL);
/* Handle obsolete-style parameters */
if (obsolete_checksetup(param)) //该函数是最终解析early=0类型param的
return 0;
/* Unused module parameter. */
if (strchr(param, '.') && (!val || strchr(param, '.') < val))
return 0;
if (panic_later)
return 0;
if (val) {
/* Environment option */
unsigned int i;
for (i = 0; envp_init[i]; i++) {
if (i == MAX_INIT_ENVS) {
panic_later = "env";
panic_param = param;
}
if (!strncmp(param, envp_init[i], val - param))
break;
}
envp_init[i] = param;
} else {
/* Command line option */
unsigned int i;
for (i = 0; argv_init[i]; i++) {
if (i == MAX_INIT_ARGS) {
panic_later = "init";
panic_param = param;
}
}
argv_init[i] = param;
}
return 0;
}
static int __init obsolete_checksetup(char *line)
{
const struct obs_kernel_param *p;
int had_early_param = 0;
p = __setup_start;
do {
int n = strlen(p->str);
if (parameqn(line, p->str, n)) {
if (p->early) { //如果early=1,跳过,继续轮询
/* 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) { //如果setup_func不存在,就停止
pr_warn("Parameter %s is obsolete, ignored\n",
p->str);
return 1;
} else if (p->setup_func(line + n)) //循环执行setup_func
return 1;
}
p++;
} while (p < __setup_end);
return had_early_param;
}
由此,整个cmdline的解析就完成了。
评论