處理模型Linux kernel 的啟動包括很多組件的初始化和相關配置,這些配置參數一般是通過command line 進行配置的。在進行後續分析之前,先來理解一下command
line 的處理模型: 要處理的對象是一個字串,其中包含了各種配置資訊,通常各個配置之間通過空格進行分離,每個配置的表達形式是如:param=value1,value2或者很簡單就是一個rw 。
那麼kernel 就需要提供對這些參數進行處理的處理函數列表。根據參數的作用以及執行期的先後不同,這些處理函數被定義到不同的段中。針對每一個參數,Kernel 都會到相應的段中尋找相應的處理函數,最終進行各個組件的配置。1 配置格式常見的配置格式如:
console=ttySAC0,115200 root=nfs nfsroot=192.168.1.9:/source/rootfs initrd=0x10800000,0x14af47 |
2 配置方式2.1 Bootloader動態配置由bootloader 進行參數配置,command line 將做為atag_list 的一個節點傳遞到Kernel
。 2.2 Kernel
靜態配置通過make menuconfig 進行配置:運行後配置boot options->Default kernel command string 。該配置將被靜態編譯到Kernel
中,通過變數default_command_line 訪問。3 解析配置3.1 相關定義根據執行的先後順序,可以將處理函數分為三個大類,他們分別存在於下面三個段中(參考top/arch/arm/kernel/vmlinux.lds ):
__setup_start = .; *(.init.setup) __setup_end = .; __early_begin = .; *(.early_param.init) __early_end = .; __start___param = .; *(__param) __stop___param = .; |
這三個段記憶體儲的不是參數,而是command line 參數所需要的處理函數。3.1.1 .early_param.init段
“.early_param.init ” 所定義的處理相對靠前一些,它所處理的參數例如:initrd= ,cachepolicy= ,nocache
, nowb , ecc= , vmalloc= , mem= ,等等。這些處理函數是通過
__early_param宏來定義的,例如:
static void __init early_initrd(char **p) { …… } __early_param("initrd=", early_initrd); |
對於宏
__early_param,可以在top/arch/arm/include/asm/Setup.h 中找到如下定義:
struct early_params { const char *arg; void (*fn)(char **p); }; #define __early_param(name,fn) \ static struct early_params __early_##fn __used \ __attribute__((__section__(".early_param.init"))) = { name, fn } |
3.1.2 .init.setup
段
“.init.setup ”定義的處理則要靠後一些,它所處理的參數例如:nfsroot= , ip= ,等等。這些處理函數是通過
__setup宏來定義的,例如:
static int __init nfs_root_setup(char *line) { …… } __setup("nfsroot=", nfs_root_setup); |
對於宏
__setup,可以在top/include/linux/Init.h 中看到:
#define __setup_param(str, unique_id, fn, early) \ static char __setup_str_##unique_id[] __initdata __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)
|
注意看的話,可以看到還有一個宏
early_param
,它與宏
__setup的定義相似,只不過最後一個宏參數是1 而不是0 。1
表示需要提前處理的參數。3.1.3 __param
段這個段中儲存的是build-in 類型module 的配置參數。該宏直接用來修飾需要的變數。3.2 解析 3.2.1
相關變數 相關的變數包括:
default_command_line:
儲存memuconfig 配置的參數,如果bootloader 傳入了命令列參數,那麼這個新的配置將被更新到該變數中。
boot_command_line:
存在於.init.data 段。最初是default_command_line 的拷貝。
command_line:
存在於.init.data 段。在parse_cmdline() 中被賦值,資料來源是default_command_line 。
saved_command_line:
用於儲存沒有處理過的命令列參數,是boot_caommand_line 的拷貝。
static_command_line:
是command_line 的拷貝。3.2.2
主要函數
函數名稱:parse_cmdline()
操作資料:default_command_line。
函數列表:
.early_param.init 段(在__early_begin 和__early_end 之間)。
函數功能: 依據函數列表對default_command_line 中的參數進行處理。
函數名稱:parse_early_param()
操作資料:boot_command_line。
函數列表:
.init.setup 段中(__setup_start 和__setup_end 之間),主要是通過宏
early_param定義的部分。
函數功能: 依據函數列表對boot_command_line 中的參數進行處理。注意parse_one() 的第四個入參是0 ,而且第五個參數是NULL 。這裡沒有給出參數隊列,不會對boot_command_line
的每個參數在參數隊列中進行對比尋找,而是直接在do_early_param() 中進行條件判斷,如果滿足下面的條件,那麼對該參數進行對應的操作:
if ((p->early && strcmp(param, p->str) == 0) || (strcmp(param, "console") == 0 && strcmp(p->str, "earlycon") == 0) ) |
函數名稱:parse_args()
操作資料:static_command_line。
函數列表:
__param 段(__start___param 和__stop___param 之間)。
函數功能: 該操作將依據函數列表,對static_command_line 中的參數進行相應的操作。這個操作在parse_one() 的第一部分程式碼完成:
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]); } } |
接下來對於不被這個列表所支援的參數,將在unknown_bootoption() 中進行處理。在unknown_bootoption() 中主要是obsolete_checksetup()
的操作。
函數名稱:obsolete_checksetup()
操作資料:static_command_line。
函數列表:
.init.setup 段中(__setup_start 和__setup_end 之間),主要是通過宏
__setup定義的部分。
函數功能: 該操作將依據函數列表,對static_command_line 中的參數進行相應的操作。如果是在parse_early_param() 中已經處理的操作,那麼這裡不再處理;如果是尋找到的條目中沒有操作函數,那麼這表示是過時的資料定義(有些早期的代碼,沒有定義這個函數);如果不是以上兩種情形,那麼利用找到的函數對參數進行處理。3.2.3圖示