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_CMDLINE,ATAG_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做簡單的 分析,主要是mem和initrd的
這裏的處理和B步比較類似,通過對cmdline中的一個個參數和__early_begin到__early_end間的參數進行比較。從而得到匹配的參數,然後調用其相應的parse函數進行處理,同時將剩餘部分存放到setup_arch(&command_line)傳進來的字符串指針command_line中。。這部分先對cmdline進行分析是因爲接下來就需要對頁表進行建立,所以必須知道內存mem和initrd文件系統的信息,所以這部分屬於early,parse的參數很少。其餘的參數解析都留至後面的參數分析中。
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));//解析參數
由於傳進去的num爲0,因此對於每一個參數param和值value,直接調用do_early_param解析。
}
// do_early_param這部分的實現就和1中的B/D的處理類似,通過對__setup_start和__setup_end區間的參數進行比較,找到對應的參數,調用該參數的解析函數。這部分的定義是以__setup(str, fn)的類型出現的,在linux中這類型的啓動參數有非常多(比如root,console,ro,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);
綜述:對於比較新的版本真正起作用的函數,與2的parse_early_param();相比,此處對解析列表的處理範圍加大了,解析列表中除了包括系統以setup定義的啓動參數,還包括模塊中定義的param參數以及系統不能辨別的參數。
詳解:
command_line是setup_arch函數傳遞出來的值;
__start___param是param參數的起始地址,在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;
}
}