[转载]结合early_param/__setup/__setup_param来学习cmdline的解析(基于kernel-4.9)

adtxl
2022-11-10 / 0 评论 / 309 阅读 / 正在检测是否收录...

版权声明:本文为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的解析就完成了。

0

评论 (0)

取消