Linux kernel利用静态数组存储cmdline,arm默认1024 Bytes,arm64默认2048 Bytes。
./arch/arm/include/uapi/asm/setup.h:19:#define COMMAND_LINE_SIZE 1024
./arch/arm64/include/uapi/asm/setup.h:24:#define COMMAND_LINE_SIZE 2048
restore cmdline
kernel启动后获取bootargs的flow如下:
+start_kernel
|--+setup_arch
|----+setup_machine_fdt
|------+of_scan_flat_dt
|--------+early_init_dt_scan_chosen
|----------+of_get_flat_dt_prop
从dtb里拿到bootargs字串后,存储到boot_command_line全局变量中:
[init/main.c]
/* Untouched command line saved by arch-specific code. */
char __initdata boot_command_line[COMMAND_LINE_SIZE];
在架构相关的setup_arch函数中,会将boot_command_line copy 到cmd_line变量中,后者定义在架构相关目录下:
[arch/arm/kernel/setup.c]
static char __initdata cmd_line[COMMAND_LINE_SIZE];
不过这两个存储位置都是__initdata类型,意味着这部分内存会被回收,所以要保存cmdline以供上层调用,需要另存:
+start_kernel
|--+setup_command_line
上述调用,会将cmdline转存到saved_command_line中,后者为/proc/cmdline提供数据支持。
kernel参数分成三种类型:early_param,module_param_named,__setup。cmdline会被逐层进行解析。
early_param parse
early_param注册的参数是最早被解析的:
+start_kernel
|--+setup_arch
|----+parse_early_param
|------+parse_early_options
|--------+parse_args
|----------+handle_unknown (do_early_param)
+start_kernel
|--+parse_early_param
|----+parse_early_options
|------+parse_args
|--------+handle_unknown (do_early_param)
[init/main.c]
/* Check for early params. */
static int __init do_early_param(char *param, char *val, const char *unused)
{
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;
}
[include/asm-generic/vmlinux.lds.h]
#define INIT_SETUP(initsetup_align) \
. = ALIGN(initsetup_align); \
VMLINUX_SYMBOL(__setup_start) = .; \
*(.init.setup) \
VMLINUX_SYMBOL(__setup_end) = .;
do_early_param利用.init.setup段的结构进行解析cmdline,而且要求p->early置位。
来看下early_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 }
#define __setup(str, fn) \
__setup_param(str, fn, fn, 0)
/* 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)
从上面的code,可以看出early_param和__setup都会构建结构体在.init.setup中,不同的是early_param会置位early flag,所以在do_early_param中,会对这部分进行解析。
module_param_named
[include/linux/moduleparam.h]
/**
* module_param_named - typesafe helper for a renamed module/cmdline parameter
* @name: a valid C identifier which is the parameter name.
* @value: the actual lvalue to alter.
* @type: the type of the parameter
* @perm: visibility in sysfs.
*
* Usually it's a good idea to have variable names and user-exposed names the
* same, but that's harder if the variable must be non-static or is inside a
* structure. This allows exposure under a different name.
*/
#define module_param_named(name, value, type, perm) \
param_check_##type(name, &(value)); \
module_param_cb(name, ¶m_ops_##type, &value, perm); \
__MODULE_PARM_TYPE(name, #type)
/**
* module_param_cb - general callback for a module/cmdline parameter
* @name: a valid C identifier which is the parameter name.
* @ops: the set & get operations for this parameter.
* @perm: visibility in sysfs.
*
* The ops can have NULL set or get functions.
*/
#define module_param_cb(name, ops, arg, perm) \
__module_param_call(MODULE_PARAM_PREFIX, name, ops, arg, perm, -1)
/* This is the fundamental function for registering boot/module
parameters. */
#define __module_param_call(prefix, name, ops, arg, perm, level) \
/* Default value instead of permissions? */ \
static int __param_perm_check_##name __attribute__((unused)) = \
BUILD_BUG_ON_ZERO((perm) < 0 || (perm) > 0777 || ((perm) & 2)) \
+ BUILD_BUG_ON_ZERO(sizeof(""prefix) > MAX_PARAM_PREFIX_LEN); \
static const char __param_str_##name[] = prefix #name; \
static struct kernel_param __moduleparam_const __param_##name \
__used \
__attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) \
= { __param_str_##name, ops, perm, level, { arg } }
从上面看出,module_param_named注册结构体到__param section,看下kernel parse的过程:
+start_kernel
|--+parse_early_param
|----+parse_args("Booting kernel", static_command_line, __start___param,
| __stop___param - __start___param,
| -1, -1, &unknown_bootoption);
|------+parse_one
|--------+params[i].ops->set
[include/asm-generic/vmlinux.lds.h]
/* Built-in module parameters. */ \
__param : AT(ADDR(__param) - LOAD_OFFSET) { \
VMLINUX_SYMBOL(__start___param) = .; \
*(__param) \
VMLINUX_SYMBOL(__stop___param) = .; \
} \
parse_args的传入参数为__param section,parse module_param_named传入的参数,而parse函数定义为:
[kernel/params.c]
/* Lazy bastard, eh? */
#define STANDARD_PARAM_DEF(name, type, format, tmptype, strtolfn) \
int param_set_##name(const char *val, const struct kernel_param *kp) \
{ \
tmptype l; \
int ret; \
\
ret = strtolfn(val, 0, &l); \
if (ret < 0 || ((type)l != l)) \
return ret < 0 ? ret : -EINVAL; \
*((type *)kp->arg) = l; \
return 0; \
} \
int param_get_##name(char *buffer, const struct kernel_param *kp) \
{ \
return sprintf(buffer, format, *((type *)kp->arg)); \
} \
struct kernel_param_ops param_ops_##name = { \
.set = param_set_##name, \
.get = param_get_##name, \
}; \
EXPORT_SYMBOL(param_set_##name); \
EXPORT_SYMBOL(param_get_##name); \
EXPORT_SYMBOL(param_ops_##name)
STANDARD_PARAM_DEF(byte, unsigned char, "%c", unsigned long, strict_strtoul);
STANDARD_PARAM_DEF(short, short, "%hi", long, strict_strtol);
STANDARD_PARAM_DEF(ushort, unsigned short, "%hu", unsigned long, strict_strtoul);
STANDARD_PARAM_DEF(int, int, "%i", long, strict_strtol);
STANDARD_PARAM_DEF(uint, unsigned int, "%u", unsigned long, strict_strtoul);
STANDARD_PARAM_DEF(long, long, "%li", long, strict_strtol);
STANDARD_PARAM_DEF(ulong, unsigned long, "%lu", unsigned long, strict_strtoul);
其他的:
./kernel/params.c:297:struct kernel_param_ops param_ops_charp = {
./kernel/params.c:322:struct kernel_param_ops param_ops_bool = {
./kernel/params.c:349:struct kernel_param_ops param_ops_invbool = {
./kernel/params.c:372:struct kernel_param_ops param_ops_bint = {
./kernel/params.c:496:struct kernel_param_ops param_ops_string = {
__setup
+start_kernel
|--+parse_early_param
|----+parse_args("Booting kernel", static_command_line, __start___param,
| __stop___param - __start___param,
| -1, -1, &unknown_bootoption);
|------+unknown_bootoption
|--------+obsolete_checksetup
[init/main.c]
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) {
/* 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) {
pr_warn("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;
}
[include/asm-generic/vmlinux.lds.h]
#define INIT_SETUP(initsetup_align) \
. = ALIGN(initsetup_align); \
VMLINUX_SYMBOL(__setup_start) = .; \
*(.init.setup) \
VMLINUX_SYMBOL(__setup_end) = .;
__setup函数之前有提过,在.init.setup中构建解析结构体。