Linux系統啓動的標準流程

轉自:http://www.kerneltravel.net/journal/i/04.htm

Linux系統啓動的標準流程

系統的啓動是指從計算機加電到顯示用戶登陸提示的整個過程。我們將在這裏對整個流程以及關係到的一些內容做討論。過程主要可以分爲兩個階段:載入內核和準備運行環境,我們分別進行討論。本部分的討論只基於i386硬件架構,但大部分內容是有共通性的。

圖一 啓動過程綜述

載入內核(將內核載入內存,並將控制權傳遞給它)

    計算機加電到Boot Loader開始工作,硬件含量遠大於軟件含量,所以這裏暫不提及,如果實在有關心的朋友,請先彆着急,我們將在下期裏討論它。

這一階段是 Boot Loader 的主戰場。它必須將可執行的內核映像和內核啓動所需的額外數據信息從存儲介質上載入內存,這並不是件簡單的工作,因爲除了從硬盤載入,可能還會需要從網絡引導服務器這樣的外部介質上載入。各種紛繁蕪雜的文件系統類型也給載入帶來了巨大的挑戰。

Boot Loader 可能還需要改變CPU的運行特權級別,然後就可以讓內核投入運行了。

除此之外, Boot Loader 還要完成一些其它功能,比如從BIOS中獲取系統信息,或者從啓動時的命令行參數中提取信息等。有的 Boot Loader 還要扮演引導選擇工具的角色,方便用戶選擇不同的操作系統。

Boot Loader的職責:

l         判斷到底要載入什麼,這可以要求用戶進行選擇

l         載入內核和它可能需要用到的相關數據,比如initrd或者其它參數

l         爲內核準備好運行環境,比如,讓CPU進入特權模式

l         讓內核投入運行

Boot Loader的歷史變遷:

早期的Linux只支持軟盤引導扇區和 Shoelace 兩種 Boot Loader。 Shoelace 是從Minix繼承下來的、文件系統相關的 Boot Loader。它只支持 Minix 文件系統。當時Linux只使用 Minix 一種文件系統,所以這樣做並沒什麼問題。可是, Minix 文件系統存在不能保存創建、修改和訪問時間信息;文件名長度限制在14個字節等問題。隨着Linux的發展,這些與傳統Unix文件系統大相徑庭的缺陷越來越讓人難以忍受,它已經不適合作爲Linux的主要文件系統了。

爲了支持其它文件系統的實現,Linux引入了VFS(虛擬文件系統)。這個舉措很快就引起了熱烈的反響,一大批新的文件系統實現出現了。其中一個 Minix 文件系統的變體,擴展文件系統 Xiafs (根據它的作者命名)突破了Minix 文件系統的文件名長度限制,將此長度一舉提高到全部30個字符。當時文件系統之間的競爭着實激烈,很難看出誰會勝出,甚至搞不清楚會不會有一個最終的“贏家”。

       儘管不確定性很大,但是有一點卻是清楚的:不管最後哪種文件系統會受到青睞,但是除了 Minix 作爲根文件系統,誰也不能從硬盤上啓動,因爲Shoelace只支持Minix文件系統。LILO應運而生了。由於支持多種文件系統(當時內核支持的主流文件系統已經有 Minix ,擴展文件系統 ext ,Xiafs 。還有人在移植 BSD 的 FFS ,根本看不出來什麼時候是個盡頭)在實現和維護上難度太大,而 Boot Loader 也不應該成爲人們試驗新的文件系統的絆腳石,所以LILO採取了和文件系統無關的設計。

這種設計經受住了時間的考驗,被證明是非常成功的。即使在今天,LILO仍舊可以從內核支持的絕大部分文件系統的硬盤上啓動。但是,由於ext2歷經了這麼長的時間一直沒有大的演變,成爲了事實上的標準,所以跟文件系統相關的Boot Loader又漸漸流行了起來。

    儘管ext2已經能滿足大部分人的日常需要,但是文件系統的設計者們還是在研製以日誌機制爲特徵的新的文件系統,並且已經取得了相當大的進展。考慮到當前又有可能出現多種文件系統的實現同時並存的情況,因此對與文件系統無關的Boot Loader的需求可能會再次變得強勁。

初始化基本的操作環境

一旦內核開始運行,它會初始化內部的數據結構,檢測硬件,並且激活相應的驅動程序,爲應用軟件的準備運行環境。期間包含一個重要操作——應用軟件的運行環境必須要有一個文件系統,所以內核必須首先裝載root文件系統。由於我們的目的是介紹基本流程,所以相關的硬件初始化細節就不再討論,相關內容在下一期雜誌中會有詳細介紹。

硬件初始化完成後,內核着手創建第一個進程——初始進程。說是創建, 其實也不盡然,該進程其實是整個硬件上電初始化過程的延續,只不過執行到這裏,進程的邏輯已經完備,所以我們就按照進程的創建方式給它進行了“規格化” ——我們把這個初始進程也叫做“硬件進程”,它會佔據進程描述符表的第一個位置,所以可以用task[0][k1] 或INIT_TASK表示。該進程進而會再創建一個新進程去執行init()函數,其實,這個新進程纔是系統第一個實際有用的進程,它會負責接着執行下一個階段的初始化操作; 而初始進程(INIT_TASK)自己則會開始執行idle循環,也就是說,內核初始化完成之後,初始進程唯一的任務就是在沒有任何其它進程需要執行的時候, 消耗空閒的CPU時 間(因此初始進程也被稱爲idle進程)。

下一階段的初始化工作要比前一階段輕鬆一點,因爲現在是由一個真正進程來接手負責完成它們了,而前一階段都是由“硬件進程”手工去做的。在此階段,這個由INIT_TASK創建的新進程需要初始化總線、網絡並啓動系統中的各種系統內核後臺線程,然後再初始化外設、設置文件格式,在這之後,它要爲進入系統做最後的準備——初始化文件系統,安裝root文件系統,打開/dev/console設備,重定向stdin、stdout和stderr到控制檯,然後搜索文件系統中的init程序,並使用 execve()系統調用加載執行init程序。系統自此進入了用戶態。

裝載root文件系統

爲了裝載文件系統,內核需要:1、知道root文件系統位於那個存儲介質上;2有訪問該種介質的驅動程序。

最常見的情況是,root文件系統是ext2文件系統,位於IDE硬盤上。這種情況下需要的操作很簡單:將設備號作爲參數給內核就可以了,IDE的設備驅動程序通常都是編譯進內核的。

如果內核沒有相關介質的驅動程序,問題就會變得更爲複雜。而這種情況並不罕見,比如Linux安裝盤使用的“通用”內核一般都會碰到這種情況。如果內核把所有支持的硬件的設備驅動程序都包含進來,就會變成一個龐然大物;而且一些驅動程序在檢測硬件的時候會影響其它設備。

這個問題可以通過initrd機制解決,它允許在裝載實際的root文件系統之前先使用RAM文件系統。除了上述兩個原因,引入initrd還可以解決內核的動態合成問題。(詳見參考資料一。)

不過,我們應該注意到,initrd在整個啓動過程中並不是從來就有的,它可以說是一個插件,爲了解決以上問題,而被加入啓動過程,像圖一所示,Linux系統在啓動時也可以不選擇它。

爲什麼要引入initrd?

Linux啓動過程中肯定要載入內核鏡像,在此過程中有些要素必須考慮:

首先,內核鏡像不能太大。由於受到各種硬件和兼容性的限制,Linux的內核鏡像不能太大,但是這並不容易做到。Linux內核的核心部分本身就不小了;而且還必須加入會使用到的驅動程序。

其次,要支持儘可能多的硬件設備。我們在啓動過程中有一件重要工作:掛載root文件系統,因爲進一步的數據和應用軟件都在其上,所以我們的內核必須能夠訪問root文件系統。對於一般用戶,如果他們使用IDE硬盤上的ext2文件分區作爲root文件系統,不會有什麼問題。因爲不管是IDE硬盤還是ext2文件系統,它們的驅動肯定會包含在內核鏡像自身裏面。但是,確實存在一些特殊情況:比如說我們希望發行Linux系統的安裝光盤,那麼對光盤的驅動,就不一定包含在內核裏面了。(有人可能要奇怪了,咦,光盤中的內核鏡像不都已經讀進來了嗎,怎麼內核還訪問不了光盤呢? 注意,讀入內核鏡像的是 Boot Loader ,內核並不具備 Boot Loader 的功能。)如果沒有光盤的驅動,我們又怎麼把光盤裏的軟件包安裝到用戶的計算機裏呢?把驅動程序預先編譯到內核裏?聽起來還不錯,可是如果我們除了光盤還有一些其它的安裝介質,那麼所有這些驅動就會讓內核鏡像龐大不堪。

而且,還有更嚴重的問題,各種不同的驅動程序很有可能會發生衝突,特別是以前ISA設備佔市場主導地位的時候,這種衝突簡直難以避免。

那時的解決辦法是發行商提供預先編譯好的支持各種設備的不同內核,把每個內核放進一張軟盤,隨發行包一起交給用戶,用戶自己選擇裝有合適內核的軟盤進行引導。或者給用戶提供製作引導盤的工具,讓用戶在安裝前製作自己的啓動盤。當然,哪一種辦法都不能讓人滿意。

唯一的希望在於使用模塊化機制。在內核啓動的時候調用相應的模塊加載驅動程序,然後訪問root文件系統。無論是通過內核對設備做進一步的分析還是直接從用戶那裏得到配置信息,先配置再加載模塊的辦法,都能有效地避免衝突的發生。

除了在安裝的時候需要在掛載root文件系統之前調用相應的模塊之外,在完成安裝的系統上,我們可能仍然需要在掛載root文件系統之前調用一些模塊。這主要是爲給計算機進行配置——一般都要針對不同的計算機進行內核配置。

理想情況下,用戶按照自己的實際情況配置編譯文件,重新編譯內核,一步步完成這種工作。但是沒有幾個用戶喜歡這種冗長並且極易出現錯誤的工作。而且編譯和生成內核需要相應的工具,可是大部分用戶不需要這些工具。

在安裝的過程中可以直接編譯一個整體式的內核,但這並不能很好的解決問題:首先,所有的編譯工具還是需要的,其次,編譯過程中出現差錯導致無法完成任務的概率太大了。所以,我們仍然要使用模塊機制:模塊機制很可靠,出了錯誤也只不過不加載對應的模塊而已,不會使整個任務失敗。而載入模塊,像前面說的,也是在掛載root文件系統之前就要得到模塊的。

基於以上理由,Linux引入了initrd機制。

initrd做什麼

initrd允許系統在啓動的時候載入一個RAM盤,這個RAM盤可以被當作一個root文件系統,程序可以在其上運行。(有兩重含義,第一,程序在上面;第二,程序的文件系統環境也在上面。)在此之後,可以從別的設備上掛載一個新的root文件系統,先前的root文件系統(initrd)就會被移動到一個目錄上去,最終被卸載掉。

爲什麼要使用RAM盤呢?首先,使用RAM盤能方便的支持以後可能發生的變化;另外,也是爲了保持Boot Loader 工作儘可能的簡單。在系統引導時,除了內核鏡像之外,Boot Loader把所有相關的信息作爲一個文件讀入內存,內核在啓動中將該文件作爲一段連續的內存塊看待。也就是把它當作RAM盤來 使用了。正因爲如此,這種機制被稱作“初始 RAM 盤(initial RAM Disk)”,縮寫成 initrd。

initrd主要用來把系統的啓動劃分爲兩個階段:初始啓動的內核只需保留最精簡的驅動程序最小集,此後,在啓動必須加載附加的模塊時,從initrd中加載。

initrd進行的操作

使用initrd的時候,典型的系統啓動的流程變爲:

1)  Boot Loader讀入內核鏡像以及initrd文件

2)  內核將initrd文件轉成“普通”的RAM盤,並且釋放掉initrd文件佔用的內存。

3)  initrd被當作root文件系統,以可讀可寫(read-write)方式安裝。

4)  /linuxrc被執行(它可以是任何可執行文件,包括腳本在內;它以uid0身份執行,基本上能完成所有init程序可以做的工作)

5)  linuxrc安裝“實際”的root文件系統

6)  linuxrc通過pivot_root系統調用將root文件系統放置在root目錄下。

7)  常用的啓動流程(比如調用/sbin/init)開始執行。

8)  卸載initrd文件系統。

注意,這是一個典型流程。其實initrd機制可以通過兩種方式使用:要麼就是作爲一個普通的root文件系統使用,這樣的話第5、第6兩個步驟可以被略過,直接執行/sbin/init(我們的試驗系統就是利用這種方法);要麼作爲一個過渡環境使用,通過它內核可以繼續裝載“實際”的root文件系統。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章