Linux啓動bootargs參數分析

Linux啓動bootargs參數分析

Written by leeming

這幾天剛好在看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, &param, &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, &param, &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, &params[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;

}

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章