Linux启动bootargs参数分析

    技术2022-05-19  24

    这几天刚好在看linux c语言启动,现在就顺便把内核在启动时解析bootargs这一块单独拎出来讲解下,内核对于bootargs的解析分为几块:

    1.      setup_arch(&command_line);

    综述:在这个函数中,系统会获得bootargs参数,并对其做简单的初步分析。并将bootargs的参数保存在command_line这个地址中。

    详解:

    A.  先获得bootargs的地址,uboot传进来的参数是放在30000100的地方的

           //一般默认为0x30000100位置//boot_params 如果为0则表示bootloader没有传参数

           if (mdesc->boot_params)

                  tags = phys_to_virt(mdesc->boot_params);

    B. 是通过标签ATAG来辨别的, uboot中有相应的标签字,将相应的uboot参数放置到相应的全局变量中。

           if (tags->hdr.tag == ATAG_CORE) {

    //已经被fixup函数修改,则将atag中的mem段置为none

                  if (meminfo.nr_banks != 0)

                         squash_mem_tags(tags);

    //继续把atag的参数传递结束, 通过参数的类型(比如ATAG_CMDLINEATAG_MEM诸如此类的参数)将bootargs参数全部分析完毕。

                  parse_tags(tags);

    {

    extern struct tagtable __tagtable_begin, __tagtable_end;

    struct tagtable *t;

    //我们的参数是放在__tagtable_begin__tagtable_end区间内,各个类型的通过__tagtable的宏定义在编译的时候就将其定位在这个区间,我们的每一个参数只需要和每个宏比较,并调用其对用的parse函数。

    //对于我们一般的bootargs,只传递了ATAG_CMDLINE,而在其对应的parse函数就是把传递进来的cmdline存放到default_command_line中。

    for (t = &__tagtable_begin; t < &__tagtable_end; t++)

            if (tag->hdr.tag == t->tag) {

                   t->parse(tag);

                   break;

            }

    return t < &__tagtable_end;

    }

    }

    C.  cmdline存放至saved_command_line        

    //setup_arch函数刚开始就定义了char *from= default_command_line,因此通过下面这个函数实现把cmdline存放至saved_command_line中。 memcpy(saved_command_line, from, COMMAND_LINE_SIZE);

    D.         cmdline做简单的 分析,主要是meminitrd

    这里的处理和B步比较类似,通过对cmdline中的一个个参数和__early_begin__early_end间的参数进行比较。从而得到匹配的参数,然后调用其相应的parse函数进行处理,同时将剩余部分存放到setup_arch(&command_line)传进来的字符串指针command_line中。。这部分先对cmdline进行分析是因为接下来就需要对页表进行建立,所以必须知道内存meminitrd文件系统的信息,所以这部分属于earlyparse的参数很少。其余的参数解析都留至后面的参数分析中。

     

    2.      parse_early_param();

    综述:第二次分析cmdline,不过在这里分析的是系统能够辨别的一些早期参数(这个函数甚至可以去掉),而且在分析的时候并不是以setup_arch&command_line)传出来的command_line为基础,而是以最原生态的saved_command_line为基础的。

    详解:

    parse_args("early options", tmp_cmdline, NULL, 0, do_early_param);

    {

            args = next_arg(args, ¶m, &val);//一个个参数分离

            ret = parse_one(param, val, params, num, unknown(就是do_early_param);//解析参数

    由于传进去的num0,因此对于每一个参数param和值value,直接调用do_early_param解析。

    }

          

           // do_early_param这部分的实现就和1中的B/D的处理类似,通过对__setup_start__setup_end区间的参数进行比较,找到对应的参数,调用该参数的解析函数。这部分的定义是以__setup(str, fn)的类型出现的,在linux中这类型的启动参数有非常多(比如rootconsolero,rw,rootfstype,md,resume……),几乎这步可以涵盖所有有用的,而且我们自己也可以增加这种操作来支持新的启动参数。

    static int __init do_early_param(char *param, char *val)

    {

    struct obs_kernel_param *p;

     

    for (p = __setup_start; p < __setup_end; p++) {

            if (p->early && strcmp(param, p->str) == 0) {

                   if (p->setup_func(val) != 0)

                          printk(KERN_WARNING

                                 "Malformed early option '%s'/n", param);

    }

    }

    }

     

    3.      parse_args("Booting kernel", command_line, __start___param,       __stop___param - __start___param,   &unknown_bootoption);

     

    综述:对于比较新的版本真正起作用的函数,与2parse_early_param();相比,此处对解析列表的处理范围加大了,解析列表中除了包括系统以setup定义的启动参数,还包括模块中定义的param参数以及系统不能辨别的参数。

    详解:

    command_linesetup_arch函数传递出来的值;

    __start___paramparam参数的起始地址,在System.map文件中能看到

    __stop___param - __start___param是参数个数

    unknown_bootoption是对应与启动参数不是param的相应处理函数

     

    同样跟进去最核心的函数也是parse_args(同2中分析):

    parse_args("early options", tmp_cmdline, NULL, 0, do_early_param);

    {

            args = next_arg(args, ¶m, &val);//一个个参数分离

            ret = parse_one(param, val, params, num, unknown(就是do_early_param);//解析参数

    由于传进去的num为就是parm的个数,所以先要将启动参数和param一个个比较。

    static int parse_one(char *param,char *val,struct kernel_param *params,

                unsigned num_params,int (*handle_unknown)(char *param, char *val))

    {

    unsigned int i;

    //先寻找启动参数是否和param匹配, param变量一般是在驱动模块中module_param定义的,存放在*param)空间。如果匹配则将param参数的值用相应的value代替。

    //也就是说通过这种方式可以在启动参数中为驱动的参数赋值,而且可以看出linux中认为param参数是以后主要使用的启动参数传递方式,将慢慢摒弃__setup的形式。

    for (i = 0; i < num_params; i++) {

            if (parameq(param, params[i].name)) {

                   DEBUGP("They are equal!  Calling %p/n",

                          params[i].set);

                   return params[i].set(val, ¶ms[i]);

            }

    }

     

    //当然对于嵌入式的cmdline,一般而言都没有param参数的值,所以都是调用此处的handle_unknown

    如一个很简单的例子:

    Kernel command line: root=/dev/mtdblock3 console=ttyS0,115200 rootfstype=yaffs mem=32m

    Unknown argument: calling c00082e4

    parram is root, val is /dev/mtdblock3

    parram is console, val is ttyS0,115200

    parram is rootfstype, val is yaffs

     

    if (handle_unknown) {

            DEBUGP("Unknown argument: calling %p/n", handle_unknown);

            return handle_unknown(param, val);

    }

    {

           /* Handle obsolete-style parameters */其实我们的参数在handle_unknown中还是过时的参数解析方式的,就是obsolette_checksetup函数,这个函数内部的处理和parse_early_param()类似,所以这里就不详细解释了。

    if (obsolete_checksetup(param))

            return 0;

     

    对于既不是param,在handle_unknown中又不是setup形式的参数字符串,但设置了参数值。就将其放置在系统启动后的环境变量全局数组envp_init[]中的同名参数或空环境变量中。

    对于没有设置参数值的参数字符串就将其传给argv_init[]中同名参数或空参数。

    ……

    }

     

    DEBUGP("Unknown argument `%s'/n", param);

    return -ENOENT;

    }

    }


    最新回复(0)