作用

根据传入的字符串参数,与bootloader传递的参数(bootargs)进行匹配,从而调用传入的函数

示例

static int __init cmdline_test_setup(char *s)

{

    printk("%s\n", s);

    return 1;

}

__setup("test=", cmdline_test_setup);

若bootloader的bootargs中包含“test=hello”,则会打印出“hello”

定义

include/linux/init.h:228:#define __setup(str, fn) 

#define __setup(str, fn)    __setup_param(str, fn, fn, 0)

#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 }

static int __init cmdline_test_setup(char *s)

{

    printk("%s\n", s);

    return 1;

}

__setup("test=", cmdline_test_setup);

  __setup_param("test=", cmdline_test_setup, cmdline_test_setup, 0)

    static const char __setup_str_cmdline_test_setup[] __initconst __aligned(1) = "test=";

    static struct obs_kernel_param __setup_unique_cmdline_test_setup __used __section(.init.setup) __attribute__((aligned((sizeof(long))))) = {

      __setup_str_cmdline_test_setup,

      cmdline_test_setup,

      0

    }

    简单来讲就是定义了一个 obs_kernel_param 结构题变量,它的名字叫做 __setup_test_setup,里面有三个成员,

分别是 char数组__setup_str_test_setup="test=" 、函数test_setup 、 一个叫做early的变量,此处的值为0。

重点是,这个结构体变量被存放进了.init.setup 段。

调用流程

vmlinux.lds.h:

#define INIT_SETUP(initsetup_align)                    \

        . = ALIGN(initsetup_align);                \

        VMLINUX_SYMBOL(__setup_start) = .;            \

        *(.init.setup)                        \

        VMLINUX_SYMBOL(__setup_end) = .;


main.c  kernel/init

start_kernel()

  parse_early_param()   //main.c  kernel/init

    parse_early_options(tmp_cmdline);

      parse_args("early options", cmdline, NULL, 0, 0, 0, NULL, do_early_param);    //kernel/params.c

        //最终调用do_early_param    //main.c  kernel/init

  parse_args("Booting kernel", static_command_line, __start___param, __stop___param - __start___param, &unknown_bootoption);

    //最终调用unknown_bootoption    //main.c  kernel/init

    char *param, *val;

        ... ...

    while (*args) {

        int ret;

        int irq_was_disabled;

        args = next_arg(args, &param, &val);

        irq_was_disabled = irqs_disabled();

        ret = parse_one(param, val, params, num, unknown);

        ... ...

    }

    解析bootloader传给kernel的参数,在while循环里将参数一个个的取出,丢给 parse_one 函数


   do_early_param和unknown_bootoption区别

   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 && strcmp(param, p->str) == 0) ||

                (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;

    }


    首先定义了 struct obs_kernel_param *p, 还记得我们在分解 __setup 宏的时候提到过 名字叫做 __setup_video_setup 的 obs_kernel_param 变量,这个的指针p就是它。

然后从 __setup_start 开始遍历到 __setup_end,根据上面 vmlinux.lds.h 的代码知道,就是遍历整个 .init.setup 段。

看代码 “ (p->early && strcmp(param, p->str) == 0) ” 这边就体现了 early 的作用了,原来early 的值假如不为0,则会被更早得解析处理到。

随后,将param(从bootloader传过来的)和 p->str(__setup传进来的)进行匹配,若匹配成功,则调用 p->setup_func 方法,这个方法也是我们 __setup 自己定义传进来的。

unknown_bootoption 函数的功能类似,但它不会处理 p->early = 0 的数据。