久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

內(nèi)核命令行處理

 lifei_szdz 2013-06-22

內(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宏的形式如下:

  1. __setup("console=", console_setup); 

經(jīng)過稍稍簡化,編譯器在宏擴(kuò)展后,,其預(yù)處理器產(chǎn)生如下結(jié)果:

  1. static char __setup_str_console_setup[] __initdata = "console=";  
  2. static struct obs_kernel_param __setup_console_setup  /  
  3. __attribute__((__section__(".init.setup")))=  
  4.    {__setup_str_console_setup, console_setup, 0}; 

為了增加可讀性,,將上述結(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)核命令行處理

  1. 1 extern struct obs_kernel_param __setup_start[], __setup_end[];  
  2. 2  
  3. 3 static int __init obsolete_checksetup(char *line)  
  4. 4 {  
  5. 5         struct obs_kernel_param *p;  
  6. 6  
  7. 7         p = __setup_start;  
  8. 8         do {  
  9. 9                 int n = strlen(p->str);  
  10. 10                 if (!strncmp(line, p->str, n)) {  
  11. 11                         if (p->early) {  
  12. 12                                  /* Already done in parse_early_param? (Needs  
  13. 13                                   * exact match on param part) */  
  14. 14                                  if (line[n] == '/0' || line[n] == '=')  
  15. 15                                           return 1;  
  16. 16                         } else if (!p->setup_func) {  
  17. 17                             printk(KERN_WARNING "Parameter %s is obsolete,"  
  18. 18                                     " ignored/n", p->str);  
  19. 19                                  return 1;  
  20. 20                         } else if (p->setup_func(line + n))  
  21. 21                                  return 1;  
  22. 22                }  
  23. 23                p++;  
  24. 24        } while (p < __setup_end);  
  25. 25        return 0;  
  26. 26 } 

對(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 初始化程序示例

  1. static int __init customize_machine(void)  
  2. {  
  3.     /* customizes platform devices, or adds new ones */  
  4.     if (init_machine)  
  5.         init_machine();  
  6.     return 0;  
  7. }  
  8. arch_initcall(customize_machine); 

這部分代碼來源于.../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宏系列

  1. #define __define_initcall(level,fn) /  
  2.     static initcall_t __initcall_##fn __attribute_used__ /  
  3.     __attribute__((__section__(".initcall" level ".init"))) = fn  
  4.  
  5. #define core_initcall(fn)         __define_initcall("1",fn)  
  6. #define postcore_initcall(fn)     __define_initcall("2",fn)  
  7. #define arch_initcall(fn)         __define_initcall("3",fn)  
  8. #define subsys_initcall(fn)       __define_initcall("4",fn)  
  9. #define fs_initcall(fn)           __define_initcall("5",fn)  
  10. #define device_initcall(fn)       __define_initcall("6",fn)  
  11. #define late_initcall(fn)         __define_initcall("7",fn) 

__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ù)的分配形式如下(為了簡化起見,,省去了段屬性):

  1. static initcall_t __initcall_customize_machine = customize_machine; 

該數(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)建

  1. static void noinline rest_init(void)  
  2.         __releases(kernel_lock)  
  3. {  
  4.         kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND);  
  5.         numa_default_policy();  
  6.         unlock_kernel();  
  7.         preempt_enable_no_resched();  
  8.  
  9.         /*  
  10.          * The boot idle thread must execute schedule()  
  11.          * at least one to get things moving:  
  12.          */  
  13.         schedule();  
  14.  
  15.         cpu_idle();  

從代碼清單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初始化

  1. static void __init do_initcalls(void)  
  2. {  
  3.     initcall_t *call;  
  4.  
  5.     for( call = &__initcall_start; call < &__initcall_end; call++) {  
  6.  
  7.         if (initcall_debug) {  
  8.             printk(KERN_DEBUG "Calling initcall 0x%p", *call);  
  9.             print_symbol(":%s()", (unsigned long) *call);  
  10.             printk("/n");  
  11.         }  
  12.  
  13.  
  14.         (*call)();  
  15.  

除了兩個(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í)的輸出的例子:

  1. ...  
  2. Calling initcall 0xc00168f4: tty_class_init+0x0/0x3c()  
  3. Calling initcall 0xc000c32c: customize_machine+0x0/0x2c()  
  4. Calling initcall 0xc000c4f0: topology_init+0x0/0x24()  
  5. Calling initcall 0xc000e8f4: coyote_pci_init+0x0/0x20()  
  6. PCI: IXP4xx is host  
  7. PCI: IXP4xx Using direct access for memory space  
  8. ... 

注意在代碼清單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初始化

  1. static void __init do_initcalls(void)  
  2. {  
  3.     initcall_t *call;  
  4.  
  5.     for( call = &__initcall_start; call < &__initcall_end; call++) {  
  6.  
  7.         if (initcall_debug) {  
  8.             printk(KERN_DEBUG "Calling initcall 0x%p", *call);  
  9.             print_symbol(":%s()", (unsigned long) *call);  
  10.             printk("/n");  
  11.         }  
  12.  
  13.  
  14.         (*call)();  
  15.  

除了兩個(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í)的輸出的例子:

  1. ...  
  2. Calling initcall 0xc00168f4: tty_class_init+0x0/0x3c()  
  3. Calling initcall 0xc000c32c: customize_machine+0x0/0x2c()  
  4. Calling initcall 0xc000c4f0: topology_init+0x0/0x24()  
  5. Calling initcall 0xc000e8f4: coyote_pci_init+0x0/0x20()  
  6. PCI: IXP4xx is host  
  7. PCI: IXP4xx Using direct access for memory space  
  8. ... 

注意在代碼清單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)核初始化程序,。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請(qǐng)點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多