内核启动参数cmdline
背景
我们常用cmdline去控制某些功能的开启或关闭,或是传递一些参数。
在系统下,我们可以使用cat /proc/cmdline
来查看启动参数,
那uboot或是grub的启动参数cmdline是怎么传递解析的呢?
传递与解析
以ARM64平台,early_param()
为例
流程图:
early_param --> obs_kernel_param(.init.setup段) ----
|
---> do_early_param (匹配,处理,执行`early_param`中的处理函数)
|
(uefi) -------> fdt ------> boot_command_line ----
early_param
以常用的loglevel
参数为例,
static int __init loglevel(char *str)
{
int newlevel;
/*
* Only update loglevel value when a correct setting was passed,
* to prevent blind crashes (when loglevel being set to 0) that
* are quite hard to debug
*/
if (get_option(&str, &newlevel)) {
console_loglevel = newlevel;
return 0;
}
return -EINVAL;
}
early_param("loglevel", loglevel);
early_param
的定义:
/*
* NOTE: fn is as per module_param, not __setup!
* Emits warning if fn returns non-zero.
*/
#define early_param(str, fn) \
__setup_param(str, fn, fn, 1)
当不是内核模块时,__setup_param()
定义如下,
当是内核模块时,__setup_param()
定义为空,会忽略;
内核模块的参数具体分析及补充见:内核模块参数
/*
* Only for really core code. See moduleparam.h for the normal way.
*
* Force the alignment so the compiler doesn't space elements of the
* obs_kernel_param "array" too far apart in .init.setup.
*/
#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 }
按照上面的宏定义展开后为:
static const char __setup_str_loglevel __initconst \ //__initconst: 存放到.init.rodata段
__aligned(1) = "loglevel";
static struct obs_kernel_param __setup_loglevel \
__used __section(.init.setup) \ //__section(.init.setup): 存放到.init.setup段, __used: 避免被编译器优化
__attribute__((aligned((sizeof(long))))) \ //对齐
= { "loglevel", loglevel, 1 }
struct obs_kernel_param
结构体定义为:
struct obs_kernel_param {
const char *str;
int (*setup_func)(char *);
int early;
};
do_early_param
Kernel初始化时
start_kernel(init/main.c)
--> parse_early_param
-->parse_early_options
--> do_early_param
do_early_param
该函数会遍历__setup_start
和 __setup_end
之间的所有结构体,然后将 cmdline中的各个参数与字符串str ”xxx”比较,如果匹配,那么调用setup_func
(early_xxx)函数。
具体实现如下:
/* Check for early params. */
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)) ||
(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;
}
拷贝boot_command_line
到tmp_cmdline
, 给parse_early_options
处理
/* Arch code calls this early on, or if not, just before other parsing. */
void __init parse_early_param(void)
{
static int done __initdata;
static char tmp_cmdline[COMMAND_LINE_SIZE] __initdata;
if (done)
return;
/* All fall through to do_early_param. */
strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
parse_early_options(tmp_cmdline);
done = 1;
}
void __init parse_early_options(char *cmdline)
{
parse_args("early options", cmdline, NULL, 0, 0, 0, NULL,
do_early_param);
}
parse_args
函数主要是用于分解参数,并将分解的每个参数交给do_early_param
处理。
实现如下:
/* Args looks like "foo=bar,bar2 baz=fuz wiz". */
char *parse_args(const char *doing,
char *args,
const struct kernel_param *params,
unsigned num,
s16 min_level,
s16 max_level,
void *arg,
int (*unknown)(char *param, char *val,
const char *doing, void *arg))
{
char *param, *val, *err = NULL;
/* Chew leading spaces */
args = skip_spaces(args);
if (*args)
pr_debug("doing %s, parsing ARGS: '%s'\n", doing, args);
while (*args) {
int ret;
int irq_was_disabled;
args = next_arg(args, ¶m, &val);
/* Stop at -- */
if (!val && strcmp(param, "--") == 0)
return err ?: args;
irq_was_disabled = irqs_disabled();
ret = parse_one(param, val, doing, params, num,
min_level, max_level, arg, unknown);
if (irq_was_disabled && !irqs_disabled())
pr_warn("%s: option '%s' enabled irq's!\n",
doing, param);
switch (ret) {
case 0:
continue;
case -ENOENT:
pr_err("%s: Unknown parameter `%s'\n", doing, param);
break;
case -ENOSPC:
pr_err("%s: `%s' too large for parameter `%s'\n",
doing, val ?: "", param);
break;
default:
pr_err("%s: `%s' invalid for parameter `%s'\n",
doing, val ?: "", param);
break;
}
err = ERR_PTR(ret);
}
return err;
}
boot_command_line
/* Untouched command line saved by arch-specific code. */
char __initdata boot_command_line[COMMAND_LINE_SIZE];
该变量boot_command_line
怎么来的呢?怎么传递获取的?
以acpi+uefi+arm64为例:
efi_entry(drivers/firmware/efi/libstub/arm-stub.c)
--> efi_convert_cmdline --从uefi中拿到cmdline
--> allocate_new_fdt_and_exit_boot
--> update_fdt --更新fdt
--> fdt_setprop --设置chosen节点下的bootargs
后面就和使用fdt方式是一样了, 具体见后面。
arm64+devicetree:
start_kernel(init/main.c)
--> setup_arch -- 对应不同架构的`setup_arch`
--> setup_machine_fdt(__fdt_pointer) -- devicetree的物理地址转成虚拟地址
--> early_init_dt_scan -- 扫描devicetree的节点
--> early_init_dt_scan_nodes
--> of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line) -- `early_init_dt_scan_chosen`中拷贝chosen节点下`bootargs`等到boot_command_line
__fdt_pointer
地址由bootloader启动kernel时通过x0,x21寄存器传递, 具体可见汇编代码:arch/arm64/kernel/head.S
efi_entry(efi-entry.S)-> start_kernel (arch/arm64/kernel/head.S)
扩展:__setup()
与early_param()
比较类似的还有一个:__setup()
。
但也有些不同:
- 处理顺序不一样:
early_param
宏注册的内核选项必须要在其他内核选项之前被处理。 - 对应的参数处理函数不一样:在函数
start_kernel
中,parse_early_param
处理early_param
定义的参数,parse_args
处理__setup
定义的参数
__setup
的源码定义,与early_param()
都是对应__setup_param()
函数,只是第三个参数不一样:
#define __setup(str, fn) __setup_param(str, fn, fn, 0)
参考
early_param:
https://blog.csdn.net/flc2762/article/details/108626144
boot_command_line:
https://blog.csdn.net/tiantao2012/article/details/54923232
https://blog.csdn.net/tiantao2012/article/details/54923552