引導期間的核心選項
Linux運行使用者將核心配置選項傳遞給引導記錄,引導記錄再把選項傳給核心,進而通過引導配置參數微調核心。在系統引導期間,將調用parse_args兩次解析引導期間輸入的配置參數。
parse_args函數解析的輸入字串參數是"變數名稱=值"的形式,根據解析出的關鍵字並啟用適當的處理函數。載入模組時,也會用到parse_args解析命令列參數。
註冊關鍵字
核心組件利用定義在include/linux/init.h中的__setup宏註冊關鍵字和相關聯的處理函數:
__setup(string, function_handler)
string是關鍵字,而function_handler是相關聯的處理函數。核心初始化時parse_args函數解析到關鍵字string是,就執行相關聯的function_handler函數,但string必須以=字元結束,而任何跟在=字元後面的字串將被作為函數輸入參數傳遞給function_handler函數處理。這些參數存放在記憶體區塊.setup.init中。
如arch\arm\kernel\process.c檔案中,執行個體只需要輸入關鍵字即可:
static int __init nohlt_setup(char *__unused)
{
hlt_counter = 1;
return 1;
}
static int __init hlt_setup(char *__unused)
{
hlt_counter = 0;
return 1;
}
__setup("nohlt", nohlt_setup);
__setup("hlt", hlt_setup);
在網路子系統的net/core/dev.c檔案中,註冊了netdev=關鍵字和對應的處理函數netdev_boot_setup函數:
int __init netdev_boot_setup(char *str)
{
int ints[5];
struct ifmap map;
str = get_options(str, ARRAY_SIZE(ints), ints);
if (!str || !*str)
return 0;
/* Save settings */
memset(&map, 0, sizeof(map));
if (ints[0] > 0)
map.irq = ints[1];
if (ints[0] > 1)
map.base_addr = ints[2];
if (ints[0] > 2)
map.mem_start = ints[3];
if (ints[0] > 3)
map.mem_end = ints[4];
/* Add new entry to the list */
return netdev_boot_setup_add(str, &map);
}
__setup("netdev=", netdev_boot_setup);
而在net/ethernet/eth.c檔案中,ether=關鍵字通用被註冊到了同一個處理函數netdev_boot_setup。
__setup("ether=", netdev_boot_setup);
當一端代碼被編譯為模組時,__setup宏會被護理,即定義為空白操作。
#ifndef MODULE
……
#define __setup(str, fn) \
__setup_param(str, fn, fn, 0)
……
#else
……
#define __setup(str, func) /* nothing */
#endif
在init\main.c檔案中start_kernel函數中:
asmlinkage void __init start_kernel(void)
{
……
parse_early_param();
parse_args("Booting kernel", static_command_line, __start___param,
__stop___param - __start___param,
&unknown_bootoption);
……
}
start_kernel兩次調用parse_args以解析引導配置參數的原因在於引導期間的參數實際是分為兩類的,每一次調用分別針對其中的一類:
- 預設選項:多數參數都屬於這一類,這些參數由__setup宏定義,有parse_args第二次被調用時處理
- 早期選項:核心引導期間,有些選項需要更早處理。這些參數是由early_param宏代替__setup宏定義的,這些選項由parse_early_params負責。early_param和__setup宏的唯一區別是early_param會設定一個特殊標記,使核心能區分這兩種情況,而此標記是obs_kernel_param資料結構的一部分。
#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)
在start_kernel函數中第二次調用parse_args函數時,會檢查有module_param宏填入的模組選項,這些選項會儲存在kernel_param資料結構中,module_param宏會確保這些資料結構都會被存放在特定的記憶體區塊中(__param),有指標__start___param
和__stop___param限定。
__setup_start……__setup_end:這個地區會在引導階段結束時釋放掉,使用者在運行期間不能看到或修改這些選項;
__start___param……__stop___param:這個地區不會被釋放,其內容會被輸出到/sys檔案系統,這些選項可以顯示給使用者。
有__setup()和early_param()宏定義的關鍵字會被放入__setup_start……__setup_end地區,只是early_param宏定義的關鍵字會被設定early標記。
struct obs_kernel_param {
const char *str;
int (*setup_func)(char *);
int early;
};
str是關鍵字,setup_func是關聯的處理函數,early則是early標記。__setup_param宏回家所有的obs_kernel_params執行個體儲存到專用的記憶體地區內,這樣比較容易遍曆,__setup_start和__setup_end指標分別指向這個地區的開端和尾端。而當這些地區不再使用時也便於快速釋放記憶體。