(linux)main.c中的初始化

main.c中的初始化


head.s在最後部分調用main.c中的start_kernel() 函數,從而把控制權交給了它。
所以啓動程序從start_kernel()函數繼續執行。這個函數是main.c乃至整個操作系統初始化的最重要的函數,一旦它執行完了,整個操作系統的初始化也就完成了。
如前所述,計算機在執行start_kernel()前處已經進入了386的保護模式,設立了中斷向量表並部分初始化了其中的幾項,建立了段和頁機制,設立了九個段,把線性空間中用於存放系統數據和代碼的地址映射到了物理空間的頭4MB,可以說我們已經使386處理器完全進入了全面執行操作系統代碼的狀態。但直到目前爲止,我們所做的一切可以說都是針對386處理器所做的工作,也就是說幾乎所有的多任務操作系統只要使用386處理器,都需要作這一切。而一旦start_kernel()開始執行,Linux內核的真實面目就一步步的展現在你的眼前了。
start_kernel()執行後,你就可以以一個用戶的身份登錄和使用Linux了。
讓我們來看看start_kernel()到底做了些什麼,這裏,通過介紹start_kernel()所調用的函數,我們來討論它的流程和功能。

start_kernel()的流程和功能


我們仿照C語言函數的形式來進行這種描述,不過請注意,真正的start_kernel()函數調用子函數並不象我們在下面所寫的這樣簡單,畢竟這本書的目的是幫助你深入分析Linux。我們只能給你提供從哪兒入手和該怎麼看的建議,真正深入分析Linux,還需要你自己來研究代碼。
start_kernel()這個函數是在/init/main.c中,這裏也只是將main.c中較爲重要的函數列舉出來。

/*定義於init/main.c */
start_kernel()         
{
    ……
    setup_arch();
}
 start_kernel主要用於對處理器、內存等最基本的硬件相關部分的初始化,如初始化處理器的類型(是在386,486,還是586的狀態下工作,這是有必要的,比如說Pentium芯片支持4MB大小的頁,而386就不支持),初始化RAM盤所佔用的空間(如果你安裝了RAM盤的話)等。其中,`setup_arch()`給系統分配了intel系列芯片統一使用的幾個I/O端口的口地址。
paging_init();   /*該函數定義於arch/i386/mm/init.c */
它的具體作用是把線性地址中尚未映射到物理地址上的部分通過頁機制進行映射。這一部分在本書第六章有詳細的描述,在這裏需要特別強調的是,當`paging_init()`函數調用完後,頁的初始化就整個完成了。
 trap_init();/*該函數在arch/i386/kernel/traps.c中定義*/

這個初始化程序是對中斷向量表進行初始化,詳見第四章。它通過調用set_trap_gate(或set_system_gate等)宏對中斷向量表的各個表項填寫相應的中斷響應程序的偏移地址。
事實上,Linux操作系統僅僅在運行trap_init()函數前使用BIOS的中斷響應程序(我們這裏先不考慮V86模式)。一旦真正進入了Linux操作系統,BIOS的中斷向量將不再使用。對於軟中斷,Linux提供一套調用十分方便的中斷響應程序,對於硬件設備,Linux要求設備驅動程序提供完善的中斷響應程序,而調用使用多個參數的BIOS中斷就被這些中斷響應程序完全代替了。
另外,在trap_init()函數裏,還要初始化第一個任務的Ldt和TSS,把它們填入Gdt相應的表項中。第一個任務就是init_task這個進程,填寫完後,還要把init_task的TSS和LDT描述符分別讀入系統的TSS和LDT寄存器。

init_IRQ()  /*在arch/i386/kernel/irq.c中定義*/

這個函數也是與中斷有關的初始化函數。不過這個函數與硬件設備的中斷關係更密切一些。
我們知道intel的80386系列採用兩片8259作爲它的中斷控制器。這兩片級連的芯片一共可以提供16個引腳,其中15個與外部設備相連,一個用於級連。可是,從操作系統的角度來看,怎麼知道這些引腳是否已經使用;如果一個引腳已被使用,Linux操作系統又怎麼知道這個引腳上連的是什麼設備呢?在內核中,同樣是一個數組(靜態鏈表)來紀錄這些信息的。這個數組的結構在irq.h中定義:

  struct irqaction {
       void (*handler)(intvoid *, struct pt_regs *);
       unsigned long flags;
       unsigned long mask;   
       const char *name; 
       void *dev_id;
      struct irqaction *next;
  }

具體內容請參見第四章。我們來看一個例子:

static void math_error_irq(int cpl, void *dev_id, struct pt_regs *regs)
  {
      outb(00xF0);
      if (ignore_irq13 || !hard_math) 
          return;
      math_error();
  }
static struct irqaction  irq13 = {math_error_irq, 00"math error", NULL, NULL };
該例子就是這個數組結構的一個應用,這個中斷是用於協處理器的。在`init_irq()`這個函數中,除了協處理器所佔用的引腳,只初始化另外一個引腳,即用於級連的2引腳。不過,這個函數並不僅僅做這些,它還爲兩片8259分配了I/O地址,對應於連接在管腳上的硬中斷,它初始化了從0x20開始的中斷向量表的15個表項(386中斷門),不過,這時的中斷響應程序由於中斷控制器的引腳還未被佔用,自然是空程序了。當我們確切地知道了一個引腳到底連接了什麼設備,並知道了該設備的驅動程序後,使用`setup_x86_irq()`這個函數填寫該引腳對應的386的中斷門時,中斷響應程序的偏移地址才被填寫進中斷向量表。
sched_init()/*在/kernel/sched.c中定義*/

看到這個函數的名字可能令你精神一振,終於到了進程調度部分了,但在這裏,你非但看不到進程調度程序的影子,甚至連進程都看不到一個,這個程序是名副其實的初始化程序:僅僅爲進程調度程序的執行做準備。它所做的具體工作是調用init_bh()函數(在kernel/softirq.c中)把timer,tqueue,immediate三個任務隊列加入下半部分的數組。

time_init(); /*在arch/i386/kernel/time.c中定義*/

時間在操作系統中是個非常重要的概念。特別是在Linux,Unix這種多任務的操作系統中它更是作爲主線索貫穿始終,之所以這樣說,是因爲無論進程調度(特別是時間片輪轉算法)還是各種守護進程(也可以稱爲系統線程,如頁表刷新的守護進程)都是根據時間運作的。可以說,時間是他們運行的基準。那麼,在進程和線程沒有真正啓動之前,設定系統的時間就是一件理所當然的事情了。
我們知道計算機中使用的時間一般情況下是與現實世界的時間一致的。當然,爲了避開CIH,把時間跳過每月26號也是種明智的選擇。不過如果你在銀行或證交所工作,你恐怕就一定要讓你計算機上的時鐘與掛在牆上的鐘表分秒不差了。還記得CMOS嗎?計算機的時間標準也是存在那裏面的。所以,我們首先通過get_cmos_time()函數設定Linux的時間,不幸的是,CMOS提供的時間的最小單位是秒,這完全不能滿足需要,否則CPU的頻率1赫茲就夠了。Linux要求系統中的時間精確到納秒級,所以,我們把當前時間的納秒設置爲0。
完成了當前時間的基準的設置,還要完成對8259的一號引腳上的8253(計時器)的中斷響應程序的設置,即把它的偏移地址註冊到中斷向量表中去。

parse_options(); /*在main.c中定義*/

這個函數把啓動時得到的參數如debug,init等等從命令行的字符串中分離出來,並把這些參數賦給相應的變量。這其實是一個簡單的詞法分析程序。

console_init(); /*在linux/drivers/char/tty_io.c中定義*/

這個函數用於對終端的初始化。在這裏定義的終端並不是一個完整意義上的TTY設備,它只是一個用於打印各種系統信息和有可能發生的錯誤的出錯信息的終端。真正的TTY設備以後還會進一步定義。

kmalloc_init(); /*在linux/mm/kmalloc.c中定義*/

kmalloc代表的是kernel_malloc的意思,它是用於內核的內存分配函數。而這個針對kmalloc的初始化函數用來對內存中可用內存的大小進行檢查,以確定kmalloc所能分配的內存的大小。所以,這種檢查只是檢測當前在系統段內可分配的內存塊的大小,具體內容參見第六章內存分配與回收一節。
下面的幾個函數是用來對Linux的文件系統進行初始化的,爲了便於理解,這裏需要把Linux的文件系統的機制稍做介紹。不過,這裏是很籠統的描述,目的只在於使我們對初始化的解釋工作能進行下去,詳細內容參見第八章的虛擬文件系統。

虛擬文件系統


虛擬文件系統是一個用於消滅不同種類的實際文件系統間(相對於VFS而言,如ext2,fat等實際文件系統存在於某個磁盤設備上)差別的接口層。在這裏,您不妨把它理解爲一個存放在內存中的文件系統。它具體的作用非常明顯:Linux對文件系統的所有操作都是靠VFS實現的。它把系統支持的各種以不同形式存放於磁盤上或內存中(如proc文件系統)的數據以統一的形式調入內存,從而完成對其的讀寫操作。(Linux可以同時支持許多不同的實際文件系統,就是說,你可以讓你的一個磁盤分區使用windows的FAT文件系統,一個分區使用Unix的SYS5文件系統,然後可以在這兩個分區間拷貝文件)。爲了完成以及加速這些操作,VFS採用了塊緩存目錄緩存(name_cach),索引節點(inode)緩存等各種機制,以下的這些函數,就是對這些機制的初始化。

inode_init(); /*在Linux/fs/inode.c中定義*/

這個函數是對VFS的索引節點管理機制進行初始化。這個函數非常簡單:把用於索引節點查找的哈希表置入內存,再把指向第一個索引節點的全局變量置爲空。

name_cache_init();/*在linux/fs/dcache.c中定義*/

這個函數用來對VFS的目錄緩存機制進行初始化。先初始化LRU1鏈表,再初始化LRU2鏈表。

Buffer_init();/*在linux/fs/buffer.c中定義*/

這個函數用來對用於指示塊緩存的buffer free list初始化。

mem_init() /* 在arch/i386/mm/init.c中定義*/

啓動到了目前這種狀態,只剩下運行/etc下的啓動配置文件。這些文件一旦運行,啓動的全過程就結束了,系統也終將進入我們所期待的用戶態。現在,讓我們回顧一下,到目前爲止,我們到底做了哪些工作。
其實,啓動的每一個過程都有相應的程序在屏幕上打印與這些過程相應的信息。我們回顧一下這些信息,整個啓動的過程就一目瞭然了。當然,你的計算機也許速度很快,你甚至來不及看清這些信息,系統就已經就緒,“Login:”就已經出現了,不要緊,登錄以後,你只要打一條dmesg | more命令,所有這些信息就會再現在屏幕上。

【Loading……】出自bootsect.S,表明內核正被讀入。
【uncompress……】很多情況下,內核是以壓縮過的形式存放在磁盤上的,這裏是解壓縮的過程。
下面這部分信息是在main.c的start_kernel函數被調用時顯示的。
【 Linux version 2.2.6(root@lance) (gcc version 2.7.2.3)】Linux的版本信息和編譯該內核時所用的gcc的版本。
【 Detected 199908264 Hz processor】調用init_time()時打出的信息。
【Console:colour VGA+ 80x25,1 virtaul console(max 63)】調用 console_init()打出的信息 。初始化的終端屏幕使用彩色VGA模式,最大可以支持63個終端。
【 Memory: 63396k/65536k available(848k kernel code, 408k reserved, 856k data)】調用 init_mem()時打印的信息。內存共計65536K,其中空閒內存爲63396K,已經使用的內存中,有848K用於存放內核代碼,404K保留,856K用於內核數據。
【 VFS:Diskquotas version dquot_6.4.0 initialized】調用dquote_init()打出的信息。quota是用來分配用戶磁盤定額的程序。關於這個程序請參看第八章。
以下是對設備的初始化 :
【 PCI: PCI BIOS revision 2.10 entry at 0xfd8d1
| PCI: Using configuration type 1
| PCI: Probing PCI
hardware】調用pci_init()函數時顯示的信息。
【 Linux NET4.0 for Linux 2.2
Based upon Swansea University Computer Society NET3.039
NET4: Unix domain sockets 1.0 for Linux NET4.0.
NET4: Linux TCP/IP 1.0 for NET4.0
IP Protocols: ICMP, UDP, TCP】調用socket_init()函數時打印的信息。使用Linux的4.0版本的網絡包,採用sockets 1.0和1.0版本的TCP/IP協議,TCP/IP協議中包含有ICMP,UDP,TCP三組協議。
【 Detected PS/2 Mouse Port。
Sound initialization started Sound
initialization complete Floppy drive(s):fd0 is 1.44M
Floppy drive(s):fd0 is 1.44M FDC 0 is a National
Semiconductor PC87306 】調用device_setup()函數時打印的信息。包括對ps/2型鼠標,聲卡和軟驅的初始化。

看完上面這一部分代碼和與之相應的信息,你應該發現,這些初始化程序並沒有完成操作系統的各個部分的初始化,比如說,文件系統的初始化只是初始化了幾個內存中的數據結構,而更關鍵的文件系統的安裝還沒有涉及,其實,這是在init進程建立後完成的。

發佈了27 篇原創文章 · 獲贊 19 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章