日本設(shè)計(jì)網(wǎng)站推薦商丘關(guān)鍵詞優(yōu)化推廣
注:本文為“計(jì)算機(jī)啟動(dòng)、 Linux 啟動(dòng)”相關(guān)文章合輯。
替換引文部分不清晰的圖。
探索計(jì)算機(jī)的啟動(dòng)過(guò)程
Aleksandr Goncharov
2023/04/21
很多人對(duì)計(jì)算機(jī)的啟動(dòng)方式很感興趣。只要設(shè)備開(kāi)啟,這就是魔法開(kāi)始和持續(xù)的地方。在本文中,我們將概述 啟動(dòng) 過(guò)程,包括其各個(gè)階段、涉及的關(guān)鍵組件以及過(guò)程中面臨的挑戰(zhàn)。
雖然我們主要關(guān)注x86 架構(gòu)(使用最廣泛的架構(gòu)),但其他架構(gòu)在其引導(dǎo)過(guò)程中會(huì)有許多相似之處。我希望這篇文章對(duì)于任何希望加深他們?cè)谠擃I(lǐng)域的知識(shí)的人來(lái)說(shuō)都是寶貴的資源。開(kāi)始了!
開(kāi)機(jī) ROM
位于主板上并存儲(chǔ)負(fù)責(zé)啟動(dòng)計(jì)算機(jī)的固件代碼的 集成電路(芯片)稱為 BOOT ROM。這個(gè)名字是不規(guī)范的,所以其他開(kāi)發(fā)者常稱它為FLASH ROM、BIOS FLASH、BOOT FLASH、SPI FLASH等(這些名字是因?yàn)榧夹g(shù)、接口和用途名稱而給它們起的)。不用擔(dān)心,這些術(shù)語(yǔ)可以互換。BOOT ROM中的固件代碼在計(jì)算機(jī)開(kāi)機(jī)時(shí)首先執(zhí)行。它執(zhí)行基本測(cè)試、初始化硬件,然后將操作系統(tǒng)加載程序從可啟動(dòng)設(shè)備(例如硬盤(pán)驅(qū)動(dòng)器或 USB 驅(qū)動(dòng)器)加載到內(nèi)存中。該芯片由非易失性存儲(chǔ)器 (NVM) 制成。
非易失性存儲(chǔ)器
非易失性存儲(chǔ)器是一種計(jì)算機(jī)存儲(chǔ)器,即使在電源關(guān)閉時(shí)也能保留其內(nèi)容。它使這種類型的內(nèi)存非常適合存儲(chǔ)即使在計(jì)算機(jī)斷電時(shí)也需要保留的重要數(shù)據(jù)。此外,討論將僅集中在保存固件代碼的存儲(chǔ)器上。我們不會(huì)談?wù)撚脖P(pán)驅(qū)動(dòng)器 (HDD)、固態(tài)驅(qū)動(dòng)器 (SSD)、軟盤(pán)等存儲(chǔ)。
基本上,我們可以將這種類型的內(nèi)存分為以下幾類。
一次性可編程
-
Masked ROM:內(nèi)容在制造時(shí)確定,之后無(wú)法更改。
-
可編程 ROM (PROM):與屏蔽 ROM 不同,這種類型的存儲(chǔ)器可以在制造后進(jìn)行編程。但還是只有一次。
現(xiàn)場(chǎng)可編程
-
可擦可編程 ROM (EPROM):可以多次編程,但可以使用 紫外線 擦除和重新編程其內(nèi)容。
-
電可擦除可編程 (EEPROM):可以使用 電信號(hào) 多次重新編程。
-
NOR 閃存:在結(jié)構(gòu)上按塊排列,其中數(shù)據(jù)在 塊級(jí)別 擦除,并且可以在 字節(jié)級(jí)別 讀取或?qū)懭搿?NOR 存儲(chǔ)器可使用字節(jié)并行、I2C 或 SPI 等標(biāo)準(zhǔn)接口直接訪問(wèn)。
在行業(yè)中,有一種慣例將術(shù)語(yǔ) EEPROM 保留為字節(jié)方式可擦除存儲(chǔ)器,而不是塊方式可擦除閃存。
可編程存儲(chǔ)器有一條規(guī)則 ——先擦后寫(xiě)。在這樣的內(nèi)存中,寫(xiě)入新數(shù)據(jù)更加復(fù)雜,因?yàn)閿?shù)據(jù)以電荷的形式存儲(chǔ)在浮柵上(原因主要在于存儲(chǔ)單元的物理特性)。柵極上的電荷量決定了單元存儲(chǔ) “0” 還是 “1”。
擦除閃存芯片時(shí),會(huì)將存儲(chǔ)在其上的所有數(shù)據(jù)位設(shè)置為已知(默認(rèn))狀態(tài),通常為邏輯 “1”。這允許您從一個(gè)干凈的平臺(tái)開(kāi)始,可以這么說(shuō),并將新數(shù)據(jù)編程到芯片上,而不會(huì)在其中存儲(chǔ)任何舊數(shù)據(jù)的殘余。當(dāng)新數(shù)據(jù)寫(xiě)入芯片時(shí),各個(gè)位的狀態(tài)從 “1” 變?yōu)?“0” 以表示新數(shù)據(jù)。
如果不先擦除芯片而直接向芯片寫(xiě)入新數(shù)據(jù),新數(shù)據(jù)將與舊數(shù)據(jù)結(jié)合,導(dǎo)致不可預(yù)知的結(jié)果。例如,考慮一個(gè)具有 8 位內(nèi)存存儲(chǔ)值 “0110 0010” 的閃存芯片。如果您將新數(shù)據(jù) “1100 1001” 寫(xiě)入芯片而不先將其擦除,則芯片的最終狀態(tài)將是 “0100 0000”,這可能不是您想要的。
主要的混淆與 ROM 這個(gè)詞有關(guān),它代表 只讀存儲(chǔ)器 。術(shù)語(yǔ) “只讀存儲(chǔ)器” 歷來(lái)用于指代永久且用戶無(wú)法更改的存儲(chǔ)器。然而,隨著技術(shù)的進(jìn)步,ROM 的定義也發(fā)生了變化,現(xiàn)在它通常被用來(lái)指代在工廠預(yù)編程且最終用戶無(wú)法 輕易 更改的存儲(chǔ)器。但如果用戶擁有所需的技能和專用設(shè)備(例如編程器),則可以對(duì)芯片進(jìn)行重新編程。盡管定義已更改,但 ROM 這個(gè)名稱仍然保留,作為對(duì)存儲(chǔ)器原始用途的歷史參考。
通過(guò)應(yīng)用寫(xiě)保護(hù),某些類型的可重新編程 ROM 可能會(huì)暫時(shí)變?yōu)橹蛔x存儲(chǔ)器。
這些不是所有現(xiàn)有類型的非易失性存儲(chǔ)器,而是您可能偶然聽(tīng)說(shuō)的大多數(shù)流行類型。如今,在大多數(shù)主板上,這些芯片都是使用 NOR Flash 技術(shù)制成的。
就地執(zhí)行 (XIP)
Execute in Place (XIP) 是一種允許處理器直接從閃存中執(zhí)行代碼而無(wú)需先將其復(fù)制到易失性存儲(chǔ)器(如 RAM )中的方法。這是通過(guò)將閃存映射到處理器的地址空間來(lái)實(shí)現(xiàn)的,因此可以直接從閃存執(zhí)行代碼。因此,系統(tǒng)能夠盡快開(kāi)始執(zhí)行代碼,而不必先等待 RAM 初始化。
等等…CPU 可以通過(guò) SPI/Parallel/etc 協(xié)議與 BOOT ROM 通信嗎?當(dāng)然不是,它只是從系統(tǒng)內(nèi)存中獲取指令,對(duì)這個(gè)內(nèi)存區(qū)域的請(qǐng)求被重定向到 Intel Direct Media Interface (DMI ) 或 AMD Infinity Fabric (IF) / Unified Media Interface (UMI)(前身)。它是主板上 CPU 和芯片組之間的鏈接。此時(shí),通過(guò)位于芯片組中的解碼器對(duì)地址進(jìn)行解碼,并將來(lái)自芯片的數(shù)據(jù)返回給處理器。
當(dāng)芯片由支持隨機(jī)訪問(wèn)讀取但不支持隨機(jī)訪問(wèn)寫(xiě)入的 NOR 閃存制成時(shí),問(wèn)題來(lái)了。只要可寫(xiě)內(nèi)存不可用,所有計(jì)算都必須在處理器寄存器內(nèi)執(zhí)行。此時(shí),代碼只能用匯編語(yǔ)言編寫(xiě),并傾向于為高級(jí)語(yǔ)言(通常為 C 語(yǔ)言)搭建環(huán)境。原因是內(nèi)存初始化變得如此復(fù)雜,純匯編很難寫(xiě)。由于此類語(yǔ)言至少需要堆和棧,因此我們需要可寫(xiě)內(nèi)存。一些處理器在芯片本身中嵌入了 SRAM,但更現(xiàn)代的方法是使用板載高速緩存作為 RAM (CAR)。
緩存作為 Ram (CAR)
CPU 高速緩存是一種高速存儲(chǔ)器,用于存儲(chǔ)來(lái)自主存儲(chǔ)器的常用數(shù)據(jù)和指令的副本。高速緩存位于更靠近處理器的位置,并分為多個(gè)級(jí)別(L1、L2、L3 ……),每個(gè)級(jí)別都比前一個(gè)更大、更慢。
如果數(shù)據(jù)在緩存中,CPU 可以從緩存中檢索請(qǐng)求的數(shù)據(jù)(稱為緩存命中)。當(dāng) CPU 緩存無(wú)法找到所需的數(shù)據(jù)時(shí),就會(huì)導(dǎo)致緩存未命中。發(fā)生這種情況的原因可能是數(shù)據(jù)從未存儲(chǔ)在緩存中,或者因?yàn)閿?shù)據(jù)以前存儲(chǔ)過(guò)但已從緩存中逐出。無(wú)論如何,處理器必須一路走到主存才能訪問(wèn)數(shù)據(jù)并將其復(fù)制到緩存中。
緩存逐出是從緩存中刪除數(shù)據(jù)以為新數(shù)據(jù)釋放空間的過(guò)程。數(shù)據(jù)的逐出可以由緩存系統(tǒng)發(fā)起(通常當(dāng)緩存已滿并且需要存儲(chǔ)新數(shù)據(jù)時(shí),或者當(dāng)數(shù)據(jù)的生存時(shí)間策略已過(guò)期時(shí))或通過(guò)顯式請(qǐng)求發(fā)起。
但是,如果我們想將 CPU Cache 用作 RAM,我們需要將緩存設(shè)置為在Non-Eviction Mode下運(yùn)行,也稱為No-Fill Mode。此技術(shù)可防止因 高速緩存未命中而被逐出。相反,緩存被視為常規(guī) SRAM,所有訪問(wèn)(讀 / 寫(xiě))都將訪問(wèn)緩存,而不會(huì)訪問(wèn)主內(nèi)存。可以使用供應(yīng)商特定的 CPU 指令激活該模式。
布局和內(nèi)存映射
實(shí)際上,BOOT ROM 包含多種類型的固件。一旦一堆固件存儲(chǔ)在 BOOT ROM 中,就需要以某種方式對(duì)其進(jìn)行組織以區(qū)分它們。讓我們看看它是如何完成的。
非描述符模式
最初,芯片組將整個(gè) BOOT ROM 內(nèi)容直接映射到內(nèi)存(從 4GB 到 4GB - 16MB)。通常,如果 BOOT ROM 小于 16 MB,則內(nèi)容會(huì)被重復(fù)映射。 CPU 和固件可以不受任何限制地讀 / 寫(xiě)閃存。
新芯片組不再支持非描述符模式。
Intel 閃存描述符 / 描述符模式
最終,在 ICH8 中,Intel 引入了一種特殊的 BOOT ROM 布局。閃存分為以下區(qū)域:
- 閃存描述符 (FD) - 此數(shù)據(jù)結(jié)構(gòu)必須位于設(shè)備的開(kāi)頭,偏移量為
0x10
。它由十一部分組成,如下圖所示:
Descriptor MAP具有指向其他區(qū)域的指針以及每個(gè)區(qū)域的大小。
組件部分包含有關(guān)系統(tǒng)中閃存的信息(組件數(shù)量、每個(gè)組件的密度、無(wú)效指令等)。
Masters 部分定義了區(qū)域的讀 / 寫(xiě)權(quán)限。就讀 / 寫(xiě)而言,權(quán)限必須設(shè)置為只讀,該區(qū)域存儲(chǔ)的信息只能在制造過(guò)程中寫(xiě)入。
- BIOS - 只有這個(gè)區(qū)域被映射到內(nèi)存中。
- Intel Converged Security and Management Engine (CSME / ME) - 支持不同英特爾技術(shù)和 ME 的固件。
- 千兆以太網(wǎng) (GbE) - 只能由千兆以太網(wǎng)控制器直接訪問(wèn)。
- 平臺(tái)數(shù)據(jù)
- 嵌入式控制器 (EC)
閃存描述符和Intel ME是唯一需要的區(qū)域。
英特爾固件接口表 (FIT)
FIT 是 BIOS 區(qū)域內(nèi)的數(shù)據(jù)結(jié)構(gòu),包含描述平臺(tái)配置的各種條目。表中的每個(gè)條目大小為 16 個(gè)字節(jié)。第一個(gè)稱為 FIT 標(biāo)頭,另一個(gè)稱為 FIT 條目。它位于物理地址 0xFFFFFFC0
(4GB - 0x40) 的 FIT 指針處。
在執(zhí)行來(lái)自 * 復(fù)位向量的 * 第一條 CPU 指令之前,必須處理這些組件。條目包括 CPU 微代碼更新、啟動(dòng) ACM、平臺(tái)引導(dǎo) / TPM/BIOS/TXT 策略和其他內(nèi)容。但至少 FIT 應(yīng)該包括 FIT 標(biāo)頭和微碼更新條目。因此,FIT 的常見(jiàn)用法是在執(zhí)行 * 重置向量 * 之前更新微碼。
這是內(nèi)存映射的樣子:
AMD 嵌入式固件結(jié)構(gòu)
不幸的是,信息少得多,我找不到任何泄露的 AMD 芯片組文檔,其中包含有關(guān)其布局的詳細(xì)信息。所以我不能告訴你比 coreboot 文檔 更好的了。它是根據(jù)僅在 NDA 下可用的 AMD 文檔編寫(xiě)的。
實(shí)際上,只要知道閃存描述符的 AMD 模擬是嵌入式固件結(jié)構(gòu)就足夠了,它包含指向 PSP 目錄表、BIOS 目錄表和其他固件的指針。
硅初始化
如果你想看看現(xiàn)代內(nèi)存和 CPU 是如何初始化的,那么我不得不打擾你。英特爾和 AMD 并不急于向社區(qū)發(fā)布硅初始化代碼。就此類信息不公開(kāi)而言,他們提供必要的硅初始化代碼的二進(jìn)制分發(fā)。這被認(rèn)為是固件開(kāi)發(fā)人員的庫(kù),包含用于初始化內(nèi)存控制器、芯片組、CPU 和系統(tǒng)其他不同部分的二進(jìn)制代碼。
英特爾固件支持包 (FSP)
該二進(jìn)制文件可以分為 4 個(gè)部分:
- FSP-T:設(shè)置可以執(zhí)行 C 代碼的早期執(zhí)行環(huán)境(“臨時(shí) RAM”)。實(shí)際上,這個(gè)二進(jìn)制文件設(shè)置了 CAR,但也做了一些早期的硬件初始化,比如設(shè)置 PCIe 內(nèi)存映射配置空間。
- FSP-M:初始化永久存儲(chǔ)器(如 DRAM)。
- FSP-S:完成芯片初始化,包括 CPU 和 IO 控制器初始化。
- FSP-O:提供 OEM 設(shè)備初始化的可選組件。
這是英特爾發(fā)布的 英特爾 FSP 二進(jìn)制文件存儲(chǔ)庫(kù),您可以在他們的 GitHub 上找到。 FSP Specification v2.1 可從 Intel 網(wǎng)站獲取。
AMD 通用封裝軟件架構(gòu) (AGESA)
早于 Family 17h 的產(chǎn)品的 AGESA 稱為 v5 或 Arch2008。當(dāng)時(shí),AGESA 是開(kāi)源的,代碼在 coreboot 存儲(chǔ)庫(kù) 中可用(4.18 版后已棄用)。 Arch2008 的規(guī)范 可以在 AMD 網(wǎng)站上找到。
隨著 Family 17h (Zen 微架構(gòu))產(chǎn)品的推出,AMD 并未發(fā)布 AGESA 源代碼,僅發(fā)布了預(yù)構(gòu)建的二進(jìn)制解決方案。這樣的繼任者稱為 AGESA v9,支持 Family 17h 及更高版本。
開(kāi)放 SIL
沒(méi)有可用的詳細(xì)信息,只有 新聞。
自治子系統(tǒng)
現(xiàn)代 x86 啟動(dòng)過(guò)程的一個(gè)組成部分,沒(méi)有它 x86 內(nèi)核將永遠(yuǎn)不會(huì)被激活。因此不可能完全禁用它們。這些技術(shù)負(fù)責(zé)硬件初始化、驗(yàn)證系統(tǒng)完整性、電源管理和 CPU 啟動(dòng)。這些子系統(tǒng)的固件在主處理器開(kāi)始執(zhí)行它自己的固件之前被加載和執(zhí)行。此類系統(tǒng)上的代碼獨(dú)立于平臺(tái)的 CPU 內(nèi)核運(yùn)行。
只要許多硬件公司通過(guò)隱匿性來(lái)納入安全原則,這些子系統(tǒng)的源代碼和文檔就都無(wú)法獲得。幸運(yùn)的是,我們知道它如何影響引導(dǎo)過(guò)程 - 請(qǐng)參閱 [硬件電源序列](https://hackernoon.com/lang/zh/ 固件的隱藏世界探索您的計(jì)算機(jī)啟動(dòng)過(guò)程 #power_sequence)。
我們不會(huì)詳細(xì)介紹,因?yàn)榛ヂ?lián)網(wǎng)上已經(jīng)有來(lái)自世界各地的研究人員的大量文章。但我只會(huì)給你一個(gè)簡(jiǎn)短的描述。
英特爾管理引擎 (ME)
Intel ME 是一個(gè)獨(dú)立的 i486/80486 微處理器,自 2008 年起集成到 Intel 芯片組 (PCH) 中。它有自己的 RAM、內(nèi)置 ROM、連接芯片組內(nèi)所有總線的總線橋(因此,它可以訪問(wèn)網(wǎng)絡(luò)甚至 CPU 上的主 RAM),等等。運(yùn)行基于 MINIX 的自定義操作系統(tǒng)。
AMD 平臺(tái)安全處理器 (PSP)
AMD PSP 是一個(gè)依賴于 Trustzone 擴(kuò)展的 ARM 內(nèi)核,作為協(xié)處理器被插入到 CPU die 中。自 2013 年以來(lái),該芯片已集成到大多數(shù) AMD 平臺(tái)中。運(yùn)行未記錄的專有操作系統(tǒng)。
硬件電源序列
此過(guò)程也稱為上電順序或電源排序,以平臺(tái)所需的特定順序提供多個(gè)派生電壓電平和 / 或 * 電源 * 軌。更簡(jiǎn)單地說(shuō),它按特定順序?yàn)槎鄠€(gè)平臺(tái)組件供電。該過(guò)程因系統(tǒng)或平臺(tái)設(shè)計(jì)而異,但通常標(biāo)準(zhǔn) PC 包括以下步驟:
-
您按下電源按鈕。但是等等…… 這個(gè)按鈕位于計(jì)算機(jī)機(jī)箱上,它不是計(jì)算機(jī)的必要部件。通常,電源按鈕是一根電纜。我們?cè)谝粋?cè)有一個(gè)按鈕,在另一側(cè)的主板上的兩個(gè)金屬插腳上有一個(gè)開(kāi)關(guān)。當(dāng)我們按下按鈕時(shí),這些插腳連接在一起,因此電流可以通過(guò)它們。如果您感興趣,請(qǐng)觀看下面的視頻,了解如何在沒(méi)有電源按鈕的情況下打開(kāi)計(jì)算機(jī)。
https://www.youtube.com/embed/G4FOBL1c3pA
-
主板向電源單元 (PSU) 發(fā)送信號(hào)。
-
電源接收信號(hào),提供適量的電量,并將信號(hào)傳回主板。
-
一旦主板收到電源良好信號(hào),它就會(huì)為核心、時(shí)鐘、芯片組、內(nèi)存、不同控制器等平臺(tái)組件加電。
-
各種子系統(tǒng),包括自主子系統(tǒng)(上面討論過(guò)),可以在主處理器之前啟動(dòng)。
-
基于 AMD 的系統(tǒng)(適用于 Family 17h 及更高版本)
-
PSP 執(zhí)行片上BOOT ROM。
-
PSP 在片外 BOOT ROM 中找到嵌入式固件表并執(zhí)行 PSP 固件。
-
PSP 解析 PSP 目錄表以查找 ABL 階段并執(zhí)行它們。
-
ABL 階段初始化主內(nèi)存,在 BOOT ROM 中找到 BIOS 映像,并將其加載到 DRAM 中(如果映像被壓縮,則解壓縮)。
該平臺(tái)沒(méi)有理由使用 CAR,因?yàn)?DRAM 已經(jīng)可用并且 PSP 將固件映像加載到其中。
-
基于英特爾的系統(tǒng)
-
芯片組 (ICH/PCH) 在 BOOT ROM 中找到Intel 閃存描述符。
-
芯片組將 CSME 固件復(fù)制到內(nèi)部存儲(chǔ)器中,Intel ME 可以訪問(wèn)它,最后一個(gè)開(kāi)始執(zhí)行它。
-
芯片組將 BIOS 區(qū)域映射到內(nèi)存。
-
位于固件接口表中的微代碼更新被加載到 CPU 中。它們必須在每次系統(tǒng)啟動(dòng)時(shí)應(yīng)用。
-
(可選)如果找到經(jīng)過(guò)身份驗(yàn)證的代碼模塊 (ACM),則執(zhí)行該條目。
-
一直以來(lái),CPU 復(fù)位信號(hào)都有效,以防止 CPU 在系統(tǒng)其他部分準(zhǔn)備就緒之前啟動(dòng)。當(dāng)平臺(tái)準(zhǔn)備就緒時(shí),CPU 復(fù)位線被取消斷言。在多處理器或多核系統(tǒng)中,一個(gè) CPU 被動(dòng)態(tài)選擇為運(yùn)行所有固件初始化代碼的引導(dǎo)處理器 (BSP)。其余的處理器,此時(shí)稱為應(yīng)用程序處理器 (AP),將保持暫停狀態(tài),直到稍后它們被固件 / 內(nèi)核明確激活。
-
CPU 首次上電后,以 實(shí)模式 運(yùn)行。大多數(shù)寄存器都有明確定義的值,包括指令指針 (IP)、代碼段 (CS) 和描述符緩存,它是處理器中每個(gè)段描述符的副本,以允許快速訪問(wèn)段內(nèi)存。
段描述符.是全局描述符表(GDT).中的一個(gè)條目,包含基地址、段限制和訪問(wèn)信息(這部分被忽略,因?yàn)閷?shí)模式?jīng)]有像保護(hù)模式那樣的訪問(wèn)控制)。不是為每次內(nèi)存訪問(wèn)訪問(wèn) GDT(位于內(nèi)存中),而是將信息存儲(chǔ)在描述符緩存中。
但是,GDT 不涉及實(shí)模式,因此處理器在內(nèi)部生成條目。用于訪問(wèn)段描述符的 CS 選擇器寄存器加載了 0xF000
。 CS 基地址初始化為 0xFFFF_0000
。 IP 初始化為 0xFFF0
。
因此,處理器開(kāi)始從位于物理地址 0xFFFF_FFF0
( 0xFFFF_0000
+ 0x0000_FFF0
) 的內(nèi)存中獲取指令。在該地址執(zhí)行的第一條指令稱為 復(fù)位向量。
注意:此技巧可讓您訪問(wèn)高地址空間,但是,您無(wú)法訪問(wèn) 0xFFFF_0000
地址以下的代碼。 CS 基地址保持此初始值,直到 CS 選擇器寄存器被固件加載??梢酝ㄟ^(guò)執(zhí)行遠(yuǎn)跳來(lái)完成。
此時(shí),最好的決定是切換到具有 4 GB 尋址能力的保護(hù)模式。如果固件不這樣做,那么為了使實(shí)模式工作,芯片組必須能夠?qū)⒌陀?1 MB 的內(nèi)存范圍別名化為略低于 4 GB 的等效范圍。某些芯片組沒(méi)有這種別名,可能需要在執(zhí)行第一次跳遠(yuǎn)之前切換到另一種操作模式。
-
該地址位于一段非易失性存儲(chǔ)器中,因此 CPU 使用就地執(zhí)行 (XIP) 方法。盡管如果它是基于 AMD 的系統(tǒng),您可能正在從主內(nèi)存中讀取。
-
CPU 執(zhí)行固件代碼。
建議觀看下面關(guān)于開(kāi)機(jī)順序的視頻,該視頻以華碩 P9X79 主板為例說(shuō)明了該過(guò)程。盡管它是俄語(yǔ),但如果您打開(kāi)自動(dòng)生成的英文字幕,您將能夠理解所有內(nèi)容。
- Процесс запуска материнской платы. Power on Sequence - YouTube
motherboard startup process. Power on Sequence
https://www.youtube.com/embed/lAH7eSjR1d4
從 BIOS/UEFI 到 OS
Aleksandr Goncharov
2024/08/22
按下計(jì)算機(jī)電源按鈕的那一刻會(huì)發(fā)生什么?
在屏幕亮起之前,在那短暫的停頓背后,發(fā)生了一系列復(fù)雜的過(guò)程。
本文將深入迷人的固件世界,探索不同組件在啟動(dòng)過(guò)程中如何交互。
通過(guò)了解這些聯(lián)系,您將更清楚地了解使您的系統(tǒng)充滿活力的基礎(chǔ)元素。我們的主要重點(diǎn)是英特爾 x86 架構(gòu),但許多原則也適用于其他架構(gòu)。
現(xiàn)在,讓我們揭開(kāi)固件背后的秘密。
定義
-固件:一種嵌入在硬件中的專用軟件,提供低級(jí)控制并使硬件能夠正常運(yùn)行并與其他系統(tǒng)組件交互。
-
基本輸入 / 輸出系統(tǒng) (BIOS):一種傳統(tǒng)固件(最初為 IBM PC 創(chuàng)建),負(fù)責(zé)平臺(tái)開(kāi)機(jī)后的硬件初始化。如今,它通常被含糊地稱為整套固件。
-
引導(dǎo)加載程序:負(fù)責(zé)啟動(dòng)計(jì)算機(jī)的固件的統(tǒng)稱。它被用作BIOS的現(xiàn)代概念,通常提供帶有引導(dǎo)代碼的 框架 來(lái)初始化處理器和芯片組,以及第三方(例如主板開(kāi)發(fā)人員)執(zhí)行平臺(tái)特定初始化的接口。
-
Payload:在 bootloader 退出時(shí)執(zhí)行的軟件??梢允堑诙A段的 bootloader、操作系統(tǒng)、BIOS/UEFI 應(yīng)用程序等。它通常根據(jù)固件設(shè)計(jì)負(fù)責(zé)引導(dǎo)流程。
-
BIOS和bootloader這兩個(gè)術(shù)語(yǔ)的使用可能會(huì)令人困惑,因?yàn)樗鼈兊暮x取決于上下文。然而,當(dāng)有人提到固件、BIOS或bootloader時(shí),他們通常指的是在操作系統(tǒng)和硬件之間運(yùn)行的整套固件。
-
EFI與UEFI: 可擴(kuò)展固件接口 (EFI) 是英特爾開(kāi)發(fā)的原始規(guī)范。統(tǒng)一可擴(kuò)展固件接口 (UEFI)是EFI的后繼者,由 UEFI 論壇 創(chuàng)建,旨在標(biāo)準(zhǔn)化和擴(kuò)展原始規(guī)范。在大多數(shù)情況下,EFI和UEFI可互換使用。
整體固件架構(gòu)
為了理解固件組件如何交互,我們將探索整個(gè)架構(gòu)及其所有連接部分。下圖所示的執(zhí)行流程從 復(fù)位向量 開(kāi)始,它是第一階段引導(dǎo)加載程序的一部分。從那里開(kāi)始,它經(jīng)歷了各種固件階段:
固件或 BIOS 通??梢苑譃閮蓚€(gè)主要部分,它們之間通常具有最小的接口:
1.硬件初始化:負(fù)責(zé)初始化系統(tǒng)的硬件組件。
2.操作系統(tǒng)和用戶界面:向操作系統(tǒng)和用戶提供必要的接口。
平臺(tái)固件的設(shè)計(jì)可以是單片的,結(jié)合硬件初始化和啟動(dòng)功能,也可以遵循模塊化和分階段的啟動(dòng)流程。設(shè)計(jì)選擇取決于系統(tǒng)要求,可能對(duì)某些設(shè)備更有利。
下圖說(shuō)明了不同的固件組件如何交互以及如何一起使用來(lái)支持啟動(dòng)過(guò)程(箭頭表示執(zhí)行順序):
如果這些圖表現(xiàn)在看起來(lái)很復(fù)雜,請(qǐng)不要擔(dān)心。閱讀本文后再回顧一下,它們會(huì)更清晰。
第一階段引導(dǎo)加載程序 (FSBL)
此固件旨在初始化計(jì)算機(jī)和嵌入式系統(tǒng),重點(diǎn)是最少的硬件初始化:只執(zhí)行絕對(duì)需要的操作,然后將控制權(quán)交給第二階段引導(dǎo)加載程序以啟動(dòng)操作系統(tǒng)。FSBL不會(huì)從閃存芯片以外的存儲(chǔ)介質(zhì)加載操作系統(tǒng)。 由于它只初始化底層硬件,而不處理硬盤(pán)、SSD 或 USB 閃存驅(qū)動(dòng)器等啟動(dòng)介質(zhì),因此需要另一個(gè)軟件來(lái)實(shí)際啟動(dòng)操作系統(tǒng)。
FSBL 的主要職責(zé):
1.CPU:從 16 位實(shí)模式.切換到 32 位保護(hù)模式.(注意:或者在 BIOS 的情況下處于虛擬 8086 模式.)。
2.緩存利用率:調(diào)用 FSP-T 為 C 環(huán)境 配置 Cache-As-RAM 。
3.調(diào)試端口:通過(guò)調(diào)用特定于板的初始化方法來(lái)初始化配置的調(diào)試端口。
4.內(nèi)存初始化:調(diào)用 FSP-M 初始化系統(tǒng)主內(nèi)存并保存關(guān)鍵的系統(tǒng)內(nèi)存信息。
5.GPIO:配置通用輸入 / 輸出(GPIO)引腳用于與外部設(shè)備連接。
6.硅:執(zhí)行早期平臺(tái)初始化并使用 FSP-S 完成芯片組、CPU 和 IO 控制器的初始化。
7.PCI 枚舉:枚舉 PCI 設(shè)備并分配內(nèi)存地址和 IRQ 等資源。
8.有效載荷準(zhǔn)備:設(shè)置 SMBIOS 和 ACPI 表,包括需要傳遞給有效載荷的準(zhǔn)備信息(coreboot 表、HOB)。
9.裝載和交接:裝載并將控制權(quán)轉(zhuǎn)交給有效載荷。
BIOS(POST 階段)
在計(jì)算機(jī)發(fā)展的早期,開(kāi)源軟件并不流行,大多數(shù) BIOS 實(shí)現(xiàn)都是專有的。只有少數(shù)可用的開(kāi)放解決方案提供 BIOS POST 源代碼,例如 Super PC/Turbo XT BIOS 和 GLaBIOS 。這些項(xiàng)目旨在在 IBM 5150/5155/5160 系統(tǒng)和大多數(shù) XT 克隆系統(tǒng)上運(yùn)行。
然而,更知名的開(kāi)源 BIOS 實(shí)現(xiàn)(如 OpenBIOS 和 SeaBIOS )不執(zhí)行硬件初始化,因?yàn)樗鼈儾淮蛩阍诼銠C(jī)上運(yùn)行。但它們被廣泛用作第二階段引導(dǎo)加載程序,并在 QEMU 和 Bochs 等虛擬環(huán)境中本地運(yùn)行。
無(wú)論如何,您幾乎不需要直接使用這些早期的 BIOS 或深入研究它們的細(xì)節(jié)。但如果您有興趣探索,上述存儲(chǔ)庫(kù)是一個(gè)很好的起點(diǎn)。
就目前的發(fā)展趨勢(shì)而言,似乎沒(méi)有專有 BIOS 解決方案的持續(xù)開(kāi)發(fā),而且面對(duì)現(xiàn)代替代方案,此類項(xiàng)目已經(jīng)過(guò)時(shí)。
UEFI 平臺(tái)初始化 (PI)
啟動(dòng)過(guò)程遵循分階段的流程,在下圖中從左側(cè)開(kāi)始,然后移至右側(cè)。平臺(tái)啟動(dòng)過(guò)程的時(shí)間線分為以下幾個(gè)階段,如黃色框所示:
- 安全(SEC):復(fù)位向量 之后的第一個(gè)階段,其主要功能是設(shè)置臨時(shí) RAM(CPU Cache-As-RAM 或 SRAM)。
- 預(yù) EFI 初始化 (PEI):此階段調(diào)度稱為預(yù) EFI 初始化模塊 (PEIM) 的專用驅(qū)動(dòng)程序。這些模塊處理必要的硬件初始化,例如配置 CPU 和芯片組以及設(shè)置主內(nèi)存 (DRAM)。
- 驅(qū)動(dòng)程序執(zhí)行環(huán)境 (DXE):在此階段,執(zhí)行系統(tǒng)初始化的其余部分。DXE 階段提供 UEFI 服務(wù)并支持系統(tǒng)運(yùn)行所需的各種協(xié)議和驅(qū)動(dòng)程序。
- 啟動(dòng)設(shè)備選擇 (BDS):此階段實(shí)施平臺(tái)啟動(dòng)策略,確定啟動(dòng)順序并選擇適當(dāng)?shù)膯?dòng)設(shè)備 / 加載程序。
- 瞬態(tài)系統(tǒng)加載 (TSL):在此階段,系統(tǒng)使用 UEFI 服務(wù)運(yùn)行應(yīng)用程序來(lái)準(zhǔn)備操作系統(tǒng)。它包括從 UEFI 環(huán)境到操作系統(tǒng)的轉(zhuǎn)換,最后以
ExitBootServices ()
調(diào)用結(jié)束。 - 運(yùn)行時(shí)(RT):在此階段,操作系統(tǒng)完全運(yùn)行,管理其控制下的系統(tǒng)。
- 使用壽命結(jié)束后 (AL):此階段處理硬件或操作系統(tǒng)崩潰 / 關(guān)閉 / 重啟的情況。固件可能會(huì)嘗試恢復(fù)操作,但是,UEFI PI 規(guī)范并未為此階段定義特定要求或行為。
這個(gè)過(guò)程及其執(zhí)行階段在UEFI 平臺(tái)初始化 (PI) 規(guī)范中有所介紹。不過(guò),還有UEFI 接口(圖中用粗藍(lán)線表示),它不屬于前一個(gè)文檔,而是在UEFI 規(guī)范中有所描述。雖然 * UEFI * 的名稱和頻繁使用可能會(huì)造成混淆,但這兩個(gè)文檔的重點(diǎn)有所不同:
-UEFI PI 規(guī)范:專注于低級(jí)固件組件之間的接口,并詳細(xì)說(shuō)明這些模塊如何交互以初始化平臺(tái)。
-UEFI 規(guī)范:定義操作系統(tǒng) (OS) 和固件之間交互的接口。這將在第二階段引導(dǎo)加載程序的背景下進(jìn)一步討論。請(qǐng)注意,UEFI 規(guī)范依賴于 PI 規(guī)范。
本質(zhì)上,這兩個(gè)規(guī)范都是關(guān)于接口的,只是層次不同。有關(guān)詳細(xì)信息,您可以在 UEFI 論壇網(wǎng)站 上訪問(wèn)這兩個(gè)規(guī)范。
UEFI PI 最初設(shè)計(jì)為統(tǒng)一的固件解決方案,沒(méi)有考慮第一階段和第二階段引導(dǎo)程序之間的區(qū)別。但是,當(dāng)我們將 UEFI 稱為**[第一階段引導(dǎo)程序](https://hackernoon.com/lang/zh/ 從 - biosuefi - 到 - os - 的固件秘密之旅 #fsbl)** 時(shí),它包括 SEC、PEI 和早期 DXE 階段。我們將 DXE 分為早期和晚期階段的原因是由于它們?cè)诔跏蓟^(guò)程中扮演的角色不同。
在早期 DXE 階段,驅(qū)動(dòng)程序通常執(zhí)行必要的 CPU/PCH/ 主板初始化,并生成**DXE 架構(gòu)協(xié)議 (AP),這有助于將 DXE 階段與平臺(tái)特定的硬件隔離開(kāi)來(lái)。AP 封裝了特定于平臺(tái)的細(xì)節(jié),使 后期 DXE 階段能夠獨(dú)立于硬件細(xì)節(jié)運(yùn)行。
核心啟動(dòng)
關(guān)于 Coreboot 工作原理的詳細(xì)文章即將發(fā)布。關(guān)注我的社交媒體 - 它們很快就會(huì)發(fā)布!
其他解決方案
- Intel Slim Bootloader (SBL). :純粹的第一階段引導(dǎo)程序,僅提供核心硬件組件初始化,然后加載有效負(fù)載。但是,它僅適用于 Intel x86 平臺(tái),不支持 AMD x86 或其他架構(gòu)。
- Das U-Boot. :既是第一階段的引導(dǎo)程序,也是第二階段的引導(dǎo)程序。但是,與其他固件相比,直接從平臺(tái)的 x86 重置向量(稱為裸模式)引導(dǎo)的支持有限。它在嵌入式系統(tǒng)和基于 ARM 的設(shè)備上更受歡迎。作為第二階段的引導(dǎo)程序,U-Boot 實(shí)現(xiàn)了 UEFI 的一個(gè)子集,但專注于嵌入式系統(tǒng)。
第二階段引導(dǎo)加載程序 (SSBL)
初始硬件設(shè)置完成后,進(jìn)入第二階段。其主要作用是在操作系統(tǒng)和平臺(tái)固件之間設(shè)置軟件接口,確保操作系統(tǒng)可以管理系統(tǒng)資源并與硬件組件交互。
SSBL 旨在盡可能隱藏硬件差異,通過(guò)處理大部分硬件級(jí)接口來(lái)簡(jiǎn)化操作系統(tǒng)和應(yīng)用程序開(kāi)發(fā)。這種抽象使開(kāi)發(fā)人員可以專注于更高級(jí)別的功能,而不必?fù)?dān)心底層硬件差異。
SSBL 的主要職責(zé):
1.平臺(tái)信息檢索:從第一階段引導(dǎo)加載程序獲取平臺(tái)特定信息,包括內(nèi)存映射、SMBIOS、ACPI 表、SPI 閃存等。
2.運(yùn)行平臺(tái)獨(dú)立驅(qū)動(dòng)程序:包括 SMM、SPI、PCI、SCSI/ATA/IDE/DISK、USB、ACPI、網(wǎng)絡(luò)接口等驅(qū)動(dòng)程序。
3.服務(wù)實(shí)現(xiàn)(又名接口):提供一組促進(jìn)操作系統(tǒng)和硬件組件之間通信的服務(wù)。
4.設(shè)置菜單:提供系統(tǒng)配置的設(shè)置菜單,允許用戶調(diào)整與啟動(dòng)順序、硬件首選項(xiàng)和其他系統(tǒng)參數(shù)相關(guān)的設(shè)置。
5.啟動(dòng)邏輯:從可用的啟動(dòng)媒體定位并加載有效負(fù)載(可能是操作系統(tǒng))的機(jī)制。
BIOS
BIOS 中的接口稱為BIOS 服務(wù) / 功能 / 中斷調(diào)用。這些功能提供了一組用于硬件訪問(wèn)的例程,但它們?cè)谙到y(tǒng)特定硬件上執(zhí)行的具體細(xì)節(jié)對(duì)用戶是隱藏的。
在 16 位 實(shí)模式.中,可以通過(guò) INT x86 匯編語(yǔ)言指令調(diào)用軟件中斷輕松訪問(wèn)它們。在 32 位保護(hù)模式.中,由于段值的處理方式不同,幾乎所有 BIOS 服務(wù)都不可用。
我們以 磁盤(pán)服務(wù)( INT 13h
) 為例,它使用 柱面 - 磁頭 - 扇區(qū) (CHS) 尋址提供基于扇區(qū)的硬盤(pán)和軟盤(pán)讀寫(xiě)服務(wù),作為如何使用此接口的示例。假設(shè)我們要讀取 2 個(gè)扇區(qū) (1024 字節(jié)) 并將它們加載到內(nèi)存地址 * 0x9020* ,則可以執(zhí)行以下代碼:
mov $0x02, %ah # Set BIOS read sector routine
mov $0x00, %ch # Select cylinder 0
mov $0x00, %dh # Select head 0 [has a base of 0]
mov $0x02, %cl # Select sector 2 (next after the boot sector) [has a base of 1]
mov $0x02, %al # Read 2 sectors
mov $0x00, %bx # Set BX general register to 0
mov %bx, %es # Set ES segment register to 0
mov $0x9020, %bx # Load sectors to ES:BX (0:0x9020)
int $0x13 # Start reading from drive
jmp $0x9020 # Jump to loaded code
如果您對(duì) SeaBios 中這項(xiàng)服務(wù)的編寫(xiě)方式感興趣,請(qǐng)查看 src/disk.c 。
引導(dǎo)階段
-
通過(guò)讀取設(shè)備的第一個(gè) 512 字節(jié)扇區(qū)(扇區(qū)零)開(kāi)始搜索可啟動(dòng)設(shè)備(可以是硬盤(pán)、CD-ROM、軟盤(pán)等)。
-
BIOS 中的引導(dǎo)序列將它找到的第一個(gè)有效的 主引導(dǎo)記錄 (MBR) 加載到計(jì)算機(jī)物理內(nèi)存的物理地址 0x7C00 處(提示: 0x0000:0x7c00 和 0x7c0:0x0000 指的是同一個(gè)物理地址)。
-
BIOS 將控制權(quán)移交給有效負(fù)載的前 512 個(gè)字節(jié)。這個(gè)已加載的扇區(qū)太小,無(wú)法容納整個(gè)有效負(fù)載代碼,其目的是從可啟動(dòng)設(shè)備加載剩余的有效負(fù)載。此時(shí),有效負(fù)載可以使用 BIOS 公開(kāi)的接口。
值得注意的是,早期并不存在 BIOS 規(guī)范。BIOS 是事實(shí)上的標(biāo)準(zhǔn)- 它的工作方式與 20 世紀(jì) 80 年代實(shí)際的 IBM PC 上的工作方式相同。其余制造商只是進(jìn)行逆向工程并制作與 IBM 兼容的 BIOS。因此,沒(méi)有法規(guī)阻止 BIOS 制造商發(fā)明新的 BIOS 功能或具有重疊的功能。
統(tǒng)一可擴(kuò)展固件接口 (UEFI)
如前所述,UEFI 本身只是一個(gè)規(guī)范,有很多實(shí)現(xiàn)。最廣泛使用的就是 TianoCore EDK II ,它是 UEFI 和 PI 規(guī)范的開(kāi)源 參考實(shí)現(xiàn)。雖然僅靠 EDKII 不足以創(chuàng)建功能齊全的啟動(dòng)固件,但它為大多數(shù)商業(yè)解決方案提供了堅(jiān)實(shí)的基礎(chǔ)。
為了支持不同的第一階段引導(dǎo)加載程序并提供 UEFI 接口,我們使用了 UEFI Payload項(xiàng)目。它依賴于完成的初始設(shè)置和引導(dǎo)固件提供的平臺(tái)信息來(lái)為 UEFI 環(huán)境準(zhǔn)備系統(tǒng)。
UEFI Payload 使用 DXE 和 BDS 階段,這兩個(gè)階段的設(shè)計(jì)與平臺(tái)無(wú)關(guān)。它提供可適應(yīng)不同平臺(tái)的通用負(fù)載。在大多數(shù)情況下,它不需要任何自定義或針對(duì)特定平臺(tái)的調(diào)整,并且可以通過(guò)使用第一階段引導(dǎo)加載程序中的平臺(tái)信息按原樣使用。
UEFI Payload 的變體:
1.舊版 UEFI 有效負(fù)載:需要解析庫(kù)來(lái)提取必要的特定于實(shí)現(xiàn)的平臺(tái)信息。如果引導(dǎo)加載程序更新其 API,則有效負(fù)載也必須更新。
2.通用 UEFI 負(fù)載:遵循 通用可擴(kuò)展固件 (USF) 規(guī)范,使用 可執(zhí)行和可鏈接格式 (ELF) 或 平面映像樹(shù) (FIT) 作為通用映像格式。它不需要自己解析它們,而是希望在負(fù)載入口處接收切換塊 (HOB)。
雖然 Legacy UEFI Payload運(yùn)行良好,但 EDK2 社區(qū)正試圖將行業(yè)轉(zhuǎn)向 Universal UEFI Payload。有效載荷之間的選擇取決于您的固件組件。例如,如果沒(méi)有 我的補(bǔ)丁, 就無(wú)法在 Slim Bootloader 上運(yùn)行支持 SMM 的 Legacy Payload。另一方面,將Universal Payload與 coreboot 一起使用需要一個(gè)墊片層來(lái)將 coreboot 表轉(zhuǎn)換為 HOB,這是 StarLabs EDK2 fork 中才有的功能。
界面
每個(gè)符合 UEFI 標(biāo)準(zhǔn)的系統(tǒng)都提供了一個(gè)系統(tǒng)表,該表會(huì)傳遞給在 UEFI 環(huán)境中運(yùn)行的每個(gè)代碼(驅(qū)動(dòng)程序、應(yīng)用程序、操作系統(tǒng)加載器)。此數(shù)據(jù)結(jié)構(gòu)允許 UEFI 可執(zhí)行文件訪問(wèn)**系統(tǒng)配置表,**例如 ACPI、SMBIOS和一組 UEFI 服務(wù)。
Only available prior to OS runtime | Available before and during OS runtime |
---|
表結(jié)構(gòu)在 MdePkg/Include/Uefi/UefiSpec.h 中描述:
typedef struct {EFI_TABLE_HEADER Hdr;CHAR16 *FirmwareVendor;UINT32 FirmwareRevision;EFI_HANDLE ConsoleInHandle;EFI_SIMPLE_TEXT_INPUT_PROTOCOL *ConIn;EFI_HANDLE ConsoleOutHandle;EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut;EFI_HANDLE StandardErrorHandle;EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *StdErr;// A pointer to the EFI Runtime Services Table.// EFI_RUNTIME_SERVICES *RuntimeServices;// A pointer to the EFI Boot Services Table.// EFI_BOOT_SERVICES *BootServices;UINTN NumberOfTableEntries;EFI_CONFIGURATION_TABLE *ConfigurationTable;
} EFI_SYSTEM_TABLE;
服務(wù)包括以下類型:引導(dǎo)服務(wù)、運(yùn)行時(shí)服務(wù)和通過(guò)協(xié)議提供的服務(wù)。
UEFI 通過(guò)設(shè)置 UEFI 協(xié)議來(lái)抽象對(duì)設(shè)備的訪問(wèn)。這些協(xié)議是包含函數(shù)指針的數(shù)據(jù)結(jié)構(gòu),由**全局唯一標(biāo)識(shí)符 (GUID)**標(biāo)識(shí),允許其他模塊找到并使用它們。它們可以通過(guò)引導(dǎo)服務(wù)發(fā)現(xiàn)。
UEFI 驅(qū)動(dòng)程序生成這些協(xié)議,實(shí)際函數(shù)(不是指針!)包含在驅(qū)動(dòng)程序本身中。此機(jī)制允許 UEFI 環(huán)境中的不同組件相互通信,并確保操作系統(tǒng)在加載其自己的驅(qū)動(dòng)程序之前可以與設(shè)備交互。
雖然某些協(xié)議是在 UEFI 規(guī)范中預(yù)定義和描述的,但固件供應(yīng)商也可以 * 創(chuàng)建自己的自定義協(xié)議 * 來(lái)擴(kuò)展平臺(tái)的功能。
引導(dǎo)服務(wù)
提供只能在啟動(dòng)時(shí)使用的函數(shù)。這些服務(wù)保持可用,直到調(diào)用 EFI_BOOT_SERVICES.ExitBootServices ()
函數(shù)( MdeModulePkg/Core/Dxe/DxeMain/DxeMain.c )。
指向所有啟動(dòng)服務(wù)的指針都存儲(chǔ)在啟動(dòng)服務(wù)表( MdePkg/Include/Uefi/UefiSpec.h ) 中:
typedef struct {EFI_TABLE_HEADER Hdr;...EFI_GET_MEMORY_MAP GetMemoryMap;EFI_ALLOCATE_POOL AllocatePool;EFI_FREE_POOL FreePool;...EFI_HANDLE_PROTOCOL HandleProtocol;...EFI_EXIT_BOOT_SERVICES ExitBootServices;...
} EFI_BOOT_SERVICES;
運(yùn)行時(shí)服務(wù)
操作系統(tǒng)運(yùn)行時(shí),仍有一組最少的服務(wù)可供訪問(wèn)。與引導(dǎo)服務(wù)不同,在任何有效負(fù)載(例如,操作系統(tǒng)引導(dǎo)加載程序)通過(guò)調(diào)用 EFI_BOOT_SERVICES.ExitBootServices()
控制平臺(tái)后,這些服務(wù)仍然有效。
指向所有運(yùn)行時(shí)服務(wù)的指針都存儲(chǔ)在 運(yùn)行時(shí)服務(wù)表 (MdePkg/Include/Uefi/UefiSpec.h) 中:
typedef struct {EFI_TABLE_HEADER Hdr;...EFI_GET_TIME GetTime;EFI_SET_TIME SetTime;...EFI_GET_VARIABLE GetVariable;EFI_GET_NEXT_VARIABLE_NAME GetNextVariableName;EFI_SET_VARIABLE SetVariable;...EFI_GET_NEXT_HIGH_MONO_COUNT GetNextHighMonotonicCount;EFI_RESET_SYSTEM ResetSystem;...
} EFI_RUNTIME_SERVICES;
下圖顯示了啟動(dòng)和運(yùn)行時(shí)服務(wù)的時(shí)間線,因此您可以準(zhǔn)確地看到每個(gè)服務(wù)何時(shí)處于活動(dòng)狀態(tài)。
啟動(dòng)設(shè)備選擇 (BDS) 階段
UEFI 規(guī)范定義了一個(gè)稱為 UEFI 啟動(dòng)管理器的啟動(dòng)策略引擎。它將嘗試按特定順序加載 UEFI 應(yīng)用程序。可以通過(guò)修改全局 NVRAM(非易失性隨機(jī)存取存儲(chǔ)器)變量來(lái)配置此順序和其他設(shè)置。讓我們討論其中最重要的一些:
Boot####
(####
由一個(gè)唯一的十六進(jìn)制值替換)—— 啟動(dòng) / 加載選項(xiàng)。BootCurrent
— 用于啟動(dòng)當(dāng)前運(yùn)行系統(tǒng)的啟動(dòng)選項(xiàng)。BootNext
— 僅用于下次啟動(dòng)的啟動(dòng)選項(xiàng)。它僅用于一次BootOrder
,并在首次使用后被啟動(dòng)管理器刪除。這允許您更改下次啟動(dòng)行為而不更改BootOrder
。BootOrder
— 有序的啟動(dòng)選項(xiàng)加載列表。啟動(dòng)管理器嘗試啟動(dòng)此列表中的第一個(gè)活動(dòng)選項(xiàng)。如果不成功,則嘗試下一個(gè)選項(xiàng),依此類推。BootOptionSupport
— 啟動(dòng)管理器支持的啟動(dòng)選項(xiàng)類型。Timeout
— 固件的啟動(dòng)管理器在自動(dòng)從BootNext
或BootOrder
中選擇啟動(dòng)值之前的超時(shí)時(shí)間(以秒為單位)。
使用 efibootmgr (8) 可以輕松從 Linux 獲取這些變量:
[root@localhost ~]# efibootmgr
BootCurrent: 0000
Timeout: 5 seconds
BootOrder: 0000,0001,2001,2002,2003
Boot0000* ARCHLINUX HD (5,GPT,d03ca3cf-1511-d94e-8400-c7a125866442,0x40164000,0x100000)/File (\EFI\ARCHLINUX\grubx64.efi)
Boot0001* Windows Boot Manager HD (1,GPT,6f185443-09fc-4f15-afdf-01c523565e52,0x800,0x32000)/File (\EFI\Microsoft\Boot\bootmgfw.efi) 57a94e544f5753000100000088900100780000004200430044039f0a42004a004500430054003d007b00390064006500610038003600320063002d1139006300640064002d0034006500370030102d0061006300630031002d006600330032006200330034003400640034003700390035007d00000033000300000710000000040000007fff0400
Boot0002* ARCHLINUX HD (5,GPT,d03ca3cf-1511-d94e-8400-c7a125866442,0x40164000,0x100000)
Boot2001* EFI USB Device RC
Boot2002* EFI DVD/CDROM RC
Boot2003* EFI Network RC
讓我們根據(jù)上面的代碼片段來(lái)看一下啟動(dòng)過(guò)程。UEFI 將開(kāi)始迭代 BootOrder
列表。對(duì)于列表中的每個(gè)條目,它會(huì)查找相應(yīng)的 Boot####
變量 — Boot0000
表示 0000, Boot2003
表示 2003,依此類推。如果變量不存在,它將繼續(xù)查找下一個(gè)條目。如果變量存在,它會(huì)讀取變量的內(nèi)容。每個(gè)啟動(dòng)選項(xiàng)變量都包含一個(gè) EFI_LOAD_OPTION
描述符,它是可變長(zhǎng)度字段的字節(jié)打包緩沖區(qū)(它只是數(shù)據(jù)結(jié)構(gòu))。
數(shù)據(jù)結(jié)構(gòu)描述在 [MdePkg/Include/Uefi/UefiSpec.h][ https://github.com/tianocore/edk2/blob/edk2-stable202405/MdePkg/Include/Uefi/UefiSpec.h#L2122 )
typedef struct _EFI_LOAD_OPTION {/// The attributes for this load option entry.UINT32 Attributes;/// Length in bytes of the FilePathList.UINT16 FilePathListLength;/// The user readable description for the load option./// Example: 'ARCHLINUX' / 'Windows Boot Manager' / `EFI USB Device`CHAR16 Description [];/// A packed array of UEFI device paths./// Example: 'HD (5,GPT,d03ca3cf-1511-d94e-8400-c7a125866442,0x40164000,0x100000)/File (\EFI\ARCHLINUX\grubx64.efi)'EFI_DEVICE_PATH_PROTOCOL FilePathList [];/// The remaining bytes in the load option descriptor are a binary data buffer that is passed to the loaded image./// Example: '57a9...0400' in Boot0001 variableUINT8 OptionalData [];
} EFI_LOAD_OPTION;
此時(shí),固件將檢查設(shè)備路徑( EFI_DEVICE_PATH_PROTOCOL )。在大多數(shù)情況下,我們的計(jì)算機(jī)是從存儲(chǔ)設(shè)備(硬盤(pán) / SSD/NVMe/ 等)啟動(dòng)的。因此,設(shè)備路徑將包含 HD (Partition Number, Type, Signature, Start sector, Size in sectors)
節(jié)點(diǎn)。
-類型— 使用關(guān)鍵字 MBR(1) 或 GPT(2) 表示分區(qū)方案使用的格式。
-簽名— 如果類型為 MBR,則為 4 字節(jié) MBR 簽名;如果類型為 GPT,則為 16 字節(jié)UUID。
- 注意:如果您對(duì)如何轉(zhuǎn)換其他路徑感興趣,請(qǐng)閱讀 UEFI 規(guī)范 v2.10,10.6.1.6 文本設(shè)備節(jié)點(diǎn)參考。
UEFI 將檢查磁盤(pán),看它是否有與節(jié)點(diǎn)匹配的分區(qū)。如果存在,則應(yīng)使用特定的全局唯一標(biāo)識(shí)符 (GUID) 對(duì)其進(jìn)行標(biāo)記,將其標(biāo)記為 EFI 系統(tǒng)分區(qū) (ESP) 。該分區(qū)使用文件系統(tǒng)格式化,其規(guī)范基于特定版本的 FAT 文件系統(tǒng),名為 EFI 文件系統(tǒng);實(shí)際上,它只是一個(gè)普通的FAT12/16/32. 。
- 本機(jī)啟動(dòng):如果設(shè)備路徑包含文件
File (\Path\To\The\File.efi)
的明確路徑,則 UEFI 將查找該特定文件。例如,Boot0000
選項(xiàng)包含File (\EFI\ARCHLINUX\grubx64.efi)
。
-后備啟動(dòng):如果設(shè)備路徑只是指向一個(gè)磁盤(pán),那么在這種情況下,固件將使用基于架構(gòu)的后備啟動(dòng)路徑—\EFI\BOOT\BOOT {arch}.EFI
(對(duì)于 amd64 為BOOTx64.EFI
或?qū)τ?i386 / IA32 為BOOTia32.EFI
)。此機(jī)制允許 * 可啟動(dòng)的可移動(dòng)媒體 *(例如 USB 驅(qū)動(dòng)器)在 UEFI 中工作;它們只是使用后備啟動(dòng)路徑。例如,Boot0002
選項(xiàng)將使用此機(jī)制。
- 注意:上面提到的所有
Boot####
選項(xiàng)均指 * efibootmgr * 示例輸出中顯示的啟動(dòng)選項(xiàng)。
在這兩種情況下,UEFI 啟動(dòng)管理器都會(huì)將 UEFI 應(yīng)用程序(可能是 [OS 引導(dǎo)程序](https://hackernoon.com/lang/zh/ 從 - biosuefi - 到 - os - 的固件秘密之旅 #osbl)、UEFI Shell、實(shí)用程序軟件、系統(tǒng)設(shè)置等)加載到內(nèi)存中。此時(shí),控制權(quán)將轉(zhuǎn)移到 UEFI 應(yīng)用程序的入口點(diǎn)。與BIOS不同,UEFI 應(yīng)用程序可以將控制權(quán)返回給固件(除了應(yīng)用程序接管系統(tǒng)控制的情況)。如果發(fā)生這種情況或出現(xiàn)任何問(wèn)題,啟動(dòng)管理器將轉(zhuǎn)到下一個(gè) Boot####
條目,并遵循完全相同的過(guò)程。
規(guī)范中提到,啟動(dòng)管理器可以自動(dòng)維護(hù)數(shù)據(jù)庫(kù)變量。這包括刪除未引用或無(wú)法解析的加載選項(xiàng)變量。此外,它可以重寫(xiě)任何有序列表以刪除任何沒(méi)有對(duì)應(yīng)加載選項(xiàng)變量的加載選項(xiàng)。
以上內(nèi)容介紹了UEFI 啟動(dòng)。此外,UEFI 固件還可以在模擬 BIOS 的 兼容支持模塊 (CSM) 模式下運(yùn)行。
操作系統(tǒng)引導(dǎo)加載程序
由固件(通常是第二階段引導(dǎo)程序)啟動(dòng)并使用其接口加載操作系統(tǒng)內(nèi)核的軟件。它可以像操作系統(tǒng)一樣復(fù)雜,提供以下功能:
- 從各種文件系統(tǒng)讀取(HFS+、ext4、XFS 等)
- 通過(guò)網(wǎng)絡(luò)進(jìn)行交互(例如 TFTP、HTTP)
- 啟動(dòng)兼容多重.引導(dǎo)的內(nèi)核
- 鏈?zhǔn)郊虞d.
- 加載初始 ramdisk ( initrd )
- 還有更多!
這些程序的共同設(shè)計(jì)超出了本文的討論范圍。有關(guān)流行操作系統(tǒng)引導(dǎo)加載程序的詳細(xì)比較,您可以參考 ArchLinux wiki 和 Wikipedia 文章。
Windows 系統(tǒng)使用其專有的操作系統(tǒng)引導(dǎo)加載程序,稱為 Windows 啟動(dòng)管理器 (BOOTMGR) 。
固件不再是 一小段復(fù)雜的 代碼。它已經(jīng)變成了 大量復(fù)雜的代碼,而當(dāng)前的趨勢(shì)只會(huì)助長(zhǎng)這一點(diǎn)。我們可以在固件上運(yùn)行 Doom 、 Twitter 和許多其他有趣的應(yīng)用程序。
了解整體架構(gòu)有助于在腦海中組織這些組件。通過(guò)檢查現(xiàn)有固件的設(shè)計(jì),您可以深入了解每次啟動(dòng)計(jì)算機(jī)時(shí)展開(kāi)的迷人過(guò)程。這種自上而下的視角不僅闡明了每個(gè)部分的作用,還突出了現(xiàn)代固件系統(tǒng)的復(fù)雜和不斷發(fā)展的性質(zhì)。
About Author
Aleksandr Goncharov@tristejoursoir
This article has provided a lot of theoretical information related to how booting works. However, to truly understand this process, we need to take a closer look at the source code and architecture of existing firmware.
I respect & support ideas of open-source. Interested in low-level solutions (firmware/kernel) and application software.
計(jì)算機(jī)是如何啟動(dòng)的?
作者: 阮一峰
日期: 2013 年 2 月 16 日
從打開(kāi)電源到開(kāi)始操作,計(jì)算機(jī)的啟動(dòng)是一個(gè)非常復(fù)雜的過(guò)程。
這個(gè)過(guò)程只看見(jiàn)屏幕快速滾動(dòng)各種提示… 到底是怎么回事?
查了一些資料,下面就是我整理的筆記。
零、boot 的含義
“啟動(dòng)” 用英語(yǔ)怎么說(shuō)?
是 boot??墒?#xff0c;boot 原意是靴子,“啟動(dòng)” 與靴子有什么關(guān)系呢? 原來(lái),這里的 boot 是 bootstrap(鞋帶)的縮寫(xiě),它來(lái)自一句諺語(yǔ):
“pull oneself up by one’s bootstraps”.
字面意思是 “拽著鞋帶把自己拉起來(lái)”,這當(dāng)然是不可能的事情。最早的時(shí)候,工程師們用它來(lái)比喻,計(jì)算機(jī)啟動(dòng)是一個(gè)很矛盾的過(guò)程:必須先運(yùn)行程序,然后計(jì)算機(jī)才能啟動(dòng),但是計(jì)算機(jī)不啟動(dòng)就無(wú)法運(yùn)行程序!
早期真的是這樣,必須想盡各種辦法,把一小段程序裝進(jìn)內(nèi)存,然后計(jì)算機(jī)才能正常運(yùn)行。所以,工程師們把這個(gè)過(guò)程叫做 “拉鞋帶”,久而久之就簡(jiǎn)稱為 boot 了。
計(jì)算機(jī)的整個(gè)啟動(dòng)過(guò)程分成四個(gè)階段。
一、第一階段:BIOS
上個(gè)世紀(jì) 70 年代初,“只讀內(nèi)存”(read-only memory,縮寫(xiě)為 ROM)發(fā)明,開(kāi)機(jī)程序被刷入 ROM 芯片,計(jì)算機(jī)通電后,第一件事就是讀取它。
這塊芯片里的程序叫做 “基本輸出輸入系統(tǒng)”(Basic Input/Output System),簡(jiǎn)稱為 BIOS。
1.1 硬件自檢
BIOS 程序首先檢查,計(jì)算機(jī)硬件能否滿足運(yùn)行的基本條件,這叫做 “硬件自檢”(Power-On Self-Test),縮寫(xiě)為 POST。
如果硬件出現(xiàn)問(wèn)題,主板會(huì)發(fā)出不同含義的 蜂鳴,啟動(dòng)中止。如果沒(méi)有問(wèn)題,屏幕就會(huì)顯示出 CPU、內(nèi)存、硬盤(pán)等信息。
1.2 啟動(dòng)順序
硬件自檢完成后,BIOS 把控制權(quán)轉(zhuǎn)交給下一階段的啟動(dòng)程序。
這時(shí),BIOS 需要知道,“下一階段的啟動(dòng)程序” 具體存放在哪一個(gè)設(shè)備。也就是說(shuō),BIOS 需要有一個(gè)外部?jī)?chǔ)存設(shè)備的排序,排在前面的設(shè)備就是優(yōu)先轉(zhuǎn)交控制權(quán)的設(shè)備。這種排序叫做 “啟動(dòng)順序”(Boot Sequence)。
打開(kāi) BIOS 的操作界面,里面有一項(xiàng)就是 “設(shè)定啟動(dòng)順序”。
二、第二階段:主引導(dǎo)記錄
BIOS 按照 “啟動(dòng)順序”,把控制權(quán)轉(zhuǎn)交給排在第一位的儲(chǔ)存設(shè)備。
這時(shí),計(jì)算機(jī)讀取該設(shè)備的第一個(gè)扇區(qū),也就是讀取最前面的 512 個(gè)字節(jié)。如果這 512 個(gè)字節(jié)的最后兩個(gè)字節(jié)是 0x55 和 0xAA,表明這個(gè)設(shè)備可以用于啟動(dòng);如果不是,表明設(shè)備不能用于啟動(dòng),控制權(quán)于是被轉(zhuǎn)交給 “啟動(dòng)順序” 中的下一個(gè)設(shè)備。
這最前面的 512 個(gè)字節(jié),就叫做 “主引導(dǎo)記錄”(Master boot record,縮寫(xiě)為 MBR)。
2.1 主引導(dǎo)記錄的結(jié)構(gòu)
“主引導(dǎo)記錄” 只有 512 個(gè)字節(jié),放不了太多東西。它的主要作用是,告訴計(jì)算機(jī)到硬盤(pán)的哪一個(gè)位置去找操作系統(tǒng)。
主引導(dǎo)記錄由三個(gè)部分組成:
(1) 第 1-446 字節(jié):調(diào)用操作系統(tǒng)的機(jī)器碼。
(2) 第 447-510 字節(jié):分區(qū)表(Partition table)。
(3) 第 511-512 字節(jié):主引導(dǎo)記錄簽名(0x55 和 0xAA)。
其中,第二部分 “分區(qū)表” 的作用,是將硬盤(pán)分成若干個(gè)區(qū)。
2.2 分區(qū)表
硬盤(pán)分區(qū)有很多 好處??紤]到每個(gè)區(qū)可以安裝不同的操作系統(tǒng),“主引導(dǎo)記錄” 因此必須知道將控制權(quán)轉(zhuǎn)交給哪個(gè)區(qū)。
分區(qū)表的長(zhǎng)度只有 64 個(gè)字節(jié),里面又分成四項(xiàng),每項(xiàng) 16 個(gè)字節(jié)。所以,一個(gè)硬盤(pán)最多只能分四個(gè)一級(jí)分區(qū),又叫做 “主分區(qū)”。
每個(gè)主分區(qū)的 16 個(gè)字節(jié),由 6 個(gè)部分組成:
(1) 第 1 個(gè)字節(jié):如果為 0x80,就表示該主分區(qū)是激活分區(qū),控制權(quán)要轉(zhuǎn)交給這個(gè)分區(qū)。四個(gè)主分區(qū)里面只能有一個(gè)是激活的。
(2) 第 2-4 個(gè)字節(jié):主分區(qū)第一個(gè)扇區(qū)的物理位置(柱面、磁頭、扇區(qū)號(hào)等等)。
(3) 第 5 個(gè)字節(jié):主分區(qū)類型。
(4) 第 6-8 個(gè)字節(jié):主分區(qū)最后一個(gè)扇區(qū)的物理位置。
(5) 第 9-12 字節(jié):該主分區(qū)第一個(gè)扇區(qū)的邏輯地址。
(6) 第 13-16 字節(jié):主分區(qū)的扇區(qū)總數(shù)。
最后的四個(gè)字節(jié)(“主分區(qū)的扇區(qū)總數(shù)”),決定了這個(gè)主分區(qū)的長(zhǎng)度。也就是說(shuō),一個(gè)主分區(qū)的扇區(qū)總數(shù)最多不超過(guò) 2 的 32 次方。
如果每個(gè)扇區(qū)為 512 個(gè)字節(jié),就意味著單個(gè)分區(qū)最大不超過(guò) 2TB。再考慮到扇區(qū)的邏輯地址也是 32 位,所以單個(gè)硬盤(pán)可利用的空間最大也不超過(guò) 2TB。如果想使用更大的硬盤(pán),只有 2 個(gè)方法:一是提高每個(gè)扇區(qū)的字節(jié)數(shù),二是 增加扇區(qū)總數(shù)。
三、第三階段:硬盤(pán)啟動(dòng)
這時(shí),計(jì)算機(jī)的控制權(quán)就要轉(zhuǎn)交給硬盤(pán)的某個(gè)分區(qū)了,這里又分成三種情況。
3.1 情況 A:卷引導(dǎo)記錄
上一節(jié)提到,四個(gè)主分區(qū)里面,只有一個(gè)是激活的。計(jì)算機(jī)會(huì)讀取激活分區(qū)的第一個(gè)扇區(qū),叫做 "卷引導(dǎo)記錄"(Volume boot record,縮寫(xiě)為 VBR)。
“卷引導(dǎo)記錄” 的主要作用是,告訴計(jì)算機(jī),操作系統(tǒng)在這個(gè)分區(qū)里的位置。然后,計(jì)算機(jī)就會(huì)加載操作系統(tǒng)了。
3.2 情況 B:擴(kuò)展分區(qū)和邏輯分區(qū)
隨著硬盤(pán)越來(lái)越大,四個(gè)主分區(qū)已經(jīng)不夠了,需要更多的分區(qū)。但是,分區(qū)表只有四項(xiàng),因此規(guī)定有且僅有一個(gè)區(qū)可以被定義成 “擴(kuò)展分區(qū)”(Extended partition)。
所謂 “擴(kuò)展分區(qū)”,就是指這個(gè)區(qū)里面又分成多個(gè)區(qū)。這種分區(qū)里面的分區(qū),就叫做 “邏輯分區(qū)”(logical partition)。
計(jì)算機(jī)先讀取擴(kuò)展分區(qū)的第一個(gè)扇區(qū),叫做 “擴(kuò)展引導(dǎo)記錄”(Extended boot record,縮寫(xiě)為 EBR)。它里面也包含一張 64 字節(jié)的分區(qū)表,但是最多只有兩項(xiàng)(也就是兩個(gè)邏輯分區(qū))。
計(jì)算機(jī)接著讀取第二個(gè)邏輯分區(qū)的第一個(gè)扇區(qū),再?gòu)睦锩娴姆謪^(qū)表中找到第三個(gè)邏輯分區(qū)的位置,以此類推,直到某個(gè)邏輯分區(qū)的分區(qū)表只包含它自身為止(即只有一個(gè)分區(qū)項(xiàng))。因此,擴(kuò)展分區(qū)可以包含無(wú)數(shù)個(gè)邏輯分區(qū)。
但是,似乎很少通過(guò)這種方式啟動(dòng)操作系統(tǒng)。如果操作系統(tǒng)確實(shí)安裝在擴(kuò)展分區(qū),一般采用下一種方式啟動(dòng)。
3.3 情況 C:啟動(dòng)管理器
在這種情況下,計(jì)算機(jī)讀取 “主引導(dǎo)記錄” 前面 446 字節(jié)的機(jī)器碼之后,不再把控制權(quán)轉(zhuǎn)交給某一個(gè)分區(qū),而是運(yùn)行事先安裝的 “啟動(dòng)管理器”(boot loader),由用戶選擇啟動(dòng)哪一個(gè)操作系統(tǒng)。
Linux 環(huán)境中,目前最流行的啟動(dòng)管理器是 Grub。
四、第四階段:操作系統(tǒng)
控制權(quán)轉(zhuǎn)交給操作系統(tǒng)后,操作系統(tǒng)的內(nèi)核首先被載入內(nèi)存。
以 Linux 系統(tǒng)為例,先載入 /boot 目錄下面的 kernel。內(nèi)核加載成功后,第一個(gè)運(yùn)行的程序是 /sbin/init。它根據(jù)配置文件(Debian 系統(tǒng)是 /etc/initab)產(chǎn)生 init 進(jìn)程。這是 Linux 啟動(dòng)后的第一個(gè)進(jìn)程,pid 進(jìn)程編號(hào)為 1,其他進(jìn)程都是它的后代。
然后,init 線程加載系統(tǒng)的各個(gè)模塊,比如窗口程序和網(wǎng)絡(luò)程序,直至執(zhí)行 /bin/login 程序,跳出登錄界面,等待用戶輸入用戶名和密碼。
至此,全部啟動(dòng)過(guò)程完成。
(完)
Linux 的啟動(dòng)流程
作者: 阮一峰
日期: 2013 年 8 月 17 日
上文《計(jì)算機(jī)是如何啟動(dòng)的?》探討 BIOS 和主引導(dǎo)記錄的作用,但不涉及操作系統(tǒng),只與主板的板載程序有關(guān)。
這里接著往下寫(xiě),探討操作系統(tǒng)接管硬件以后發(fā)生的事情,也就是操作系統(tǒng)的啟動(dòng)流程。
這個(gè)部分比較有意思。因?yàn)樵?BIOS 階段,計(jì)算機(jī)的行為基本上被寫(xiě)死了,程序員可以做的事情并不多;但是,一旦進(jìn)入操作系統(tǒng),程序員幾乎可以定制所有方面。所以,這個(gè)部分與程序員的關(guān)系更密切。
我主要關(guān)心的是 Linux 操作系統(tǒng),它是目前服務(wù)器端的主流操作系統(tǒng)。下面的內(nèi)容針對(duì)的是 Debian 發(fā)行版,因?yàn)槲覍?duì)其他發(fā)行版不夠熟悉。
第一步、加載內(nèi)核
操作系統(tǒng)接管硬件以后,首先讀入 /boot
目錄下的內(nèi)核文件。
以我的電腦為例,/boot 目錄下面大概是這樣一些文件:
$ ls /bootconfig-3.2.0-3-amd64 config-3.2.0-4-amd64 grub initrd.img-3.2.0-3-amd64 initrd.img-3.2.0-4-amd64 System.map-3.2.0-3-amd64 System.map-3.2.0-4-amd64 vmlinuz-3.2.0-3-amd64 vmlinuz-3.2.0-4-amd64
第二步、啟動(dòng)初始化進(jìn)程
內(nèi)核文件加載以后,就開(kāi)始運(yùn)行第一個(gè)程序 /sbin/init,它的作用是初始化系統(tǒng)環(huán)境。
由于 init 是第一個(gè)運(yùn)行的程序,它的進(jìn)程編號(hào)(pid)就是 1。其他所有進(jìn)程都從它衍生,都是它的子進(jìn)程。
第三步、確定運(yùn)行級(jí)別
許多程序需要開(kāi)機(jī)啟動(dòng)。它們?cè)?Windows 叫做 “服務(wù)”(service),在 Linux 就叫做 “[守護(hù)進(jìn)程](https://zh.wikipedia.org/wiki/ 守護(hù)進(jìn)程)”(daemon)。
init 進(jìn)程的一大任務(wù),就是去運(yùn)行這些開(kāi)機(jī)啟動(dòng)的程序。但是,不同的場(chǎng)合需要啟動(dòng)不同的程序,比如用作服務(wù)器時(shí),需要啟動(dòng) Apache,用作桌面就不需要。Linux 允許為不同的場(chǎng)合,分配不同的開(kāi)機(jī)啟動(dòng)程序,這就叫做 “[運(yùn)行級(jí)別](https://zh.wikipedia.org/wiki/ 運(yùn)行級(jí)別)”(runlevel)。也就是說(shuō),啟動(dòng)時(shí)根據(jù) “運(yùn)行級(jí)別”,確定要運(yùn)行哪些程序。
Linux 預(yù)置七種運(yùn)行級(jí)別(0-6)。一般來(lái)說(shuō),0 是關(guān)機(jī),1 是單用戶模式(也就是維護(hù)模式),6 是重啟。運(yùn)行級(jí)別 2-5,各個(gè)發(fā)行版不太一樣,對(duì)于 Debian 來(lái)說(shuō),都是同樣的多用戶模式(也就是正常模式)。
init 進(jìn)程首先讀取文件 /etc/inittab,它是運(yùn)行級(jí)別的設(shè)置文件。如果你打開(kāi)它,可以看到第一行是這樣的:
id:2:initdefault:
initdefault 的值是 2,表明系統(tǒng)啟動(dòng)時(shí)的運(yùn)行級(jí)別為 2。如果需要指定其他級(jí)別,可以手動(dòng)修改這個(gè)值。
那么,運(yùn)行級(jí)別 2 有些什么程序呢,系統(tǒng)怎么知道每個(gè)級(jí)別應(yīng)該加載哪些程序呢?… 回答是每個(gè)運(yùn)行級(jí)別在 /etc 目錄下面,都有一個(gè)對(duì)應(yīng)的子目錄,指定要加載的程序。
/etc/rc0.d /etc/rc1.d /etc/rc2.d /etc/rc3.d /etc/rc4.d /etc/rc5.d /etc/rc6.d
上面目錄名中的 “rc”,表示 run command(運(yùn)行程序),最后的 d 表示 directory(目錄)。下面讓我們看看 /etc/rc2.d 目錄中到底指定了哪些程序。
$ ls /etc/rc2.dREADME S01motd S13rpcbind S14nfs-common S16binfmt-support S16rsyslog S16sudo S17apache2 S18acpid ...
可以看到,除了第一個(gè)文件 README 以外,其他文件名都是 “字母 S + 兩位數(shù)字 + 程序名” 的形式。字母 S 表示 Start,也就是啟動(dòng)的意思(啟動(dòng)腳本的運(yùn)行參數(shù)為 start),如果這個(gè)位置是字母 K,就代表 Kill(關(guān)閉),即如果從其他運(yùn)行級(jí)別切換過(guò)來(lái),需要關(guān)閉的程序(啟動(dòng)腳本的運(yùn)行參數(shù)為 stop)。后面的兩位數(shù)字表示處理順序,數(shù)字越小越早處理,所以第一個(gè)啟動(dòng)的程序是 motd,然后是 rpcbing、nfs… 數(shù)字相同時(shí),則按照程序名的字母順序啟動(dòng),所以 rsyslog 會(huì)先于 sudo 啟動(dòng)。
這個(gè)目錄里的所有文件(除了 README),就是啟動(dòng)時(shí)要加載的程序。如果想增加或刪除某些程序,不建議手動(dòng)修改 /etc/rcN.d 目錄,最好是用一些專門(mén)命令進(jìn)行管理。
參考這里:
-
Manage Linux init or startup scripts | Debian Admin Posted on December 7, 2006 by ruchi
https://www.debianadmin.com/manage-linux-init-or-startup-scripts.html -
Remove Unwanted Startup Files or Services | Debian Admin Posted on September 12, 2006 by ruchi
https://www.debianadmin.com/remove-unwanted-startup-files-or-services-in-debian.html
第四步、加載開(kāi)機(jī)啟動(dòng)程序
前面提到,七種預(yù)設(shè)的 “運(yùn)行級(jí)別” 各自有一個(gè)目錄,存放需要開(kāi)機(jī)啟動(dòng)的程序。不難想到,如果多個(gè) “運(yùn)行級(jí)別” 需要啟動(dòng)同一個(gè)程序,那么這個(gè)程序的啟動(dòng)腳本,就會(huì)在每一個(gè)目錄里都有一個(gè)拷貝。這樣會(huì)造成管理上的困擾:如果要修改啟動(dòng)腳本,豈不是每個(gè)目錄都要改一遍?
Linux 的解決辦法,就是七個(gè) /etc/rcN.d 目錄里列出的程序,都設(shè)為鏈接文件,指向另外一個(gè)目錄 /etc/init.d ,真正的啟動(dòng)腳本都統(tǒng)一放在這個(gè)目錄中。init 進(jìn)程逐一加載開(kāi)機(jī)啟動(dòng)程序,其實(shí)就是運(yùn)行這個(gè)目錄里的啟動(dòng)腳本。
下面就是鏈接文件真正的指向。
$ ls -l /etc/rc2.dREADME S01motd -> ../init.d/motd S13rpcbind -> ../init.d/rpcbind S14nfs-common -> ../init.d/nfs-common S16binfmt-support -> ../init.d/binfmt-support S16rsyslog -> ../init.d/rsyslog S16sudo -> ../init.d/sudo S17apache2 -> ../init.d/apache2 S18acpid -> ../init.d/acpid ...
這樣做的另一個(gè)好處,就是如果你要手動(dòng)關(guān)閉或重啟某個(gè)進(jìn)程,直接到目錄 /etc/init.d 中尋找啟動(dòng)腳本即可。比如,我要重啟 Apache 服務(wù)器,就運(yùn)行下面的命令:
$ sudo /etc/init.d/apache2 restart
/etc/init.d 這個(gè)目錄名最后一個(gè)字母 d,是 directory 的意思,表示這是一個(gè)目錄,用來(lái)與程序 /etc/init 區(qū)分。
第五步、用戶登錄
開(kāi)機(jī)啟動(dòng)程序加載完畢以后,就要讓用戶登錄了。
一般來(lái)說(shuō),用戶的登錄方式有三種:
(1)命令行登錄
?
(2)ssh 登錄
?
(3)圖形界面登錄
這三種情況,都有自己的方式對(duì)用戶進(jìn)行認(rèn)證。
(1)命令行登錄:init 進(jìn)程調(diào)用 getty 程序(意為 get teletype),讓用戶輸入用戶名和密碼。輸入完成后,再調(diào)用 login 程序,核對(duì)密碼(Debian 還會(huì)再多運(yùn)行一個(gè)身份核對(duì)程序 /etc/pam.d/login
)。如果密碼正確,就從文件 /etc/passwd 讀取該用戶指定的 shell,然后啟動(dòng)這個(gè) shell。
(2)ssh 登錄:這時(shí)系統(tǒng)調(diào)用 sshd 程序(Debian 還會(huì)再運(yùn)行 /etc/pam.d/ssh ),取代 getty 和 login,然后啟動(dòng) shell。
(3)圖形界面登錄:init 進(jìn)程調(diào)用顯示管理器,Gnome 圖形界面對(duì)應(yīng)的顯示管理器為 gdm(GNOME Display Manager),然后用戶輸入用戶名和密碼。如果密碼正確,就讀取 /etc/gdm3/Xsession,啟動(dòng)用戶的會(huì)話。
第六步、進(jìn)入 login shell
所謂 shell,簡(jiǎn)單說(shuō)就是命令行界面,讓用戶可以直接與操作系統(tǒng)對(duì)話。用戶登錄時(shí)打開(kāi)的 shell,就叫做 login shell。
Debian 默認(rèn)的 shell 是 Bash,它會(huì)讀入一系列的配置文件。上一步的三種情況,在這一步的處理,也存在差異。
(1)命令行登錄:首先讀入 /etc/profile,這是對(duì)所有用戶都有效的配置;然后依次尋找下面三個(gè)文件,這是針對(duì)當(dāng)前用戶的配置。
~/.bash_profile ~/.bash_login ~/.profile
需要注意的是,這三個(gè)文件只要有一個(gè)存在,就不再讀入后面的文件了。比如,要是~/.bash_profile 存在,就不會(huì)再讀入后面兩個(gè)文件了。
(2)ssh 登錄:與第一種情況完全相同。
(3)圖形界面登錄:只加載 /etc/profile 和~/.profile。也就是說(shuō),~/.bash_profile 不管有沒(méi)有,都不會(huì)運(yùn)行。
第七步,打開(kāi) non-login shell
老實(shí)說(shuō),上一步完成以后,Linux 的啟動(dòng)過(guò)程就算結(jié)束了,用戶已經(jīng)可以看到命令行提示符或者圖形界面了。但是,為了內(nèi)容的完整,必須再介紹一下這一步。
用戶進(jìn)入操作系統(tǒng)以后,常常會(huì)再手動(dòng)開(kāi)啟一個(gè) shell。這個(gè) shell 就叫做 non-login shell,意思是它不同于登錄時(shí)出現(xiàn)的那個(gè) shell,不讀取 /etc/profile 和.profile 等配置文件。
non-login shell 的重要性,不僅在于它是用戶最常接觸的那個(gè) shell,還在于它會(huì)讀入用戶自己的 bash 配置文件~/.bashrc。大多數(shù)時(shí)候,我們對(duì)于 bash 的定制,都是寫(xiě)在這個(gè)文件里面的。
你也許會(huì)問(wèn),要是不進(jìn)入 non-login shell,豈不是.bashrc 就不會(huì)運(yùn)行了,因此 bash 也就不能完成定制了?事實(shí)上,Debian 已經(jīng)考慮到這個(gè)問(wèn)題了,請(qǐng)打開(kāi)文件~/.profile,可以看到下面的代碼:
if [ -n "$BASH_VERSION" ]; thenif [ -f "$HOME/.bashrc" ]; then. "$HOME/.bashrc"fi fi
上面代碼先判斷變量 $BASH_VERSION 是否有值,然后判斷主目錄下是否存在 .bashrc 文件,如果存在就運(yùn)行該文件。第三行開(kāi)頭的那個(gè)點(diǎn),是 source 命令的簡(jiǎn)寫(xiě)形式,表示運(yùn)行某個(gè)文件,寫(xiě)成 “source ~/.bashrc” 也是可以的。
因此,只要運(yùn)行~/.profile 文件,~/.bashrc 文件就會(huì)連帶運(yùn)行。但是上一節(jié)的第一種情況提到過(guò),如果存在~/.bash_profile 文件,那么有可能不會(huì)運(yùn)行~/.profile 文件。解決這個(gè)問(wèn)題很簡(jiǎn)單,把下面代碼寫(xiě)入.bash_profile 就行了。
if [ -f ~/.profile ]; then. ~/.profile fi
這樣一來(lái),不管是哪種情況,.bashrc 都會(huì)執(zhí)行,用戶的設(shè)置可以放心地都寫(xiě)入這個(gè)文件了。
Bash 的設(shè)置之所以如此繁瑣,是由于歷史原因造成的。早期的時(shí)候,計(jì)算機(jī)運(yùn)行速度很慢,載入配置文件需要很長(zhǎng)時(shí)間,Bash 的作者只好把配置文件分成了幾個(gè)部分,階段性載入。系統(tǒng)的通用設(shè)置放在 /etc/profile,用戶個(gè)人的、需要被所有子進(jìn)程繼承的設(shè)置放在.profile,不需要被繼承的設(shè)置放在.bashrc。
順便提一下,除了 Linux 以外, Mac OS X 使用的 shell 也是 Bash。但是,它只加載.bash_profile,然后在.bash_profile 里面調(diào)用.bashrc。而且,不管是 ssh 登錄,還是在圖形界面里啟動(dòng) shell 窗口,都是如此。
參考鏈接
[1] Debian Wiki, Environment Variables
[2] Debian Wiki, Dot Files
[3] Debian Administration, An introduction to run-levels
[4] Debian Admin,Debian and Ubuntu Linux Run Levels
[5] Linux Information Project (LINFO), Runlevel Definition
[6] LinuxQuestions.org, What are run levels?
[7] Dalton Hubble, Bash Configurations Demystified
(完)
via:
-
固件的隱藏世界:探索計(jì)算機(jī)的啟動(dòng)過(guò)程 Aleksandr Goncharov , 2023/04/21
https://hackernoon.com/lang/zh/固件的隱藏世界探索您的計(jì)算機(jī)啟動(dòng)過(guò)程 -
固件秘密之旅:從 BIOS/UEFI 到 OS Aleksandr Goncharov ,2024/08/22
https://hackernoon.com/lang/zh/從-biosuefi-到-os-的固件秘密之旅 -
計(jì)算機(jī)是如何啟動(dòng)的? - 阮一峰的網(wǎng)絡(luò)日志 作者: 阮一峰 日期: 2013 年 2 月 16 日
https://www.ruanyifeng.com/blog/2013/02/booting.html -
Linux 的啟動(dòng)流程 - 阮一峰的網(wǎng)絡(luò)日志 作者: 阮一峰 日期: 2013 年 8 月 17 日
https://www.ruanyifeng.com/blog/2013/08/linux_boot_process.html