千裡之行,始於足下!系統啟動往往被認為是正式學習Linux內核的開始,要使用一個系統,無論Windows還是Linux,首先要做的就是將它啟動。這裡我們就一起學習一下當用戶打開計算機電源之後所發生的事。也就是說,我們要研究Linux內核映像是如何被拷貝到內存中的,又是如何被執行的。在操作系統中,啟動指把一部分操作系統裝載到主存中並讓處理器執行它,也表示內核數據結構的初始化、一些用戶進程的創建以及把控制權轉移到其中某個進程。
相關閱讀:Linux內核學習筆記——預備知識 http://www.linuxidc.com/Linux/2012-01/51497.htm
計算機啟動是一個冗長乏味的任務,因為在最開始時每個硬件設備(包括RAM)都處於一種隨機、不可預知的狀態。計算機在最初加點的那一刻是毫無用處的,你可以把它當做一堆廢銅爛鐵的組合,因為此時RAM芯片中包含的是隨機數據,此時還沒有操作系統運行在上面。順便提一下,RAM是隨機存取存儲器,內容可隨意存取,且存取速度與存儲單元位置無關,斷電時丟失存儲內容。
在開始啟動時,有一個特殊的硬件電路在CPU的一個引腳上產生一個RESET邏輯值。RESET產生以後,就把處理器的一些寄存器(包括cs和eip寄存器)設置為固定的值,並執行在物理地址0xfffffff0處找到的代碼。硬件把這個地址映射到一個只讀、持久的存儲芯片中,即ROM。ROM中存放的程序集在80x86體系中通常叫作基本輸入/輸出系統,也就是我們熟悉的、大名鼎鼎的BIOS。所有操作系統在啟動時,都要通過這些過程對計算機硬件設備進行初始化。Linux在啟動階段必須使用BIOS,此時linux必須要從磁盤或其他外部設備中獲取內核映像。
BIOS啟動過程實際執行4個操作:
1)對計算機硬件執行一系列測試,用來檢測現在都有什麼設備以及這些設備是否正常工作,這一過程稱為上電自檢
2)初始化硬件設備
3)搜索一個操作系統來啟動,根據BIOS的設置,這個過程可能要試圖訪問系統中的軟盤、硬盤和CD/DVD-ROM的第一個扇區(引導扇區),至於訪問次序則可以由用戶自己定義,一般在開機自檢後按F2(有些廠家計算機是F8或F12,屏幕上會有提醒)會進入相應的設置界面進行設置,比如我們用光盤安裝系統時,設置訪問首選項為CD/DVD-ROM,則系統會先訪問光驅,如果其中有系統安裝盤,如Windows7或Ubuntu11.10,則執行引導扇區,進入安裝界面;當然,如果光驅中沒有光盤,則系統按順序繼續依次訪問軟盤、硬盤等,當系統訪問到硬盤時就會正常啟動已安裝的系統
4)只要找到一個有效的設備,就把第一個扇區的內容拷貝到RAM中從物理地址0x00007c00開始的位置,然後跳轉到這個地址處,開始執行剛才裝載進來的代碼BIOS完成上述工作之後,會調用引導裝入程序(boot loader),用來把操作系統的內核映像裝載到RAM中。
現在我們舉例來看一下引導裝入程序究竟是如何工作的:
以硬盤啟動為例,對於一個硬盤來說,最多只能創建三個主分區,一個擴展分區。在擴展分區上又可以劃分若干邏輯分區。對於一個常規的操作系統來說,一般只能安裝在主分區中,並且安裝在主分區中的操作系統遠比安裝在邏輯分區中的方便管理且安全得多。(注:Linux就可安裝在邏輯分區中)硬盤的物理第一扇(0柱面,0面,1扇區)是硬盤主引導記錄扇MBR,計算機啟動時,首先就讀取該扇,讀出硬盤分區表,從中選擇三個主分區中唯一一個具有活動標記的分區,引導該分區上的操作系統。也就是說,無論有幾個主分區(≤3),其中必須有一個分區是活動的。該扇區中除了包括分區表外,還有一個小程序,這個小程序用來裝載被啟動的操作系統所在分區的第一個扇區。按照這種方法,只有那些內核映像存放在活動分區中的操作系統才可以被啟動。Linux的處理方式避開了這種缺憾,因為Linux使用一個巧妙的引導裝入程序取代這個MBR中不完善的程序。
眾所周知的Linux引導裝入程序(boot loader)是LILO,當然還有更為先進的GRUB。LILO或許被裝在MBR上,代替那個裝載活動引導扇區的小程序,或許被裝載在每個磁盤分區的引導扇區上。這兩種情況的結果是相同的:裝入程序在啟動過程中被執行,用戶可以選擇裝入哪個操作系統(相信裝過雙系統的用戶對於那個啟動系統選擇界面都不會陌生)。當然,並不是所有的電腦都能容忍修改主啟動記錄(MBR),這也是LILO的局限所在。
我們假定LILO引導裝入程序將裝載的是Linux內核映像,則LILO引導裝入程序依賴BIOS例程,執行如下步驟:
1)調用一個BIOS過程顯示“Loading”信息
2)調用一個BIOS過程從磁盤裝入內核映像的初始部分,即將內核映像的第一個512字節從地址0x00090000開始存入RAM,將setup( )函數的代碼從地址0x00090200開始存入RAM中
3)調用一個BIOS過程從磁盤中裝載其余內核映像,並把內核映像放入從低地址0x00010000(適用於使用make zImage編譯的小內核映像)或者從高地址0x00100000(適用於使用make bzImage編譯的大內核映像)開始的RAM中。
4)跳轉到setup( )代碼:setup( )匯編語言函數的代碼由鏈接程序放在內核映像文件的偏移量0x200處,因此引導裝入程序可以輕松的確定setup( )代碼的位置,並把它拷貝到從物理地址0x00090200開始的RAM中。
至此,接力棒就由引導裝入程序傳到了setup( )函數的手中。
setup( )初始化計算機的硬件設備,並為內核程序的執行建立環境,然後跳轉到startup_32( )匯編語言函數繼續執行
有兩個所謂的startup32()函數,一個位於arch/i386/boot/compressed/head.S;另一個則是位於 arch/i386/kernel/head.S的startup32()函數。前者主要用於解壓縮內核映像,setup()函數結束後,就直接跳到這個函數中;後者是內核真正的入口,此時內核已經被解壓了,從這裡開始,內核才開始了真正的初始化。setup()函數結束後,就會跳到0x1000(zImage)或者0x100000(bzImage),也就是startup32()函數的起點。
該函數會執行以下操作:
1.初始化寄存器和臨時堆棧
2.用0填充_edata和_end符號標識的內核未初始化數據區
3.調用decompress_kernel()函數來解壓縮內核映像.首先顯示"Uncompressing Linux..."信息完成內核映像的解壓之後,顯示"OK,booting the kernel"信息.不管是zImage還是bzImage,最終解壓好的內核都被放在0x100000開始的位置
4.跳轉到0x100000處,開始真正的內核旅程
具體解壓縮細節在此不再贅述,這也不是重點。我們所要知道的就是,前面的程序為真正的內核初始化提供必要的數據和建立了必要的運行環境,而接下來的startup32()函數和startkernel()函數,才是我們真正需要仔細了解的目標。
第二個startup_32( )函數為第一個Linux進程(進程0)建立執行環境,執行相關操作後最終跳轉到start_kernel( )函數繼續執行。start_kernel( )函數完成Linux內核的初始化工作,執行到最後,就會在控制台上出現熟悉的登錄提示符(如果在啟動時所啟動的是X Window系統,那麼登錄提示符就會出現在一個圖形窗口中,至於什麼是X Window,上網去查!)
至此,啟動階段結束!