理解Linux的啓動過程

從按下PC電源,到出現熟悉的bash提示符"$"或進入漂亮的KDE/GNOME桌面,這是我們每天開機必經的過程。那麼,在這短短几十秒內,Linux是怎樣啓動的呢?本文介紹Linux的啓動過程。
    平臺:PC機, Ubuntu 5.10

基礎知識
BIOS (Basic I/O System,基本輸入/輸出系統)
    BIOS,完整地說應該是ROM-BIOS,是隻讀存儲器基本輸入/輸出系統的簡寫,它實際上是被固化到計算機中的一組程序,爲計算機提供最低級的、最直接的硬件控制。準確地說,BIOS是硬件與軟件程序之間的一個“轉換器”或者說是接口(雖然它本身也只是一個程序),負責解決硬件的即時需求,並按軟件對硬件的操作要求具體執行。
  從功能上看,BIOS分爲三個部分:
  1.自檢及初始化程序;
  2.硬件中斷處理;
  3.程序服務請求。
    這裏我們主要關注第一部分——自檢及初始化程序:這部分負責啓動計算機,具體有三個部分,第一個部分是用於計算機剛接通電源時對硬件部分的檢測,也叫做加電自檢(POST),功能是檢查計算機是否良好,例如內存有無故障等。第二個部分是初始化,包括創建中斷向量、設置寄存器、對一些外部設備進行初始化和檢測等,其中很重要的一部分是BIOS設置,主要是對硬件設置的一些參數,當計算機啓動時會讀取這些參數,並和實際硬件設置進行比較,如果不符合,會影響系統的啓動。
  最後一個部分是引導程序,功能是引導DOS或其他操作系統。BIOS先從軟盤或硬盤的開始扇區讀取引導記錄,如果沒有找到,則會在顯示器上顯示沒有引導設備,如果找到引導記錄會把計算機的控制權轉給引導記錄,由引導記錄把操作系統裝入計算機,在計算機啓動成功後,BIOS的這部分任務就完成了。
    關於BIOS的詳細介紹,可以google一下,這篇文章就不錯。

硬盤

    就物理組成來說,一個硬盤封裝裏有多個盤片(platter),每個盤面有兩個(surface)。在盤片上都有一個磁頭(head)來進行硬盤盤片的讀/寫,盤片繞(spinder)旋轉一週時磁頭所走過的軌跡即磁道(track),所有盤片的同一磁道構成了磁柱(cylinder)。磁道又被分爲多個扇區(sector),扇區是最小的磁盤存儲單位,即硬盤分區時的最小單位——通常爲512KB。磁道由縫隙(gap)分開,gap中存儲的不是數據位,而是用來確認扇區的格式位。

MBR
    主引導扇區(MBR, Master Boot Recorder)是硬盤中最重要的部分,它記錄了硬盤的分區信息、引導信息。CU上面有一篇介紹MBR的文章
   
注意這裏所說的MBR是指BIOS中指定的啓動設備中的MBR。如果以軟盤啓動,則MBR是軟盤的第一個扇區。如果是硬盤,則是硬盤的第一個扇區。如果有多個硬盤呢?那麼就是BIOS中指定啓動硬盤的第一個扇區!

run-level
運行  $ less /etc/inittab
顯示下列信息:
# /etc/init.d executes the S and K scripts upon change
# of runlevel.
#
# Runlevel 0 is halt.
# Runlevel 1 is single-user.
# Runlevels 2-5 are multi-user.
# Runlevel 6 is reboot.
    上面顯示的就是當前可用的登錄模式,共有0~6中級別。常用的是3和5。
0:關機
1:單用戶模式(系統有問題時的登錄模式,相當於WINDOWS的"安全模式“)
2:對於Debian/Ubuntuare來說,2~5都是相同的——多用戶圖形界面模式。對於其他發行版來說,3可能是多用戶文本模式,4爲系統保留,5爲多用戶圖形模式,具體的定義可以查看該發行版對應的/etc/inittab文件內容。
6:重新啓動
另外,還可能有"S"級,它等同於1的單用戶級別。
    運行 $ runlevel 可以查看系統當前運行級別

如果把運行級別設成了0或6,想象會出現什麼情況?如何解決呢?

    WINDOWS在啓動時,如果按下F8,會出現“安全模式“、”正常啓動“、”MS-DOS“模式的選擇。相當於Linux run-level的1,5,3(不對應於Debian/Ubuntu)。

關於Debian/Ubuntu中的run-level,看這裏!

基本流程
1, 加載BIOS硬件信息,並取得第一個開機裝置的代號。
2,加載第一個開機裝置中MBR的boot loader(即lilo, grub, spfdisk等)引導信息。
3,加載Linux內核,內核開始解壓縮,並驅動硬件。
4,內核執行init程序,並獲得run-level信息;
5,init 執行 /etc/init.d/rcS 程序;
6,加載內核模塊(module)
7,init 執行 對應run-level 級的腳本文件( Scripts );
8,執行 /bin/login 程序,等待用戶登入;
9,用戶登入之後,開始以shell控制系統(如果以圖形界面登錄,則運行圖形界面)。

下面具體介紹流程中的步驟:
1,加載BIOS
    系統上電時,最先讀取BIOS信息。BIOS(Basic Input/Output System)是計算機與外設最底層的接口,它存儲了計算機啓動時最先加載的數據,包括:CPU類型、啓動設備順序、硬盤大小/類型、芯片組工作狀態、外設I/O地址、PnP (Plug and Play,既插既用設備)的開啓與否、內存時鐘等。
    讀取了BIOS設定值後,系統根據BIOS數據進行開機自我檢測(Power On Self Test, POST),對硬件進行初始化,並設定PnP設備,指定啓動設備,之後從磁盤的MBR中讀取Bootloader數據。

2,加載Boot Loader
    系統讀完BIOS之後,接着加載第一個引導磁盤的第一個扇區(MBR),boot loader就位於MBR中。此時,啓動工作的接力棒就交到了boot loader的手中。

常用的boot loader有lilo, grub, spfdisk等,現在最流行的是grub,我用的Ubuntu中,boot loader就是grub,本文假設boot loader是grub,其實基本原理都是一樣的。

    爲什麼要在MBR中安裝boot loader呢?它到底有什麼作用?實際上,boot loader的作用就是加載OS內核。系統在啓動時,要讀取文件以加載內核,必須能夠識別硬盤文件系統,但這時候系統還在啓動過程中,對文件系統信息一無所知。boot loader就輔佐系統識別文件格式,加載內核。boot loader不僅不光能夠識別Linux內核,而且能識別WINDOWS內核.所以,如果要安裝多系統,那麼要在MBR中安裝能支持這些系統文件系統的 boot loader.
    如果是以grub啓動,加載它後,會有個選擇啓動那個OS的菜單,當你作出選擇後,grub就從被選定OS所在的扇區中加載相應的內核.

3,加載內核
    在grub的菜單選定啓動Linux後,系統從Linux所在的磁盤載入內核。內核一般位於/boot目錄中,比如,我的系統中內核爲 /boot/vmlinuz-2.6.12-10-686。當然,可以有不同版本的內核位於/boot目錄中,可以通過grub菜單選擇啓動的內核版本。
   
$ uname -r  : 顯示當前運行的內核版本
   
    vmlinuz是可引導的、壓縮的內核。“vm”代表“Virtual Memory”。vmlinux是未壓縮的內核,vmlinuz是vmlinux的壓縮文件.
    加載內核時還應該注意”虛擬硬盤“,即RAM DISK。以後有機會再歸納。


圖1 有建立RAM Disk可能的啓動流程

 
圖2 更明瞭的啓動流程

總之,boot loader現將Linux內核加載到內存中(可能基於initrd建立RAM DISK),然後將BIOS中關於設備的數據傳遞給內核,內核建設設備,加載相應的驅動程序.

4,init進程
    內核被加載之後,它執行的第一個程序就是/sbin/init.init 它利用 /etc/inittab配置文件獲取開機等級 ( Run level ) 之外, 並基於run level 選擇開機時啓動的服務.
    通過 $ less /etc/inittab 查看開機init讀取的開機配置文件內容.注意下面這行:
    # The default runlevel.
    id:2:initdefault:
    Ubuntu默認的run level是2.正如前面所說,把它設置成2,3,4,5都是多用戶圖形模式登錄.千萬別設成0或6!
      
關於init進程,這裏多說幾句(針對一般UNIX系統)
pid=0 : swapper進程,用於進程調度.它位於內核內部,是系統進程.
pid=1 : init進程, 有對應的程序/sbin/init,儘管運行它需要管理員權限,但實際上它是個用戶進程,不位於內核中.系統啓動時,內核被加載後調用init進程.
pid=2 : pagedaemon, 用於支持虛擬內存的頁.

    init進程還負責處理孤兒進程(父進程在子進程之前終止,則子進程成爲孤兒進程, 此時,init繼承爲他們的父進程).

5,init 執行 /etc/init.d/rcS 程序 (重點)

在Debian/Ubuntu系統中,初始化腳本是/etc/init.d/rcS,在Rad Hat中是/etc/rc.d/rc.sysinit。這裏麪包含了裝入文件系統,設置時間,打開交換分區,得到主機名等等內容。

    在前面提到的inittab文件中,緊接runlevel之後有如下內容:
# Boot-time system configuration/initialization script.
# This is run first except when booting in emergency (-b) mode.
si::sysinit:/etc/init.d/rcS

    inittab的主要功能是描述引導及正常操作時,應該在何種運行等級下啓動什麼程序,每個運行等級的具體項目完全可以通常/etc/inittab來定義,但Debian有一個更健壯的方案sysvinit,它被認爲是init最強大的應用程序之一。Debian組織inittab的方式是把運行等級的大部分定義從inittab中移出來,移到一個腳本層次中去。惟一直接從inittab啓動的程序只有getty,它用於虛擬設備上啓動登錄提示符,保留它因爲它們要求特殊處理,在inittab之外處理要困難得多。

  inittab來啓動所有軟件當然是可能的,但將所有配置寫在同一個文件既不方便查看也不方便維護,所以文件裏會加上這許多行:

  l0:0:wait:/etc/init.d/rc 0
  l1:1:wait:/etc/init.d/rc 1
  l2:2:wait:/etc/init.d/rc 2
  l3:3:wait:/etc/init.d/rc 3
  l4:4:wait:/etc/init.d/rc 4
  l5:5:wait:/etc/init.d/rc 5
  l6:6:wait:/etc/init.d/rc 6

  這些行實際決定了系統在各個運行等級下的行爲。它們如何做到的也許並不明顯,但至少我們知道主要意思:首先每行都有個符號ID lx,lx表示runlevel x;其次,每行只在一個運行等級下激活,該運行等級對應着符號ID中的數字x。命令執行時,init停下來,直到進程結束。最後,每個命令行調用一個腳本 /etc/init.d/rc x,這裏x代表當前運行等級的數字。顯然各運行等級的具體任務在/etc/init.d/rcS腳本中安排。
      
init把從inittab中獲取的run-level值作爲參數傳遞給rc

rc與rcS腳本的區別 (rc = run command)
rc   : This file is responsible for starting/stopping services when the runlevel changes.
rcS : Call all S??* scripts in /etc/rcS.d in numerical/alphabetical order.

6,加載內核模塊(module)
    2.6的內核支持動態模塊的加載,關於內核模塊,我另外寫一篇。

7,init 執行 對應run-level 級的腳本文件( Scripts )
    到目前爲止,內核及其模塊被加載了,也完成了系統的初始化。現在要做的工作就是開啓系統服務。由於不同的run-level需要開啓的服務不同,所以系統爲不同的run-level設定了不同的腳本。
   
$ ls -d /etc/rc*.d
   
        可以看到有rc0~rc6及rcS,共8個目錄。他們所包含的文件都是連接,指向/etc/init.d/目錄中的文件。比較各目錄中的文件你會發現, rc2~rc5中的內容是相同的。正好驗證了Debian/Ubuntu中run-level 2~5是等效的。

以S開頭的表示在對應級別Start的服務,以K開頭表示Kill的服務。緊跟S或K之後的兩位數字決定了啓動順序,數字小的先運行。

關於rcS/rcS.d,我還不是很清楚,哪位大俠指點?

8,執行 /bin/login 程序,等待用戶登入
    這個就不用多說了。看過APUE的都知道login要讀取/etc/passwd文件。關於passwd文件,請參考APUE,p161 (第二版新版哦!)
   
OK,完畢,謝謝收看。有不足之處,懇請指正!

 在系統加電引導時,init從run-level = 0開始,一級一級往上運行到inittab中定義的默認run-level。在run-level過渡時,init將run-level值作爲參數傳遞給rc,進而執行啓動腳本。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章