深入理解Linux啓動過程

 
    本文詳細分析了Linux桌面操作系統的啓動過程,涉及到BIOS系統、LILO GRUB引導裝載程序,以及bootsectsetupvmlinux等映像文件,並結合引導、啓動原理和具體的代碼實現機制由淺入深地進行了分析。
    初學者剛接觸Linux桌面系統會感覺系統啓動速度較慢,那麼,爲什麼它的啓動速度慢呢?本文就桌面系統的引導和啓動過程展開分析,以期對初學者熟悉Linux有所幫助。
一、Linux系統的引導過程
    簡單地說,系統的引導和啓動過程就是計算機加電以後所要發生的事情, 比如,加電自檢、引導程序的拷貝和執行、內核的拷貝和執行及用戶程序的執行等。這個過程就是常說的bootstrap,我們把這些歸納爲5個過程, 下面來逐一分析。
   1.BIOS執行階段
    現代計算機系統的存儲機制是“揮發”性的,一旦關機斷電, 存儲在內存中的信息。連同操作系統本身的映射就丟失了。所以,必須把操作系統(內核) 的映像存儲在某些不“揮發” 的介質中,使得開機加電時由一個不“揮發”介質加載操作系統,並轉入運行的過程。這就是引導,也稱自舉。這些不“揮發” 介質通常是指硬盤或軟盤, 也可以是EPROM F1ash存儲器,還可以是網絡中別的節點。要想在開機時從不“揮發” 介質裝入操作系統的映像,系統就要CPU在開機時能執行一段程序,這段程序本身必須存儲在作爲系統內存一部分的EPROM Flash等存儲器中, 而且它們知道怎樣才能從不“揮發” 介質裝入操作系統的映像。事實上,各種CPU 被設計成一個加電後就從某個特殊的地址開始執行指令,所以這些不揮發存儲器就被安置在這個位置上。比如在i386CPU系統中,計算機在加電的那一刻,RAM 芯片中所包含的是隨機數據,還沒有操作系統,在此刻有一個特殊硬件電路在加電時會在C P U 的一個引腳上產生一個RESET邏輯值,硬件電路設置RESET邏輯值以後,代碼寄存器CS的內容爲0xffff,而指令寄存器的內容爲0。也就是說,CPU要從線性地址0xffff0開始處取第一條指令。硬件電路再把這個物理地址映射到RAM 芯片中,BIOS就存放在這裏,這時候處理器就開始執行BIOS代碼了。我們都知道BIOS中包含了幾個中斷驅動的低級程序,可以使用它們來初始化一些硬件設備,但它們是在實模式下工作的。其中實模式地址是由一個seg段和一個off偏移量組成的,相應的物理地址可以使用“seg*16+off” 來計算。
接下來BIOS要做的就是執行一系列的測試,看看到底系統中有什麼設備,以及這些設備是否正常工作。在執行這個過程時,會顯示一些如BIOS系統的版本號等信息。當檢測到可用的設備後就進行一些初始化工作,比如初始化PCI設備以避免I RQ線與I/O端口的衝突,最後顯示系統中安裝的所有PCI設備的一個列表。
在早期的計算機系統中, 類似於BIOS功能的程序非常小,並且不同時期這段程序的設計也不相同。在PC發展早期, 由於當時存儲芯片大小的限制,使得該段程序的目的和功能都很單一 再說,如此小的一段程序很難依靠自身的力量把龐大的操作系統的映像從磁盤裏讀進來。於足,人們又提出了引導扇區的概念,使得存儲在引導扇區中的程序來協助BIOS完成操作系統的引導工作。但是,引導扇區的大小也不過5l2個字節, 能夠容納的信息和代碼也很有限,所以說,操作系統的引導代碼是一個循序漸進的過程,它分佈在不同的角落。當BlOS根據設置將相應的啓動設備的第一個扇區的內容拷貝到RAM 中時, 這些內容被放在物理地址0x0O007c00開始的地方。此後,系統就開始跳到這個地址,並開始執行相應的代碼。
    2.Boot Loader階段
如此小的引導記錄要完成這麼大的任務,壓力是不小的,所以引導扇區的程序及輔助程序必須很簡練,它們都採用彙編語言編寫,這些源代碼都存放在arch/ 下具體CPU名下的boot目錄中,如bootsect.Ssetup.Svideo.S。其中bootsect.SLinux引導扇區的源代碼。這樣,經過編譯、彙編和連接以後,形成了3個組成部分,即引導扇區的映像bootsect、輔助程序setup及內核映像本身(通常是vmlinux,有時也用uImage)。嚴格地說,bootsectsetup並不是內核的一部分。
引導裝載程序就是由BIOS來把操作系統的內核映像裝入到RAM 中所調用的一個程序。這裏我們選擇用硬盤啓動來說明引導裝載程序的執行過程。說起硬盤,大家都知道它是由許許多多的扇區和柱面組成,其中把第一個扇區稱爲主引導記錄(Master Boot RecordMBR),在該扇區中包含了分區信息和一個小程序,這個小程序用來裝載被啓動的操作系統所在分區的第一個扇區。說到這裏我們就要注意,這一段Windows系統和Linux系統是有區別的:Windows系統使用分區表中所包含的一個active標誌來標識這個分區,當然這個分區也可以使用FDISK之類的程序進行設置,但有一個條件就是隻有那些內核映像存放在活動分區的操作系統纔可以啓動。Linux系統的處理方法要更靈活些,它使用GRUB或是LILO程序把這個包含在MBR 中的不完善引導裝載程序給替掉。裝入程序在啓動過程中被執行時,用戶可以選擇裝入哪個操作系統。但LILOGRUB的工作原理又不盡相同,關於它們的詳細介紹可以查閱相關資料。LILO 引導裝入程序被分爲兩部分,MBR 或分區引導扇區包括一個小的引導裝入程序,由BIOS把這個小程序裝入從地址0xO0007c00開始的RAM 中,這個小程序又把自己移到地址0x0009a000, 然後建立實模式棧。接着把LILO 的第二部分裝入到從地址0x0009b000開始的RAM 中, 第二部分又讀取可用操作系統的映射表,並給用戶一個提示符號。這個時候用戶可以從中選擇一個操作系統進行啓動,引導裝入程序就可以把相應分區的引導扇區拷貝到RAM 中並執行,或者是直接把內核映像拷貝到RAM 中。在拷貝內核的過程中,首先是把內核映像所集成的引導裝入程序拷貝到地址0xO009000,然後把setup()代碼拷貝到地址0x00090200,最後把內核映像的其餘部分拷貝到地址0x000100000x00100000, 最終系統執行跳到setup()代碼上。
    3.Setup函數執行階段
Setup()是彙編語言函數代碼,它在內核的編譯鏈接過程中被放到內核的引導裝入程序之後,也就是內核映像文件的偏移量0x200地址處,實際物理地址0x00090200開始的RAM 中。因爲內核不依賴於BIOS, 雖然BIOS已經初始化了大部分硬件設備,但Linux系統還要以自己的方式重新初始化設備,以增加可移植性和健壯性。還要注意的是,內核是工作在保護模式下的。總的來說,setup()函數的作用就是初始化計算機中的硬件設備,併爲內核程序的執行建立環境。比如,檢查系統中可用的RAM 數量、設置鍵盤重複延時速率、顯卡等其他設備的檢查,以及初始化和切換實模式到保護模式等。最後,系統執行跳到startup_ 32彙編函數上。
二、Linux系統的啓動過程
當內核映像被裝載到RAM芯片後,就開始執行內核的代碼,這意味着引導完成,開始進入Linux系統的啓動過程。
    1. Startup_32函數的執行階段
在系統的啓動過程中有兩個startup_320()函數,即位於arch/i386/boot/compressed/head.S文件中實現的。就是在setup()函數結束以後,該函數就被移動到物理地址0X001000000x00001000處,這取決於內核映像是被裝到RAM 的高位還是底位。因爲內核映像文件在編譯連接時所產生的大小不同, 如zImagebzImage大小相差很大,在裝載解壓時所使用的緩衝區也不同,所以他們所處的物理地址是不同的。不過解壓後的映像最終都處在物理地址0x00100000開始的位置。然後跳轉到這個地址處執行解壓後的映像中的另一個startup_32()函數,這個函數爲第一個Linux進程(進程0)建立執行環境,該函數初始化段寄存器、爲進程0建立內核態堆棧等一系列活動。最後識別處理器模式,並跳轉到start_kernel()函數。將Linux內核的映像裝入內存,並且setup()函數做了一些必要的準備,就該startup_32函數開始幹活了。CPU通過一條長程轉移指令轉到映像代碼段開頭的入口startup_32處,對於SMP結構的系統來說,這個時候運行的只是其中的一個處理器,就是所謂的主CPU。其他的次CPU 處於停機狀態, 等待主CPU 的啓動。次CPU在受到啓動進入內核時,同樣也要從startup_32開始執行,所以從startup_32開始的代碼是公共的。但有些操作僅由主CPU來執行,另一些操作由次CPU執行, 這並不意味着主CPU 和次CPU 併發地執行這段程序。實際上,主CPU 是開路先鋒,首先執行這段程序,完成以後逐個啓動次CPU執行,並且等待其完成。所以,在同一時間系統中最多隻有一個處理器在執行這段程序。不管是主CPU還是次CPU,進入startup_32時都運行在保護模式下的段式尋址方式,等到第二個startup_32函數執行到最後時, 就開始執行start_kernel函數。
    2. Start_kernel函數執行階段
到了這個階段纔是真正的內核初始化階段,幾乎內核每個部分的初始化工作都是由這個函數來完成,如頁表的初始化、系統日期和時間的初始化等。從某種意義上說,函數Start_kernel就好像一般可執行程序中的主函數main(),系統在進入這個函數之前已經進行了一些最底限度的初始化,爲這個函數的執行建立起了一個環境,創造了必要的條件。當然,這個函數還要繼續進行內核的初始化,甚至可以說內核的初始化在這裏才真正開始,但它是較高層次的初始化。這個函數的代碼在init/main.C中,從現在開始初始化流程不與CPU 類型和系統啓動;方式相關了。此時系統運行在CPU的特權級,也就是我們常說的內
核模式下。start_ kernel函數主要完成一些數據結構的初始化,主要包括
如下:
printk(linux_banner) 輸出
Linux版本信息;
Setup_arch()(arch/i386/kernel/traps.C)執行與體系結構相關的設置,如內存分析分配內
核頁表, 處理啓動命令行等;
Trap_init() 設置各種人口地址,如異常事件處理程序入口, 系統調用人口,
IniLIRQ() 初始化IRQ 中斷處理機制;
Sched_init() 設置並啓動第一個進程ini_task0 l
Softirq_init() 對軟中斷子系統進行初始化;
Time_initO 讀取實時時間,重新設置時鐘中斷irq0的中斷服務程序入口等;
Console__init() 初始化控制檯和顯示器;
Init_modules() 初始化
kernel__m odule l
Kmem_cache_init0 對內存的slab分配機制初始化{
Mem_init() 虛擬內存計算以及初始化;
Kmem_cache_size_jnit() 初始化slab分配器中的內部cashe和全局cashel
Fork_init() 定義了系統的最大進程數目。此外,還有一些對其他支持的初始化。
隨後,進入resetinit0函數調用kernel__thread()函數爲進程1創建init內核線程,這個內核線程又會創建其他的內核線程程序,並執行/sbin/init程序。此後start_kernel進入一個空閒等待循環(cpu_idle()), 使用系統初始化後CPU 的空閒時間片,init內核線程首先要鎖定內核,然後調用do_basic_setup()來初始化外部設備及加載驅動程序。在do_basic_setup()函數調用完之後,init()函數會釋放初始化函數所用的內存,並且打開/dev/console設備重新定向控制檯,使用系統調用execve來執行用戶態程序/sbin/init
到目前爲止,Linux內核的初始化工作完成,此時系統中已經存在5個運行實體:init線程、kflushd核心線程、kupdate核心線程、kswapd核心線程和keventd核心線程。本身所在的執行體其實就是一個線程,不過是由手工創建的。它在創建了init0線程以後就進入cpu_idle循環, 不會在進程列表中出現。如果使用pstree命令,則不能列出該線程。
最後,init程序會根據inittab文件中的設置信息啓動相應的用戶程序。當init得到控制並啓動mingetty顯示登錄界面及提示後,系統啓動完成。
三、小結
從加電自檢開始, 引導過程要經歷數十個回合來拷貝執行,使用不同的引導裝載程序所使角的流程也不同。當把內核映像拷貝到RAM中展開後, 內核開始掌管主權,開始了自己的 “事業”。內核線程init()的任務仍然還是初始化,當然是進一步的、更高層次上的初始化。
事實上,從引導結束、CPU轉入內核映像開始,一共有三個階段的初始化:第一階段是從進入startup_32()開始, 到進入start_kernel()start_ secondary()。這個階段主要是對CPU 自身的初始化,主CPU和次CPU 都要經歷這種初始化,但是主CPU要多一些貢獻。第二階段是從進入start_kernel()開始,到進入cpu_idle()。這個階段主要是對系統的寶貴資源的初始化, 僅由主CPU進行。第三階段是init()的執行,這是對系統接近用戶層的初始化, 這個時候表面上看已經沒:有主CPU和次CPU之分,但誰執行init()取決於競爭調度的結果。事實上,由於主CPU預先留了一手, 實際上還是由它來執行。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章