利用工作之便,今天研究了kernel下cmdline参数解析过程,记录在此,与大家共享,转载请注明出处,谢谢。
    
     
    
   
    
     
      Kernel 版本号:3.4.55
     
    
   
    
     Kernel启动时会解析cmdline,然后根据这些参数如console root来进行配置运行。
    
   
    
     
    
   
    
     Cmdline是由bootloader传给kernel,如uboot,将需要传给kernel的参数做成一个tags链表放在ram中,将首地址传给kernel,kernel解析tags来获取cmdline等信息。
    
   
    
     Uboot传参给kernel以及kernel如何解析tags可以看我的另一篇博文,链接如下:
    
   
    
     http://blog.csdn.net/skyflying2012/article/details/35787971
     
    
   
    
     
    
   
   
    今天要分析的是kernel在获取到cmdline之后如何对cmdline进行解析。
    
    
     
      依据我的思路(时间顺序,如何开始,如何结束),首先看kernel下2种参数的注册。
      
      第一种是kernel通用参数,如console=ttyS0,115200  root=/rdinit/init等。这里以console为例。
      
     
    
   
    
     
      第二种是kernel下各个driver中需要的参数,在写driver中,如果需要一些启动时可变参数。可以在driver最后加入module_param()来注册一个参数,kernel启动时由cmdline指定该参数的值。
     
    
   
    
     
      这里以drivers/usb/gadget/serial.c中的use_acm参数为例(这个例子有点偏。。因为最近在调试usb虚拟串口)
     
    
   
   
    
     一 kernel通用参数
    
   
   
    
     对于这类通用参数,kernel留出单独一块data段,叫
     
      
       .ini.setup段
      
     
     。在arch/arm/kernel/vmlinux.lds中:
    
   
.init.data : {
  *(.init.data) *(.cpuinit.data) *(.meminit.data) *(.init.rodata) *(.cpuinit.rodata) *(.meminit.rodata) . = ALIGN(32); __dtb_star
 . = ALIGN(16); __setup_start = .; *(.init.setup) __setup_end = .;
  __initcall_start = .; *(.initcallearly.init) __initcall0_start = .; *(.initcall0.init) *(.initcall0s.init) __initcall1_start =
  __con_initcall_start = .; *(.con_initcall.init) __con_initcall_end = .;
  __security_initcall_start = .; *(.security_initcall.init) __security_initcall_end = .;
  . = ALIGN(4); __initramfs_start = .; *(.init.ramfs) . = ALIGN(8); *(.init.ramfs.info)
 }
    
     可以看到
     
      init.setup
     
     
      段起始
     
     
      __setup_start
     
     
      和结束
     
     
      __setup_end
     
     
      。
     
    
   
    
     .init.setup
     
      段中存放的就是
     
     
      kernel
     
     
      通用参数和对应处理函数的映射表。在
     
     
      include/linux/init.h
     
     
      中
     
    
   
struct obs_kernel_param {
    const char *str;
    int (*setup_func)(char *);
    int early;
};
/*
 * 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)
    
     可以看出宏定义
     
      __setup
     
     
      以及
     
     
      early_param
     
     
      定义了
     
     
      obs_kernel_param
     
     
      结构体,该结构体存放参数和对应处理函数,存放在
     
     
      .init.setup
     
     
      段中。
     
    
   
    
     可以想象,如果多个文件中调用该宏定义,在链接时就会根据链接顺序将定义的
     
      obs_kernel_param
     
     
      放到
     
     
      .init.setup
     
     
      段中。
     
    
   
    
     以
     
      console
     
     
      为例,在
     
     
      /kernel/printk.c
     
     
      中,如下:
     
    
   
static int __init console_setup(char *str)
{
.......
}
__setup("console=", console_setup);
    
     __setup
     
      宏定义展开,如下:
     
    
   
Static struct obs_kernel_param __setup_console_setup 
__used_section(.init.setup) __attribute__((aligned((sizeof(long)))) = {
.name = “console=”,
.setup_func = console_setup,
.early = 0
}
    
     __setup_console_setup
     
      编译时就会链接到
     
     
      .init.setup
     
     
      段中,
     
     
      kernel
     
     
      运行时就会根据
     
     
      cmdline
     
     
      中的参数名与
     
     
      .init.setup
     
     
      段中
     
     
      obs_kernel_param
     
     
      的
     
     
      name
     
     
      对比。
     
    
   
    
     
      匹配则调用
     
     
      console-setup
     
     
      来解析该参数,
     
     
      console_setup
     
     
      的参数就是
     
     
      cmdline
     
     
      中
     
     
      console
     
     
      的值,这是后面参数解析的大体过程了。
     
    
   
    
    
   
    
     
      二
      
       driver
      
      
       自定义参数
      
     
    
   
    
     对于
     
      driver
     
     
      自定义参数,
     
     
      kernel
     
     
      留出
     
     
      rodata
     
     
      段一部分,叫
     
     
      
       
        __param
       
      
     
     
      
       
        段
       
      
      ,在
     
     
      arch/arm/kernel/vmlinux.lds
     
     
      中,如下:
     
    
   
__param : AT(ADDR(__param) - 0) { __start___param = .; *(__param) __stop___param = .; }
    
     该段放在
     
      .rodata
     
     
      段中。
     
    
   
    
     那该段中存放的是什么样的数据呢?
    
   
    
     Driver
     
      中使用
     
     
      module_param
     
     
      来注册参数,跟踪这个宏定义,最终就会找到对
     
     
      __param
     
     
      段的操作函数如下:
     
    
   
/* 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 } }
........
#define module_param(name, type, perm)              \
    module_param_named(name, name, type, perm)
#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)
#define module_param_cb(name, ops, arg, perm)                     \
    __module_param_call(MODULE_PARAM_PREFIX, name, ops, arg, perm, -1)
    
     以
     
      driver/usb/gadget/serial.c
     
     
      中的
     
     
      use_acm
     
     
      为例,如下:
     
    
   
static bool use_acm = true;
module_param(use_acm, bool, 0);
    
     Module_param
     
      展开到
     
     
      __module_param_call
     
     
      ,如下:
     
    
   
Static bool use_acm = true;
Param_check_bool(use_acm, &(use_acm));
__module_param_call(MODULE_PARAM_PREFIX, use_acm, ¶m_ops_bool, &(use_acm, 0, -1));
__MODULE_PARAM_TYPE(use_acm, bool);
    
     将
     
      __module_param_call
     
     
      展开,可以看到是定义了结构体
     
     
      kernel_param
     
     
      ,如下:
     
    
   
Static struct kernel_param __moduleparam_const __param_use_acm 
 __used   __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) = {
.name = MODULE_PARAM_PREFIX#use_acm,
.ops = ¶m_ops_bool,
.Perm=0,
.level = -1.
.arg = &use_acm
}
    
     很清楚,跟
     
      .init.setup
     
     
      段一样,
     
     
      kernel
     
     
      链接时会根据链接顺序将定义的
     
     
      kernel_param
     
     
      放在
     
     
      __param
     
     
      段中。
     
    
   
    
     
      
       Kernel_param
       
        有
       
       
        3
       
       
        个成员变量需要注意:
       
      
     
    
   
    
     (1)
    
   
    
     ops=param_ops_bool
     
      ,是
     
     
      kernel_param_ops
     
     
      结构体,定义如下:
     
    
   
struct kernel_param_ops param_ops_bool = {
    .set = param_set_bool,
    .get = param_get_bool,
};
    
     这
     
      2
     
     
      个成员函数分别去设置和获取参数值
     
    
   
    
     
      
       在
       
        kernel/param.c
       
       
        中可以看到
       
       
        kernel
       
       
        默认支持的driver参数类型有
       
       
        bool byte short ushort int uint long ulong string
       
       
        (字符串)
       
       
        charp
       
       
        (字符串指针)
       
       
        array
       
       
        等。
       
      
     
    
   
    
     
      
       对于默认支持的参数类型,
       
        param.c
       
       
        中提供了
       
       
        kernel_param_ops
       
       
        来处理相应类型的参数。
       
      
     
    
   
    
     (
     
      2
     
     
      )
     
    
   
    
     Arg = &use_acm
     
      ,宏定义展开,可以看到
     
     
      arg
     
     
      中存放
     
     
      use_acm
     
     
      的地址。参数设置函数
     
     
      param_set_bool
     
     
      (
     
     
      const char *val, const struct kernel_param *kp
     
     
      )
     
    
   
    
     将
     
      val
     
     
      值设置到
     
     
      kp->arg
     
     
      地址上,也就是改变了
     
     
      use_acm
     
     
      的值,从而到达传递参数的目的。
     
    
   
    
     (3)
    
   
    
     .name=MODULE_PARAM_PREFIX#use_acm,
     
      定义了该
     
     
      kernel_param
     
     
      的
     
     
      name
     
     
      。
     
    
   
    
     MODULE_PARAM_PREFIX
     
      非常重要,定义在
     
     
      include/linux/moduleparam.h
     
     
      中:
     
    
   
* You can override this manually, but generally this should match the
   module name. */
#ifdef MODULE
#define MODULE_PARAM_PREFIX /* empty */
#else
#define MODULE_PARAM_PREFIX KBUILD_MODNAME "."
#endif
    
     
      
       如果我们是模块编译(make modules),则
       
        MODULE_PARAM_PREFIX
       
       
        为
       
       
        empty
       
       
        。
       
      
     
    
   
    
     
      
       
        在模块传参时,参数名为use_acm,如insmod g_serial.ko use_acm=0
       
      
     
    
   
    
     
      正常编译
     
     
      kernel
     
     
      ,
     
     
      MODULE_PARAM_PREFIX
     
     
      为模块名
     
     
      +
     
     ”.”
    
   
    
     
      
       如果我们在传参时不知道自己的模块名是什么,可以在自己的驱动中加打印,将MODULE_PARAM_PREFIX打印出来,来确定自己驱动的模块名。
      
     
    
   
    
     所以这里将
     
      serial.c
     
     
      编入
     
     
      kernel
     
     
      ,根据
     
     
      driver/usb/gadget/Makefile
     
     
      ,如下:
     
    
   
g_serial-y          := serial.o
....
obj-$(CONFIG_USB_G_SERIAL)  += g_serial.o
    
     
      
       最终是生成
       
        g_serial.o
       
       
        ,模块名为
       
       
        g_serial.ko
       
       
        。
       
      
      
       .name = g_serial.use_acm
      
      
       。
      
     
    
   
    
     
      
       kernel传参时,该参数名为g_serial.use_acm
      
     
    
   
    
     
      
       这样处理防止
       
        kernel
       
       
        下众多
       
       
        driver
       
       
        中出现重名的参数。
       
      
     
    
   
    
    
   
    
     
      
       可以看出,对于
       
        module_param
       
       
        注册的参数,如果是
       
       
        kernel
       
       
        默认支持类型,
       
       
        kernel
       
       
        会提供参数处理函数。
       
      
     
    
   
    
     
      
       如果不是
       
        kernel
       
       
        支持参数类型,则需要自己去实现
       
       
        param_ops##type
       
       
        了。
       
      
     
    
   
    
     
      
       这个可以看
       
        drivers/video/uvesafb.c
       
       
        中的
       
       
        scroll
       
       
        参数的注册(又有点偏。。。无意间找到的)。
       
      
     
    
   
    
    
   
    
     
      
       参数注册是在
       
        kernel
       
       
        编译链接时完成的(链接器将定义结构体放到
       
       
        .init.setup
       
       
        或
       
       
        __param
       
       
        中)
       
      
     
    
   
    
     接下来需要分析
    
    
     kernel
    
    
     启动时如何对传入的
    
    
     cmdline
    
    
     进行分析。
    
   
    
     
      
       
      
     
    
   
    
     
      
       三 kernel对cmdline的解析
      
     
    
   
    
     根据我之前写的博文可知,
     
      start_kernel
     
     
      中
     
     
      setup_arch
     
     
      中解析
     
     
      tags
     
     
      获取
     
     
      cmdline
     
     
      ,拷贝到
     
     
      boot_command_line
     
     
      中。我们接着往下看
     
     
      start_kernel
     
     
      。
     
    
   
    
     调用
     
      setup_command_line
     
     
      ,将
     
     
      cmdline
     
     
      拷贝
     
     
      2
     
     
      份,放在
     
     
      saved_command_line static_command_line
     
     
      。
     
    
   
    
     下面调用
     
      parse_early_param(),
     
     
      如下:
     
    
   
void __init parse_early_options(char *cmdline)
{
    parse_args("early options", cmdline, NULL, 0, 0, 0, do_early_param);
}
/* Arch code calls this early on, or if not, just before other parsing. */
void __init parse_early_param(void)
{
    static __initdata int done = 0;
    static __initdata char tmp_cmdline[COMMAND_LINE_SIZE];
    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;
}
Parse_early_param拷贝cmdline到tmp_cmdline中一份,最终调用parse_args,如下:
/* Args looks like "foo=bar,bar2 baz=fuz wiz". */
int parse_args(const char *name,
           char *args,
           const struct kernel_param *params,
           unsigned num,
           s16 min_level,
           s16 max_level,
           int (*unknown)(char *param, char *val))
{
    char *param, *val;
    pr_debug("Parsing ARGS: %s\n", args);
    /* Chew leading spaces */
    args = skip_spaces(args);
    while (*args) {
        int ret;
        int irq_was_disabled;
        args = next_arg(args, ¶m, &val);
        irq_was_disabled = irqs_disabled();
        ret = parse_one(param, val, params, num,
                min_level, max_level, unknown);
        if (irq_was_disabled && !irqs_disabled()) {
            printk(KERN_WARNING "parse_args(): option '%s' enabled "
                    "irq's!\n", param);
        }
        switch (ret) {
        case -ENOENT:
            printk(KERN_ERR "%s: Unknown parameter `%s'\n",
                   name, param);
            return ret;
        case -ENOSPC:
            printk(KERN_ERR
                   "%s: `%s' too large for parameter `%s'\n",
                   name, val ?: "", param);
            return ret;
        case 0:
            break;
        default:
            printk(KERN_ERR
                   "%s: `%s' invalid for parameter `%s'\n",
                   name, val ?: "", param);
            return ret;
        }
    }
    /* All parsed OK. */
    return 0;
}
.....
void __init parse_early_options(char *cmdline)
{
    parse_args("early options", cmdline, NULL, 0, 0, 0, do_early_param);
}
    
     Parse_args
     
      遍历
     
     
      cmdline
     
     
      ,按照空格切割获取参数,对所有参数调用
     
     
      next_arg
     
     
      获取参数名
     
     
      param
     
     
      和参数值
     
     
      val
     
     
      。如
     
     
      console=ttyS0,115200
     
     
      ,则
     
     
      param=console
     
     
      ,
     
     
      val=ttyS0,115200
     
     
      。调用
     
     
      parse_one
     
     
      。如下:
     
    
   
static int parse_one(char *param,
             char *val,
             const struct kernel_param *params,
             unsigned num_params,
             s16 min_level,
             s16 max_level,
             int (*handle_unknown)(char *param, char *val))
{
    unsigned int i;
    int err;
    /* Find parameter */
    for (i = 0; i < num_params; i++) {
        if (parameq(param, params[i].name)) {
            if (params[i].level < min_level
                || params[i].level > max_level)
                return 0;
            /* No one handled NULL, so do it here. */
            if (!val && params[i].ops->set != param_set_bool
                && params[i].ops->set != param_set_bint)
                return -EINVAL;
            pr_debug("They are equal!  Calling %p\n",
                   params[i].ops->set);
            mutex_lock(¶m_lock);
            err = params[i].ops->set(val, ¶ms[i]);
            mutex_unlock(¶m_lock);
            return err;
        }
    }
    if (handle_unknown) {
        pr_debug("Unknown argument: calling %p\n", handle_unknown);
        return handle_unknown(param, val);
    }
    pr_debug("Unknown argument `%s'\n", param);
    return -ENOENT;
}   
    
   
    
     由于从
     
      parse_early_options
     
     
      传入的
     
     
      num_params=0
     
     
      ,所以
     
     
      parse_one
     
     
      是直接走的最后
     
     
      handle_unknown
     
     
      函数。该函数是由
     
     
      parse-early_options
     
     
      传入的
     
     
      do_early_param
     
     
      。如下:
     
    
   
static int __init do_early_param(char *param, char *val)
{
    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)
                printk(KERN_WARNING
                       "Malformed early option '%s'\n", param);
        }
    }
    /* We accept everything at this stage. */
    return 0;
}   
    
   
    
     Do_early_param
     
      遍历
     
     
      .init.setup
     
     
      段,如果有
     
     
      obs_kernel_param
     
     
      的
     
     
      early
     
     
      为
     
     
      1
     
     
      ,或
     
     
      cmdline
     
     
      中有
     
     
      console
     
     
      参数并且
     
     
      obs_kernel_param
     
     
      有
     
     
      earlycon
     
     
      参数,则会调用该
     
     
      obs_kernel_param
     
     
      的
     
     
      setup
     
     
      函数来解析参数。
     
    
   
    
     
      
       Do_early_param
       
        会对
       
       
        cmdline
       
       
        中优先级较高的参数进行解析。我翻了下
       
       
        kernel
       
       
        源码找到一个例子,就是
       
       
        arch/arm/kernel/early_printk.c
       
       
        ,利用
       
       
        cmdline
       
       
        参数
       
       
        earlyprintk
       
       
        来注册最早的一个
       
       
        console
       
       
        ,有兴趣大家可以参考下。
       
      
     
    
   
    
     如果想
     
      kernel
     
     
      启动中尽早打印输出,方便调试,可以注册
     
     
      str
     
     
      为
     
     
      earlycon
     
     
      的
     
     
      obs_kernel_param
     
     
      。
     
    
   
    
     在其
     
      setup
     
     
      参数处理函数中
     
     
      register_console
     
     
      ,注册一个早期的
     
     
      console
     
     
      ,从而是
     
     
      printk
     
     
      信息正常打印,这个在后面我还会总结一篇
     
     
      kernel
     
     
      打印机制来说这个问题。
     
    
   
    
     
      
       
        do_early_param
       
       
        是为
       
       
        kernel
       
       
        中需要尽早配置的功能(如
       
       
        earlyprintk  earlycon
       
       
        )做
       
       
        cmdline
       
       
        的解析。
       
      
     
    
   
    
     Do_early_param
     
      就说道这里,该函数并没有处理我们经常使用的
     
     
      kernel
     
     
      通用参数和
     
     
      driver
     
     
      自定义参数。接着往下看。代码如下:
     
    
   
    setup_arch(&command_line);
    mm_init_owner(&init_mm, &init_task);
    mm_init_cpumask(&init_mm);
    setup_command_line(command_line);
    setup_nr_cpu_ids();
    setup_per_cpu_areas();
    smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
    build_all_zonelists(NULL);
    page_alloc_init();
    printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);
    parse_early_param();
    parse_args("Booting kernel", static_command_line, __start___param,
           __stop___param - __start___param,
           -1, -1, &unknown_bootoption);
    
     Parse_early_param
     
      结束后,
     
     
      start_kernel
     
     
      调用了
     
     
      parse_args
     
     
      。这次调用,不像
     
     
      parse_early_param
     
     
      中调用
     
     
      parse_args
     
     
      那样
     
     
      kernel_param
     
     
      指针都为
     
     
      NULL
     
     
      ,而是指定了
     
     
      .__param
     
     
      段。
     
    
   
    
     回到上面看
     
      parse_args
     
     
      函数,
     
     
      params
     
     
      参数为
     
     
      .__param
     
     
      段起始地址,
     
     
      num
     
     
      为
     
     
      kernel_param
     
     
      个数。
     
    
   
    
     Min_level,max_level
     
      都为
     
     
      -1.unknown=unknown_bootoption
     
    
   
    
     Parse_args
     
      还是像之前那样,遍历
     
     
      cmdline
     
     
      ,分割获取每个参数的
     
     
      param
     
     
      和
     
     
      val
     
     
      ,对每个参数调用
     
     
      parse_one
     
     
      。
     
    
   
    
     回看
     
      Parse_one
     
     
      函数源码:
     
    
   
    
     
      
       (
       
        1
       
       
        )
       
       
        parse_one
       
       
        首先会遍历
       
       
        .__param
       
       
        段中所有
       
       
        kernel_param,
       
       
        将其
       
       
        name
       
       
        与参数的
       
       
        param
       
       
        对比,同名则调用该
       
       
        kernel_param
       
       
        成员变量
       
       
        kernel_param_ops
       
       
        的
       
       
        set
       
       
        方法来设置参数值。
       
      
     
    
   
    
     
      
       联想前面讲
       
        driver
       
       
        自定义参数例子
       
       
        use_acm,cmdline
       
       
        中有参数
       
       
        g_serial.use_acm=0
       
       
        ,则在
       
       
        parse_one
       
       
        中遍历匹配在
       
       
        serial.c
       
       
        中注册的
       
       
        __param_use_acm
       
       
        ,调用
       
       
        param_ops_bool
       
       
        的
       
       
        set
       
       
        函数,从而设置
       
       
        use_acm=0.
       
      
     
    
   
    
     
      
       (2)如果
       
        parse_args
       
       
        传给
       
       
        parse_one
       
       
        是
       
       
        kernel
       
       
        通用参数,如
       
       
        console=ttyS0,115200
       
       
        。则
       
       
        parse_one
       
       
        前面遍历
       
       
        .__param
       
       
        段不会找到匹配的
       
       
        kernel_param
       
       
        。就走到后面调用
       
       
        handle_unknown
       
       
        。就是
       
       
        parse_args
       
       
        传来的
       
       
        unknown_bootoption
       
       
        ,代码如下:
       
      
     
    
   
/*
 * Unknown boot options get handed to init, unless they look like
 * unused parameters (modprobe will find them in /proc/cmdline).
 */
static int __init unknown_bootoption(char *param, char *val)
{
    repair_env_string(param, val);
    /* Handle obsolete-style parameters */
    if (obsolete_checksetup(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 = "Too many boot env vars at `%s'";
                panic_param = param;
            }
            if (!strncmp(param, envp_init[i], val - param))
                break;
        }
        envp_init[i] = param;
    } else {</span><span style="font-size:14px;">        /* Command line option */
        unsigned int i;
        for (i = 0; argv_init[i]; i++) {
            if (i == MAX_INIT_ARGS) {
                panic_later = "Too many boot init vars at `%s'";
                panic_param = param;
            }
        }
        argv_init[i] = param;
    }
    return 0;
}
    
     首先
     
      repair_env_string
     
     
      会将
     
     
      param val
     
     
      重新组合为
     
     
      param=val
     
     
      形式。
     
    
   
    
     Obsolete_checksetup
     
      则遍历
     
     
      -init_setup
     
     
      段所有
     
     
      obs_kernel_param
     
     
      ,如有
     
     
      param->str
     
     
      与
     
     
      param
     
     
      匹配,则调用
     
     
      param_>setup
     
     
      进行参数值配置。
     
    
   
    
     这里需要注意的一点是
     
      repair_env_string
     
     
      将
     
     
      param
     
     
      重新拼成了
     
     
      param=val
     
     
      形式。后面遍历匹配都是匹配的
     
     
      ”param=”
     
     
      而不是
     
     
      “param”
     
     
      。
     
    
   
    
     如之前分析
     
      kernel
     
     
      通用参数所举例子,
     
     
      __setup(“console=”, console_setup)
     
     
      。
     
    
   
    
     Console=ttyS0
     
      ,
     
     
      115200,obsolete_checksetup
     
     
      是匹配前面
     
     
      console=
     
     
      ,如果匹配,则跳过
     
     
      console=
     
     
      ,获取到其值
     
     
      ttyS0,115200
     
     
      ,调用其具体的
     
     
      setup
     
     
      函数来解析设置参数值。
     
    
   
    
     
      
       可以想象,
       
        parse_one
       
       
        对于
       
       
        parse_args
       
       
        传来的每一个
       
       
        cmdline
       
       
        参数都会将
       
       
        .__param
       
       
        以及
       
       
        -init.setup
       
       
        段遍历匹配,匹配到
       
       
        str
       
       
        或
       
       
        name
       
       
        一致,则调用其相应的
       
       
        set
       
       
        或
       
       
        setup
       
       
        函数进行参数值解析或设置。
       
      
     
    
   
    
     Start_kernel
     
      中
     
     
      Parse_args
     
     
      结束,
     
     
      kernel
     
     
      的
     
     
      cmdline
     
     
      就解析完成!
     
    
   
    
    
   
    
     总结下
     
      kernel
     
     
      的参数解析:
     
    
   
    
     
      
       (
       
        1
       
       
        )
       
       
        kernel
       
       
        编译链接,利用
       
       
        .__param .init.setup
       
       
        段将
       
       
        kernel
       
       
        所需参数(driver及通用)和对应处理函数的映射表(
       
       
        obs_kernel_param  kernel_param
       
       
        结构体)存放起来。
       
      
     
    
   
    
     
      
       (
       
        2
       
       
        )
       
       
        Kernel
       
       
        启动,
       
       
        do_early_param
       
       
        处理
       
       
        kernel
       
       
        早期使用的参数(如
       
       
        earlyprintk earlycon
       
       
        )
       
      
     
    
   
    
     
      
       (3)
       
        parse_args
       
       
        对
       
       
        cmdline
       
       
        每个参数都遍历
       
       
        __param .init.setup
       
       
        进行匹配,匹配成功,则调用对应处理函数进行参数值的解析和设置。
       
      
     
    
   
    
     
      
     
    
   
    
     
      还有一点很值得思考,kernel下对于这种映射处理函数表方式还有很多使用。比如之前博文中uboot传参给kernel,kernel对于不同tags的处理函数也是以该种方式来映射的。
     
    
   
    
     
      kernel下driver私有结构体的回调处理函数也有这个思想哇!
     
    
   
   
   
  
 
