內容提要
本文以RedHat9.0和i386平台為例,剖析了從使用者開啟電源直到螢幕出現命令列提示符的整個Linux啟動過程。並且介紹了啟動中涉及到的各種檔案。
閱讀Linux原始碼,無疑是深入學習Linux的最好方法。在本文對Linux啟動過程的介紹中,我們也嘗試從原始碼的視角來更深入的剖析Linux的啟動過程,所以其中也簡單涉及到部分相關的Linux原始碼,Linux啟動這部分的源碼主要使用的是C語言,也涉及到了少量的彙編。而啟動過程中也執行了大量的shell(主要是bash shell)所寫指令碼。為了方便讀者閱讀,筆者將整個Linux啟動過程分成以下幾個部分逐一介紹,大家可以參考下圖:
當使用者開啟PC的電源,BIOS開機自我測試 (POST),按BIOS中設定的啟動裝置(通常是硬碟)啟動,接著啟動裝置上安裝的引導程式lilo或grub開始引導Linux,Linux首先進行核心的引導,接下來執行init程式,init程式調用了rc.sysinit和rc等程式,rc.sysinit和rc當完成系統初始化和運行服務的任務後,返回init;init啟動了mingetty後,開啟了終端供使用者登入系統,使用者登入成功後進入了Shell,這樣就完成了從開機到登入的整個啟動過程。
下面就將逐一介紹其中幾個關鍵的部分:
第一部分:核心的引導(核內引導)
Red Hat9.0可以使用lilo或grub等引導程式開始引導Linux系統,當引導程式成功完成引導任務後,Linux從它們手中接管了CPU的控制權,然後CPU就開始執行Linux的核心映象代碼,開始了Linux啟動過程。這裡使用了幾個組譯工具來引導Linux,這一步泛及到Linux原始碼樹中的“arch/i386/boot”下的這幾個檔案:bootsect.S、setup.S、video.S等。
其中bootsect.S是產生開機磁區的彙編源碼,它完成載入動作後直接跳轉到setup.S的程式入口。setup.S的主要功能就是將系統參數(包括記憶體、磁碟等,由BIOS返回)拷貝到特別記憶體中,以便以後這些參數被保護模式下的代碼來讀取。此外,setup.S還將video.S中的程式碼封裝含進來,檢測和設定顯示器和顯示模式。最後,setup.S將系統轉換到保護模式,並跳轉到 0x100000。
那麼0x100000這個記憶體位址中存放的是什麼代碼?而這些代碼又是從何而來的呢?
0x100000這個記憶體位址存放的是解壓後的核心,因為Red Hat提供的核心包含了眾多驅動和功能而顯得比較大,所以在核心編譯中使用了“makebzImage”方式,從而產生壓縮過的核心,在RedHat中核心常常被命名為vmlinuz,在Linux的最初引導過程中,是通過"arch/i386/boot/compressed/"中的head.S利用misc.c中定義的decompress_kernel()函數,將核心vmlinuz解壓到0x100000的。
當CPU跳到0x100000時,將執行"arch/i386/kernel/head.S"中的startup_32,它也是vmlinux的入口,然後就跳轉到start_kernel()中去了。start_kernel()是"init/main.c"中的定義的函數,start_kernel()中調用了一系列初始化函數,以完成kernel本身的設定。start_kernel()函數中,做了大量的工作來建立基本的Linux核心環境。如果順利執行完start_kernel(),則基本的Linux核心環境已經建立起來了。
在start_kernel()的最後,通過調用init()函數,系統建立第一個核心線程,啟動了init過程。而核心線程init()主要是來進行一些外設初始化的工作的,包括調用do_basic_setup()完成外設及其驅動程式的載入和初始化。並完成檔案系統初始化和root檔案系統的安裝。
當do_basic_setup()函數返回init(),init()又開啟了/dev/console裝置,重新導向三個標準的輸入輸出檔案stdin、stdout和stderr到控制台,最後,搜尋檔案系統中的init程式(或者由init=命令列參數指定的程式),並使用 execve()系統調用載入執行init程式。到此init()函數結束,核心的引導部分也到此結束了,