內(nèi)核命令行處理(1) 在啟動(dòng)代碼main.c執(zhí)行完早期的一些內(nèi)核初始化任務(wù)之后,,就會(huì)顯示內(nèi)核的命令行信息。為方便起見,,這里重新列出代碼清單5-3中的第10行內(nèi)容: Kernel command line: console=ttyS0,115200 ip=bootp root=/dev/nfs 在這個(gè)簡單的例子中,,引導(dǎo)中的內(nèi)核在串行設(shè)備ttyS0(通常是第一個(gè)串口)上打開一個(gè)控制臺(tái),通信波特率設(shè)定為115Kbit/s,。此外,,它還通過一個(gè)BOOTP服務(wù)器獲得自身的初始化IP地址,并且通過NFS協(xié)議掛載根文件系統(tǒng),。(我們將在第12章講到BOOTP,、在第9章和第12章中講到NFS。現(xiàn)在我們只是討論Linux內(nèi)核的命令行機(jī)制,。) 引導(dǎo)裝入程序或第二階段引導(dǎo)裝入程序通過一系列被稱為內(nèi)核命令行的參數(shù)實(shí)現(xiàn)對(duì)Linux的引導(dǎo),。盡管在實(shí)際中并不是通過shell命令提示來調(diào)用內(nèi)核,但是許多版本的引導(dǎo)裝入程序常常采用將參數(shù)傳遞給Linux內(nèi)核這種非常流行的模式,。某些平臺(tái)上的引導(dǎo)裝入程序不能很好地識(shí)別Linux,,那么就在內(nèi)核編譯時(shí)定義內(nèi)核命令行參數(shù),并且將其作為Linux內(nèi)核二進(jìn)制映像固件代碼的一部分,。而在另一些平臺(tái)(例如運(yùn)行Red Hat Linux的桌面PC)中,,命令行參數(shù)可以由用戶修改而不用重新編譯內(nèi)核。第二階段引導(dǎo)裝入程序(在PC中是Grub或Lilo)通過一個(gè)配置文件建立內(nèi)核命令行并且在內(nèi)核引導(dǎo)過程中傳遞給內(nèi)核,。這些命令行參數(shù)是一種引導(dǎo)機(jī)制,,用來在給定硬件平臺(tái)上設(shè)置為正確引導(dǎo)所需的初始化配置。 Linux在整個(gè)內(nèi)核中定義了大量的命令行參數(shù),。在Linux源碼中的.../Documentation子目錄中有一個(gè)名為kernel-parameters.txt文件,,該文件包含了Linux內(nèi)核命令行參數(shù)列表,它們按字母順序依次列出,。前面提到關(guān)于內(nèi)核文檔的警告信息:內(nèi)核的變化要快于內(nèi)核文檔的變化,,因此,可以以該文件為向?qū)?,但它并不是一個(gè)最權(quán)威的參考,。在kernel-parameters.txt文件中有超過400多個(gè)內(nèi)核命令行參數(shù),但這并不是所有的內(nèi)核命令行參數(shù),,所以必須直接查閱源代碼,。 Linux內(nèi)核命令行參數(shù)的基本語法比較簡單,大部分從代碼清單5-3第10行里很容易看到,。內(nèi)核命令行參數(shù)的形式可以是單個(gè)單詞,、key=value對(duì)或key= value1, value2, …等復(fù)合形式,。通過使用這些信息進(jìn)行數(shù)據(jù)傳遞,所有的命令行都是可用的并且可以由許多的模塊來處理,。前面提到的main.c中的setup_arch()函數(shù)就是通過內(nèi)核命令行參數(shù)調(diào)用的,。通過這種調(diào)用,可以向體系結(jié)構(gòu)級(jí)或硬件平臺(tái)級(jí)相關(guān)代碼中傳遞參數(shù)和配置指令,。 設(shè)備驅(qū)動(dòng)程序編寫者和內(nèi)核開發(fā)者都可以為他們特定的需要而增加相應(yīng)的命令行參數(shù),。我們來看一下這種方式的實(shí)現(xiàn)機(jī)制。遺憾的是,,在處理這些內(nèi)核命令行參數(shù)的時(shí)候會(huì)涉及一些復(fù)雜的因素,,首先就是原先的機(jī)制將受到抑制以便實(shí)現(xiàn)更為健壯的系統(tǒng)。第二個(gè)難點(diǎn)是我們需要掌握復(fù)雜的鏈接腳本以全面理解這種實(shí)現(xiàn)機(jī)制 ,。 __setup宏 可以考慮將控制臺(tái)設(shè)備作為使用內(nèi)核命令行參數(shù)的一個(gè)例子,。我們希望該設(shè)備在內(nèi)核引導(dǎo)的早期階段就初始化,這樣在引導(dǎo)過程中控制臺(tái)信息就可以通過該設(shè)備輸出,,該初始化過程創(chuàng)建在名為printk.o的內(nèi)核目標(biāo)文件中,,其C源代碼位于.../kernel/printk.c。執(zhí)行控制臺(tái)初始化的函數(shù)是console_setup(),,該函數(shù)將內(nèi)核命令行的參數(shù)作為其唯一的參數(shù),。 配置程序和設(shè)備驅(qū)動(dòng)程序與在內(nèi)核命令行中所指定控制臺(tái)參數(shù)進(jìn)行通信的難點(diǎn),在于要求該參數(shù)是標(biāo)準(zhǔn)通用的模式,。該情形更復(fù)雜的情況是,,命令行參數(shù)在那些模塊調(diào)用它們之前(或就在此時(shí))就要用到。在文件main.c中的啟動(dòng)代碼里,,在內(nèi)核命令行進(jìn)行主要處理的位置,,如果沒有每一個(gè)參數(shù)的使用信息,就不可能知道這幾百個(gè)內(nèi)核命令行參數(shù)中每一個(gè)參數(shù)的目標(biāo)函數(shù),,所以需要用一種靈活通用的方法將內(nèi)核命令行參數(shù)傳遞給其使用者,。 對(duì)于Linux 2.4或更早的版本,開發(fā)者通過使用一個(gè)簡單的宏來解決上述問題,。盡管沒有得到重視,但是__setup宏仍然在整個(gè)Linux內(nèi)核中得到了廣泛使用,。在后續(xù)內(nèi)容中,,我們會(huì)使用代碼清單5-3中的內(nèi)核命令行來演示__setup是如何工作的。 從代碼清單5-3的第10行可知,,下面的內(nèi)容即是第一個(gè)傳遞給內(nèi)核的完整的命令行參數(shù): console=ttyS0,115200 引用該例子的真正目的并不在于命令行參數(shù)的實(shí)際含義,,而在于說明其工作機(jī)制,所以如果你沒有理解該參數(shù)或參數(shù)值并不要緊,。 代碼清單5-4的內(nèi)容是.../kernel/printk.c中的一部分代碼,,其中去掉了函數(shù)的主體部分,,因?yàn)樗c這里討論的內(nèi)容無關(guān),我們關(guān)心的只是在代碼清單5-4中列出的內(nèi)容,,即對(duì)__setup的宏調(diào)用,。__setup宏在這里有兩個(gè)參數(shù):一個(gè)字符串參數(shù)和一個(gè)函數(shù)指針。傳遞給__setup宏的字符串與第一個(gè)與內(nèi)核命令行相關(guān)的8字符的參數(shù)console=一致是絕非偶然的,。 代碼清單5-4 控制臺(tái)設(shè)置部分代碼 /* * Setup a list of consoles. Called from init/main.c */ static int __init console_setup(char *str) { char name[sizeof(console_cmdline[0].name)]; char*s, *options; int idx; /* * Decode str into name, index, options. */ return 1; } __setup("console=", console_setup); 你可以將__setup宏看作是內(nèi)核命令行控制臺(tái)參數(shù)在內(nèi)核中的注冊(cè)函數(shù),。當(dāng)字符串信息console=出現(xiàn)在內(nèi)核命令行時(shí),就通過__setup宏的第2個(gè)參數(shù)調(diào)用函數(shù)console_setup(),。但是在并不知道控制臺(tái)功能的情況下,,這個(gè)模塊之外的配置代碼是如何獲取該信息呢?事實(shí)上,,其實(shí)現(xiàn)機(jī)制巧妙而復(fù)雜,,并且依賴于目標(biāo)鏈接器所創(chuàng)建的列表。 真正的細(xì)節(jié)隱藏于一系列的宏當(dāng)中,,這些宏通過在一部分目標(biāo)代碼中增加段屬性(或其他屬性)用來隱藏,。目標(biāo)文件會(huì)聯(lián)合函數(shù)指針(function pointer)依字母順序建立一個(gè)靜態(tài)列表,該列表會(huì)由最終vmlinux ELF映像中一個(gè)獨(dú)立ELF段的編譯器發(fā)出,。理解上述技術(shù)細(xì)節(jié)非常重要,,它在內(nèi)核中許多進(jìn)行特殊處理的地方都要用到。 我們來看看對(duì)于__setup宏這是如何實(shí)現(xiàn)的,。代碼清單5-5是定義了__setup宏系列的頭文件.../include/linux/init.h下的部分內(nèi)容,。 代碼清單5-5 init.h下的_setup 宏的定義 ... #define __setup_param(str, unique_id, fn, early) / static char __setup_str_##unique_id[] __initdata = str; / static struct obs_kernel_param __setup_##unique_id / __attribute_used__ / __attribute__((__section__(".init.setup"))) / __attribute__((aligned((sizeof(long))))) / = { __setup_str_##unique_id, fn, early } #define __setup_null_param(str, unique_id) / __setup_param(str, unique_id, NULL, 0) #define __setup(str, fn) / __setup_param(str, fn, fn, 0) ... 內(nèi)核命令行處理(2) 代碼清單5-5是語法乏味的定義?;叵氪a清單5-4,,我們最初所調(diào)用的__setup宏的形式如下:
經(jīng)過稍稍簡化,編譯器在宏擴(kuò)展后,,其預(yù)處理器產(chǎn)生如下結(jié)果:
為了增加可讀性,,將上述結(jié)果的第2行和第3行采用UNIX的行續(xù)符"/"分隔開來。 我們故意略去了兩個(gè)和本次討論內(nèi)容無關(guān)的編譯器屬性,。簡要地說,,__attribute_ used__(本身就是一個(gè)隱藏了很多語法細(xì)節(jié)的宏)會(huì)告訴編譯器發(fā)出一個(gè)函數(shù)或變量,即使在編譯過程中并沒有用到任何優(yōu)化參數(shù) ,。__attribute__(aligned)會(huì)告訴編譯器按照特定的邊界來對(duì)齊結(jié)構(gòu),,在本例中是sizeof(long)。 簡化處理后剩下的就是這種機(jī)制的核心部分,。首先,,編譯器會(huì)產(chǎn)生名為__setup_str_ console_setup[]的初始化后字符數(shù)組,該數(shù)組包含console=字符串信息;其次,,編譯器會(huì)產(chǎn)生一個(gè)包含三個(gè)成員的結(jié)構(gòu):指向內(nèi)核命令行字符串(在字符數(shù)組中聲明)的指針,、指向配置函數(shù)本身的指針和一個(gè)簡單的標(biāo)識(shí)。這里的關(guān)鍵在于依附于結(jié)構(gòu)的段屬性,,該屬性會(huì)通知編譯器將該結(jié)構(gòu)送到ELF目標(biāo)模塊內(nèi)名為.init.setup的特殊段中,。在這個(gè)鏈接階段,所有由__setup宏定義的結(jié)構(gòu)一起被放置到這個(gè).init.setup段中,,實(shí)際結(jié)果就是創(chuàng)建了一個(gè)包含這些結(jié)構(gòu)的數(shù)組,。代碼清單5-6是.../init/main.c中的一部分內(nèi)容,它們說明了這個(gè)數(shù)據(jù)是如何獲取和使用的,。 代碼清單5-6 內(nèi)核命令行處理
對(duì)該段代碼解釋還算簡單,。函數(shù)由一個(gè)在main.c文件中其他地方解析的單命令行參數(shù)調(diào)用。在這個(gè)例子中,,我們要討論的指針line指向字符串console=ttyS0,115200,,它是內(nèi)核命令行的一個(gè)組成部分。兩個(gè)外部結(jié)構(gòu)指針__setup_start和__setup_end是在一個(gè)鏈接腳本文本文件中定義的,,而不是定義在C文件或頭文件中,。對(duì)于obs_kernel_param結(jié)構(gòu)數(shù)組用來標(biāo)記該數(shù)組起始和結(jié)束的標(biāo)簽則存在于目標(biāo)文件的.init.setup段中。 在代碼清單5-6中,,通過指針p對(duì)這個(gè)特殊的內(nèi)核命令行參數(shù)尋找匹配信息的過程,,對(duì)整個(gè)結(jié)構(gòu)都進(jìn)行了掃描。具體在本例中,,代碼要為字符串信息console=尋找匹配信息,,在這個(gè)相關(guān)的結(jié)構(gòu)中,函數(shù)返回一個(gè)指向console_setup()函數(shù)的指針,,它會(huì)以該參數(shù)(字符串ttyS0,115200)作為其唯一的函數(shù)參數(shù),,這一處理過程會(huì)在內(nèi)核命令行處理完畢之前不停地重復(fù)。 采用所描述的這種機(jī)制將目標(biāo)對(duì)象存放到ELF段的列表中,,這種機(jī)制在內(nèi)核中的許多地方都用到了,。另一個(gè)采用這種機(jī)制的例子是,使用__init宏系列將初始化程序放到目標(biāo)文件中一個(gè)普通的段中,。與其很相近的__initdata被__setup宏用來標(biāo)記為只在初始化過程中用到的數(shù)據(jù),。使用這些宏標(biāo)記的初始化函數(shù)和數(shù)據(jù)被集中放到ELF段中,接下來,,當(dāng)使用了這些用來初始化的函數(shù)和數(shù)據(jù)之后,,內(nèi)核會(huì)釋放之前它們所占用的內(nèi)存空間。你也許在引導(dǎo)過程的最后階段看到過類似的內(nèi)核信息:"Freeing init memory: 296K.",。不同用戶對(duì)這些函數(shù)和數(shù)據(jù)的使用可能不盡相同,但是如果有三分之一兆,就值得使用__init宏系列,,這也恰恰就是使用前面聲明的__setup_str_console_setup[]數(shù)組里的__initdata宏的目的所在,。 你也許會(huì)對(duì)代碼清單5-6中的obsolete_符號(hào)感到迷惑,這是因?yàn)閮?nèi)核開發(fā)者正在用一種更通用的機(jī)制來代替內(nèi)核命令行處理機(jī)制,,以實(shí)現(xiàn)對(duì)引導(dǎo)時(shí)間和可加載模塊參數(shù)的注冊(cè),。在當(dāng)前情況下,__setup宏聲明了幾百個(gè)參數(shù),,然而在新的開發(fā)中希望使用內(nèi)核頭文件.../include/ linux/moduleparam.h中定義的一系列函數(shù)來實(shí)現(xiàn),,更值得注意的是使用module_param*宏系列。這些內(nèi)容將在第8章中介紹設(shè)備驅(qū)動(dòng)程序的時(shí)候詳細(xì)介紹,。 上面所說的這種新機(jī)制通過在解析程序中包含一個(gè)未知的函數(shù)指針參數(shù)進(jìn)而保持了向后兼容性,,因此,對(duì)于module_param*結(jié)構(gòu)來說,,是未知的參數(shù)就會(huì)被視為未知參數(shù),,并且對(duì)命令行的處理過程就在開發(fā)者的控制下重新回到了原有的機(jī)制。在仔細(xì)研究../kernel/params.c中的代碼和.../init/main.c中的parse_args()調(diào)用后就可以對(duì)這一過程有很好的理解,。 對(duì)于由__setup宏所創(chuàng)建的結(jié)構(gòu)obs_kernel_param,,其中標(biāo)志(flag)成員的用途是最后要注意的內(nèi)容。仔細(xì)研究代碼清單5-6就會(huì)明白,。該結(jié)構(gòu)中稱為early的標(biāo)志用來指示這個(gè)特定的內(nèi)核命令行參數(shù)是否會(huì)在引導(dǎo)過程中預(yù)先使用,,一些命令行參數(shù)就是特意要在引導(dǎo)過程中提前用到,那么在這種情況下的標(biāo)志就會(huì)為提前解析命令行參數(shù)提供一種實(shí)現(xiàn)機(jī)制,。你會(huì)在main.c代碼中看到一個(gè)名為do_early_param()的函數(shù),,該函數(shù)會(huì)遍歷數(shù)組,該數(shù)組是__setup宏結(jié)構(gòu)由目標(biāo)鏈接器產(chǎn)生的,,同時(shí)該函數(shù)會(huì)處理每一個(gè)被標(biāo)記為預(yù)先使用的內(nèi)核命令行參數(shù),,在引導(dǎo)過程執(zhí)行這一處理操作時(shí)給開發(fā)者一些控制權(quán)。
子系統(tǒng)初始化 許多Linux子系統(tǒng)的初始化代碼都可在main.c中找到,。一些子系統(tǒng)的初始化代碼在main.c中顯而易見,,如對(duì)init_timers()和console_init()的調(diào)用,它們?cè)诔跏蓟^程之初就要調(diào)用,。另外一些子系統(tǒng)所采用的初始化機(jī)制與前面所提到的__setup宏非常類似,,簡單地講,目標(biāo)代碼鏈接器會(huì)為不同的初始化程序創(chuàng)建函數(shù)指針列表,,同時(shí)采用簡單的循環(huán)機(jī)制依次執(zhí)行,。代碼清單5-7顯示了這一過程。 代碼清單5-7 初始化程序示例
這部分代碼來源于.../arch/arm/kernel/setup.c,,它是為一個(gè)特殊開發(fā)板提供用戶定制的簡單程序,。 *_initcall宏 對(duì)于代碼清單5-7中的初始化程序,,有兩個(gè)要點(diǎn)需要注意。首先,,程序中的函數(shù)是由__init宏定義的,,就像在前面看到的。__init宏將該函數(shù)放到了vmlinux ELF文件中一個(gè)稱為.init.text的段中,,我們可以想到將一個(gè)函數(shù)放到目標(biāo)文件中一個(gè)特殊段中的目的,,這是為了當(dāng)函數(shù)不再使用后可以將函數(shù)所占用的內(nèi)存空間釋放。 第二個(gè)需要注意的事情是在函數(shù)定義之后的宏,,即arch_initcall(customize_machine),,該宏是在.../include/linux/init.h中所定義的一系列宏中的一個(gè)。這些宏如代碼清單5-8所示,。 代碼清單5-8 initcall宏系列
__initcall宏與前面介紹的__setup宏在形式上非常相似,,這些宏基于函數(shù)名聲明了一個(gè)數(shù)據(jù)列表,并且使用段屬性將這些數(shù)據(jù)內(nèi)容放到vmlinux ELF文件中被唯一命名的段中,。這樣做的好處是,,main.c可以任意調(diào)用其并不知道的子系統(tǒng)初始化程序,如果不這樣做,,那么唯一的方法就是采用前面描述的方法,,即只能改寫main.c中的相關(guān)內(nèi)容,讓內(nèi)核了解每一個(gè)子系統(tǒng),。 如代碼清單5-8所示,,這些段的名稱為.initcallN.init,這里的N表示的是數(shù)量1~7,,數(shù)據(jù)被分配到由宏命名的函數(shù)地址處,。在代碼清單5-7和代碼清單5-8所示的例子中,數(shù)據(jù)的分配形式如下(為了簡化起見,,省去了段屬性):
該數(shù)據(jù)被放到內(nèi)核目標(biāo)文件中的一個(gè)名為.initcall1.init的段中,。 這里的N用來提供初始化調(diào)用的順序關(guān)系,比如使用core_initcall()宏聲明的函數(shù)在其他所有函數(shù)之前被調(diào)用,,使用postcore_initcall()宏聲明的函數(shù)在其后被調(diào)用,,依次類推,使用late_initcall()宏聲明的初始化函數(shù)在最后被調(diào)用,。 和__setup宏系列非常類似,,*_initcall宏系列可以看作是內(nèi)核子系統(tǒng)初始化程序的注冊(cè)函數(shù),而且這些初始化程序也是在內(nèi)核啟動(dòng)后就要執(zhí)行,,且執(zhí)行后不再使用,。這些宏提供了一種機(jī)制,以實(shí)現(xiàn)在系統(tǒng)啟動(dòng)過程中可以執(zhí)行初始化程序,,并且在程序執(zhí)行之后將程序丟棄同時(shí)回收內(nèi)存,。在執(zhí)行初始化程序的時(shí)候也為開發(fā)者提供了7種不同的級(jí)別,,因此,如果一個(gè)子系統(tǒng)依賴于另一個(gè)子系統(tǒng)可用,,那么就可以使用這些級(jí)別來提高它的執(zhí)行順序,。如果使用grep命令查找內(nèi)核中的[a-z]*_initcall字符串信息,就會(huì)發(fā)現(xiàn)這些系列的宏在內(nèi)核中使用非常廣泛,。 對(duì)于*_initcall系列的宏,最后要注意的是:多級(jí)別的用法在Linux 2.6內(nèi)核的開發(fā)過程中引入,,早期版本的內(nèi)核是用__initcall()宏來實(shí)現(xiàn)的,,目前__initcall()宏仍然在廣泛使用中,尤其是在設(shè)備驅(qū)動(dòng)程序中,。為了保持向后兼容性,,已經(jīng)將__initcall()宏定義為device_initcall(),這是一個(gè)級(jí)別為6的initcall,。
5.5 init線程 .../init/main.c中的內(nèi)容主要用來實(shí)現(xiàn)內(nèi)核的運(yùn)轉(zhuǎn),。在start_kernel()函數(shù)通過調(diào)用一些初始化函數(shù)執(zhí)行一些基本的內(nèi)核初始化任務(wù)之后,就產(chǎn)生了第一個(gè)內(nèi)核線程,。該線程最終成為內(nèi)核的init()線程,,其線程ID號(hào)(PID)為1??梢灾?,init()就成為用戶空間中所有Linux進(jìn)程的父進(jìn)程。在引導(dǎo)過程中運(yùn)行著兩個(gè)截然不同的線程:一個(gè)是前面提到的start_kernel(),;另一個(gè)就是現(xiàn)在的init(),。前者在完成自身的任務(wù)之后最終成為idle進(jìn)程,而后者稱為init進(jìn)程,,如代碼清單5-9所示,。 代碼清單5-9 內(nèi)核init線程的創(chuàng)建
從代碼清單5-9可以看出,start_kernel()函數(shù)調(diào)用了rest_init(),,通過調(diào)用kernel_thread().init來產(chǎn)生內(nèi)核的init進(jìn)程,,以繼續(xù)完成內(nèi)核其余的初始化任務(wù),而由start_kernel()開始的線程在調(diào)用cpu_idle()的過程中不停地重復(fù)執(zhí)行,。 這樣的結(jié)果非常有趣,。你或許也注意到這個(gè)相當(dāng)龐大的start_kernel()函數(shù)被__init宏所標(biāo)記,這意味著它所占用的內(nèi)存空間將在內(nèi)核初始化的最后階段被釋放,。在釋放內(nèi)存之前需要退出該函數(shù)和它所占用的地址空間,,這是通過start_kernel()調(diào)用rest_init()來實(shí)現(xiàn)的,如代碼清單5-9所示,,一段非常小的內(nèi)存空間處在了空閑狀態(tài),。
5.5.1 通過initcall初始化 當(dāng)創(chuàng)建init()之后,,它會(huì)調(diào)用do_initcalls()函數(shù),而do_initcalls()函數(shù)是用來調(diào)用所有被*_initcall宏系列所注冊(cè)的初始化函數(shù)的,,其實(shí)現(xiàn)代碼如代碼清單5-10所示,。 代碼清單5-10 使用initcalls初始化
除了兩個(gè)用于指示循環(huán)范圍的標(biāo)簽__initcall_start和__initcall_end之外,該段代碼很好理解,。在C源代碼和頭文件中不會(huì)看到這樣的標(biāo)簽,,它們是在vmlinux鏈接階段所用的鏈接腳本文件中定義的,用來表示使用*_initcall宏系列所生成的初始化函數(shù)列表的起始和結(jié)束位置,。你可以在Linux內(nèi)核頂層目錄下的System.map文件中看到每一個(gè)這樣的標(biāo)簽,,這些標(biāo)簽以字符串__initcall開始,就像代碼清單5-8中所表示的那樣,。 你如果對(duì)do_initcalls()函數(shù)中的調(diào)試打印信息感到疑惑的話,,可以看一下由在引導(dǎo)過程中設(shè)置的內(nèi)核命令行參數(shù)initcall_debug所執(zhí)行的系統(tǒng)調(diào)用,該命令行參數(shù)允許打印如代碼清單5-10所示的調(diào)試信息,。內(nèi)核只需簡單地以內(nèi)核命令行參數(shù)initcall_debug開始就可以實(shí)現(xiàn)這些調(diào)試信息的輸出 ,。 下面是一個(gè)啟用了這些調(diào)試語句時(shí)的輸出的例子:
注意在代碼清單5-7中對(duì)customize_machine()的調(diào)用,調(diào)試信息的輸出包括了函數(shù)的虛擬內(nèi)核地址(在該例中是0xc000c32c)和函數(shù)大?。ㄔ谶@里是0x2c),。這是了解內(nèi)核初始化的一個(gè)有效方法,特別是對(duì)不同子系統(tǒng)和模塊的調(diào)用次序的理解,。即使是在一個(gè)具有相當(dāng)配置的嵌入式系統(tǒng)之上,,也有幾十個(gè)這樣的初始化函數(shù)通過這種方式調(diào)用。在這個(gè)以嵌入式ARM XScale為平臺(tái)的例子中,,共有92個(gè)這樣不同的內(nèi)核初始化程序,。 5.5.1 通過initcall初始化 當(dāng)創(chuàng)建init()之后,它會(huì)調(diào)用do_initcalls()函數(shù),,而do_initcalls()函數(shù)是用來調(diào)用所有被*_initcall宏系列所注冊(cè)的初始化函數(shù)的,,其實(shí)現(xiàn)代碼如代碼清單5-10所示。 代碼清單5-10 使用initcalls初始化
除了兩個(gè)用于指示循環(huán)范圍的標(biāo)簽__initcall_start和__initcall_end之外,,該段代碼很好理解,。在C源代碼和頭文件中不會(huì)看到這樣的標(biāo)簽,它們是在vmlinux鏈接階段所用的鏈接腳本文件中定義的,,用來表示使用*_initcall宏系列所生成的初始化函數(shù)列表的起始和結(jié)束位置,。你可以在Linux內(nèi)核頂層目錄下的System.map文件中看到每一個(gè)這樣的標(biāo)簽,這些標(biāo)簽以字符串__initcall開始,,就像代碼清單5-8中所表示的那樣,。 你如果對(duì)do_initcalls()函數(shù)中的調(diào)試打印信息感到疑惑的話,可以看一下由在引導(dǎo)過程中設(shè)置的內(nèi)核命令行參數(shù)initcall_debug所執(zhí)行的系統(tǒng)調(diào)用,,該命令行參數(shù)允許打印如代碼清單5-10所示的調(diào)試信息,。內(nèi)核只需簡單地以內(nèi)核命令行參數(shù)initcall_debug開始就可以實(shí)現(xiàn)這些調(diào)試信息的輸出 ,。 下面是一個(gè)啟用了這些調(diào)試語句時(shí)的輸出的例子:
注意在代碼清單5-7中對(duì)customize_machine()的調(diào)用,調(diào)試信息的輸出包括了函數(shù)的虛擬內(nèi)核地址(在該例中是0xc000c32c)和函數(shù)大?。ㄔ谶@里是0x2c),。這是了解內(nèi)核初始化的一個(gè)有效方法,特別是對(duì)不同子系統(tǒng)和模塊的調(diào)用次序的理解,。即使是在一個(gè)具有相當(dāng)配置的嵌入式系統(tǒng)之上,,也有幾十個(gè)這樣的初始化函數(shù)通過這種方式調(diào)用。在這個(gè)以嵌入式ARM XScale為平臺(tái)的例子中,,共有92個(gè)這樣不同的內(nèi)核初始化程序,。 |
|