Linux kernel parses cmdline

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, &param_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中构建解析结构体。