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

原创 2018年04月17日 20:02:30

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的,也就是boot传给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的解析就完成了。


版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/rikeyone/article/details/79979887

基于PMBOK5零基础学习pmp项目管理

为什么别人做事多有板有眼的,管理事务有条不紊? 其实您也可以。通过学习和自我提升,系统地学习,老师带着您走进项目管理的世界。 本课程主要针对零基础或未系统学习过项目管理的同学。 内容涵盖:一般性管理知识和技能、项目管理基本概念、范围管理、时间管理、成本管理、人力资源管理、风险管理、质量管理、沟通管理、采购管理、干系人管理。
  • 2016年10月08日 14:34

SSM框架(Maven项目)-利用 limit 分页实例

SSM框架(Maven项目)-limit 分页实例 利用 SQL 语句中的 limit 实现分页。 select id="findByPage" parameterType="Map" ...
  • Peng_Hong_fu
  • Peng_Hong_fu
  • 2016-12-14 22:03:10
  • 15692

微软EFI白皮书 Microsoft Extensible Firmware Initiative FAT32 File System Specification

https://msdn.microsoft.com/zh-cn/windows/hardware/gg463080 微软EFI白皮书 Microsoft Extensible Firmware...
  • xcntime
  • xcntime
  • 2016-05-21 09:39:46
  • 701

GitHub上史上最全的Android开源项目分类汇总

http://itindex.net/detail/51896-github-android-%E5%BC%80%E6%BA%90 今天在看博客的时候,无意中发现了@Trinea在G...
  • yuxiaohui78
  • yuxiaohui78
  • 2015-08-12 06:03:46
  • 6256

c++中vector向量几种情况的总结(向量指针,指针的向量)

1.标准库vector类型 vector 是同一种类型的对象的集合,每个对象都有一个对应的整数索引值。标准库将负责管理与存储元素相关的内存。我们把 vector 称为容器,是因为它可以包含其他对象。...
  • s9434
  • s9434
  • 2016-04-03 21:35:46
  • 30643

344_Reverse String

class Solution { public: string reverseString(string s) { int len = s.length(); ...
  • Calliope1997
  • Calliope1997
  • 2016-08-12 16:04:22
  • 1083

进程概述和内存分配

进程 一个进程的内存布局是什么样的? 每个进程所所分配的内存由很多部分组成,通常我们称之为段,一般会有如下段: 文本段 包含了进程执行的程序机器语言指令,文本段具有只读属性,以防止进程通过错误指...
  • xueli1991
  • xueli1991
  • 2016-06-16 17:28:08
  • 1698

python +selenium+phantomjs 登录爬取新浪微博动态js页面

登录新浪微博最近新浪微博好烦,都取消不了验证码这个难搞得东西,而且跳来跳去,一改版以前的代码就都不能用了。目前整理的资料有三种方法: 1. 设Cookie:简单粗暴,免去了模拟登录的好多麻烦,只是要...
  • Crystal_Zero
  • Crystal_Zero
  • 2016-04-14 20:17:15
  • 12051

Real-time Operation System Course: 解密字符串

#include #define N 8 void decode(char original_message[], char resulting_message[]){ int i, j,...
  • rlikai
  • rlikai
  • 2015-01-18 08:56:08
  • 14116

Git用户手册--Git 工具

7.1 Git 工具 - 选择修订版本 现在,你已经学习了管理或者维护 Git 仓库、实现代码控制所需的大多数日常命令和工作流程。 你已经尝试了跟踪和提交文件的基本操作,并且发挥了暂存区和轻...
  • moshenglv
  • moshenglv
  • 2016-07-23 19:13:02
  • 2550
收藏助手
不良信息举报
您举报文章:结合early_param/__setup/__setup_param来学习cmdline的解析(基于kernel-4.9)
举报原因:
原因补充:

(最多只允许输入30个字)