計算機怎麼引導核心啟動的?
本文翻譯並修改自:
http://duartes.org/gustavo/blog/微信公眾號:技術原理君
之前有一篇文章介紹了《Intel計算機的主機板與記憶體對映》,從而為本文設定了一個系統引導階段的場景。引導(Booting)是一個複雜的,充滿技巧的,涉及多個階段,又十分有趣的過程。下圖列出了此過程的概要:
引導過程概要
當你按下計算機的電源鍵後(現在別按!),機器就開始運轉了。一旦主機板上電,它就會初始化自身的韌體(firmware)——晶片組和其他零零碎碎的東西 ——並嘗試啟動CPU。如果此時出了什麼問題(比如CPU壞了或根本沒裝),那麼很可能出現的情況是電腦沒有任何動靜,除了風扇在轉。一些主機板會在CPU 故障或缺失時發出鳴音提示,但以我的經驗,此時大多數機器都會處於僵死狀態。一些USB或其他裝置也可能導致機器啟動時僵死。對於那些以前工作正常,突然 出現這種症狀的電腦,一個可能的解決辦法是拔除所有不必要的裝置。你也可以一次只斷開一個裝置,從而發現哪個是罪魁禍首。
如果一切正常,CPU就開始執行了。在一個多處理器或多核處理器的系統中,會有一個CPU被動態的指派為引導處理器(bootstrap processor簡寫BSP),用於執行全部的BIOS和核心初始化程式碼。其餘的處理器,此時被稱為應用處理器(application processor簡寫AP),一直保持停機狀態直到核心明確啟用他們為止。雖然Intel CPU經歷了很多年的發展,但他們一直保持著完全的向後相容性,所以現代的CPU可以表現得跟原先1978年的Intel 8086完全一樣。其實,當CPU上電後,它就是這麼做的。在這個基本的上電過程中,處理器工作於真實模式,分頁功能是無效的。此時的系統環境,就像古老的MS-DOS一樣,只有1MB記憶體可以定址,任何程式碼都可以讀寫任何地址的記憶體,這裡沒有保護或特權級的概念。
CPU上電後,大部分暫存器的都具有定義良好的初始值,包括指令指標暫存器(EIP),它記錄了下一條即將被CPU執行的指令所在的記憶體地址。儘管此時的Intel CPU還只能定址1MB的記憶體,但憑藉一個奇特的技巧,一個隱藏的基地址(其實就是個偏移量)會與EIP相加,其結果指向第一條將被執行的指令所處的地址0xFFFFFFF0(長16位元組,在4GB記憶體空間的尾部,遠高於1MB)。這個特殊的地址叫做復位向量(reset vector),而且是現代Intel CPU的標準。
主機板保證在復位向量處的指令是一個跳轉,而且是跳轉到BIOS執行入口點所在的記憶體對映地址。這個跳轉會順帶清除那個隱藏的、上電時的基地址。感謝晶片組提供的記憶體對映功能,此時的記憶體地址存放著CPU初始化所需的真正內容。這些內容全部是從包含有BIOS的快閃記憶體對映過來的,而此時的RAM模組還只有隨機的垃圾資料。下面的圖例列出了相關的記憶體區域:
引導時的重要記憶體區域
隨後,CPU開始執行BIOS的程式碼,初始化機器中的一些硬體。之後BIOS開始執行上電自檢過程(POST),檢測計算機中的各種元件。如果找不到一個可用的顯示卡,POST就會失敗,導致BIOS進入停機狀態併發出鳴音提示(因為此時無法在螢幕上輸出提示資訊)。如果顯示卡正常,那麼電腦看起來就真的運轉起來了:顯示一個製造商定製的商標,開始記憶體自檢,天使們大聲的吹響號角。另有一些POST失敗的情況,比如缺少鍵盤,會導致停機,螢幕上顯示出錯資訊。其實POST即是檢測又是初始化,還要列舉出所有PCI裝置的資源——中斷,記憶體範圍,I/O埠。現代的BIOS會遵循高階配置與電源介面(ACPI)協議,建立一些用於描述裝置的資料表,這些表格將來會被作業系統核心用到。
POST完畢後,BIOS就準備引導作業系統了,它必須存在於某個地方:硬碟,光碟機,軟盤等。BIOS搜尋引導裝置的實際順序是使用者可定製的。如果找不到合適的引導裝置,BIOS會顯示出錯資訊並停機,比如"Non-System Disk or Disk Error"沒有系統盤或驅動器故障。一個壞了的硬碟可能導致此症狀。幸運的是,在這篇文章中,BIOS成功的找到了一個可以正常引導的驅動器。
現在,BIOS會讀取硬碟的第一個扇區(0扇區),內含512個位元組。這些資料叫做主開機記錄(Master Boot Record簡稱MBR)。一般說來,它包含兩個極其重要的部分:一個是位於MBR開頭的作業系統相關的載入程式,另一個是緊跟其後的磁碟分割槽表。BIOS 絲毫不關心這些事情:它只是簡單的載入MBR的內容到記憶體地址0x7C00處,並跳轉到此處開始執行,不管MBR裡的程式碼是什麼。
主開機記錄
這段在MBR內的特殊程式碼可能是Windows 引導裝載程式,Linux 引導裝載程式(比如LILO或GRUB),甚至可能是病毒。與此不同,分割槽表則是標準化的:它是一個64位元組的區塊,包含4個16位元組的記錄項,描述磁碟是如何被分割的(所以你可以在一個磁碟上安裝多個作業系統或擁有多個獨立的卷)。傳統上,Microsoft的MBR程式碼會檢視分割槽表,找到一個(唯一的)標記為活動(active)的分割槽,載入那個分割槽的引導扇區(boot sector),並執行其中的程式碼。引導扇區是一個分割槽的第一個扇區,而不是整個磁碟的第一個扇區。如果此時出了什麼問題,你可能會收到如下錯誤資訊:"Invalid Partition Table"無效分割槽表或"Missing Operating System"作業系統缺失。這條資訊不是來自BIOS的,而是由從磁碟載入的MBR程式所給出的。因此這些資訊依賴於MBR的內容。
隨著時間的推移,引導裝載過程已經發展得越來越複雜,越來越靈活。Linux的引導裝載程式Lilo和GRUB可以處理很多種類的作業系統,檔案系統,以及引導配置資訊。他們的MBR程式碼不再需要效仿上述"從活動分割槽來引導"的方法。但是從功能上講,這個過程大致如下:
- 1、 MBR本身包含有第一階段的引導裝載程式。GRUB稱之為階段一。
- 2、 由於MBR很小,其中的程式碼僅僅用於從磁碟載入另一個含有額外的引導程式碼的扇區。此扇區可能是某個分割槽的引導扇區,但也可能是一個被硬編碼到MBR中的扇區位置。
- 3、 MBR配合第2步所載入的程式碼去讀取一個檔案,其中包含了下一階段所需的載入程式。這在GRUB中是"階段二"載入程式,在Windows Server中是C:/NTLDR。如果第2步失敗了,在Windows中你會收到錯誤資訊,比如"NTLDR is missing"NTLDR缺失。階段二的程式碼進一步讀取一個引導配置檔案(比如在GRUB中是grub.conf,在Windows中是boot.ini)。之後要麼給使用者顯示一些引導選項,要麼直接去引導系統。
- 4、 此時,引導裝載程式需要啟動作業系統核心。它必須擁有足夠的關於檔案系統的資訊,以便從引導分割槽中讀取核心。在Linux中,這意味著讀取一個名字類似"vmlinuz-2.6.22-14-server"的含有核心映象的檔案,將之載入到記憶體並跳轉去執行核心引導程式碼。在Windows Server 2003中,一部份核心啟動程式碼是與核心映象本身分離的,事實上是嵌入到了NTLDR當中。在完成一些初始化工作以後,NTDLR從"c:/Windows/System32/ntoskrnl.exe"檔案載入核心映象,就像GRUB所做的那樣,跳轉到核心的入口點去執行。
這裡還有一個複雜的地方值得一提(這也是我說引導富於技巧性的原因)。當前Linux核心的映象就算被壓縮了,在真實模式下,也沒法塞進640KB的可用RAM裡。我的vanilla Ubuntu核心壓縮後有1.7MB。然而,引導裝載程式必須執行於真實模式,以便呼叫BIOS程式碼去讀取磁碟,所以此時核心肯定是沒法用的。解決之道是使用一種倍受推崇的"虛模式"。它並非一個真正的處理器執行模式(希望Intel的工程師允許我以此作樂),而是一個特殊技巧。程式不斷的在真實模式和保護模式之間切換,以便訪問高於1MB的記憶體同時還能使用BIOS。如果你閱讀了GRUB的原始碼,你就會發現這些切換到處都是(看看stage2/目錄下的程式,對real_to_prot 和 prot_to_real函式的呼叫)。在這個棘手的過程結束時,裝載程式終於千方百計的把整個核心都塞到記憶體裡了,但在這後,處理器仍保持在真實模式執行。
至此,我們來到了從"引導裝載"跳轉到"早期的核心初始化"的時刻,就像第一張圖中所指示的那樣。在系統做完熱身運動後,核心會展開並讓系統開始運轉。