|
0x00 聲明
因為微信公眾號不能引用外部鏈接,所以為了更好的閱讀體驗,推薦到我的網(wǎng)站:https://ytcoode.io 閱讀這篇文章。
0x01 全景圖
本篇文章會根據(jù)下面這張全景圖,來講解從開機(jī)到第一行l(wèi)inux內(nèi)核代碼執(zhí)行,之間的全部過程。
jv1r25nk4mh64039713805.png (1.2 MB, 下載次數(shù): 2)
下載附件
保存到相冊
jv1r25nk4mh64039713805.png
2024-11-29 06:44 上傳
0x02 從開機(jī)到boot loader
電腦開機(jī)后,內(nèi)嵌到主板上的uefi系統(tǒng)固件就會開始執(zhí)行。
固件的英文是firmware, 它是一種介于軟件software和硬件hardware之間的,內(nèi)嵌到硬件上的軟件。
uefi固件開始執(zhí)行后,會先檢測并初始化系統(tǒng)硬件,然后在它內(nèi)部一個叫做boot manager的組件就開始執(zhí)行。
uefi boot manager的作用,就是尋找并啟動一個uefi應(yīng)用程序。
所謂uefi應(yīng)用程序,就是一個以PE32+格式存儲的程序文件。
PE32+文件格式,是 uefi規(guī)范中指定的,uefi應(yīng)用程序使用的存儲格式,它和Windows程序使用的 存儲格式 是一樣的。
另外,linux程序使用的存儲格式是 ELF,mac程序使用的存儲格式是Mach-O。
定義程序的存儲格式,是為了在執(zhí)行程序時,程序的執(zhí)行者,比如操作系統(tǒng),可以找到程序的代碼在哪里,數(shù)據(jù)在哪里。
綜上我們可知,任何程序只要是以PE32+文件格式存儲的,且符合uefi規(guī)范的,都可以被uefi直接執(zhí)行。
linux內(nèi)核默認(rèn)也被編譯成了PE32+文件格式,所以它也是可以被uefi直接執(zhí)行的。
不過通常情況下我們不會這么做,我們一般會在uefi和linux內(nèi)核之間,添加一個boot loader,然后讓boot
loader啟動linux內(nèi)核。
這樣做的好處是,我們可以非常方便的配置要傳遞給內(nèi)核的initrd文件,以及各種參數(shù)等。
總之,增加一個boot loader層,給我們帶來了更多的靈活性。
現(xiàn)在主流的boot loader有兩個,一個是grub,一個是systemd-boot。
grub雖然功能更強(qiáng)大些,但它配置方式太復(fù)雜了,所以對于日常使用,我更推薦功能足夠但配置非常簡單的systemd-boot。
而且systemd-boot是被集成到了systemd里的,也就是說,只要你機(jī)器上裝了systemd,systemd-boot也就自動裝好了,是可以直接使用的。
因為現(xiàn)在主流的linux發(fā)行版都使用systemd作為init程序,所以默認(rèn)情況下,systemd都是已經(jīng)安裝了的,所以systemd-boot也是已經(jīng)安裝了的。
另外說一句,systemd真的是一個大而全的重型武器,非常好用。
鑒于systemd-boot的各種優(yōu)點,本文就以systemd-boot作為boot loader,來講解啟動流程。
systemd-boot作為一個boot
loader,是要被uefi啟動的,所以它也是以PE32+文件格式存儲的,即它也是一個uefi應(yīng)用程序。
不過對于uefi的boot
manager來說,它并不管它要啟動的應(yīng)用程序是什么,它只要求被啟動的應(yīng)用程序,是一個符合uefi規(guī)范的應(yīng)用程序就好。
下面我們來講下,uefi中boot manager的執(zhí)行邏輯。
在uefi空間里面,除了有uefi固件代碼,還有很多的uefi變量。
每一個uefi變量就是一個類似于硬盤的存儲單元,即在斷電之后,變量里存儲的數(shù)據(jù)不會丟失。
uefi規(guī)范里面定義了 很多用于各種用途的變量。
其中有一個變量,就是和啟動相關(guān)的,它就是BootOrder。
BootOrder變量里存儲的,是一個可執(zhí)行的uefi程序列表。
uefi的boot
manager在運行期間,就是從BootOrder里獲取這些uefi程序,然后根據(jù)這些程序在BootOrder里的位置,依次嘗試執(zhí)行它們,直到有一個成功。
這其實就是uefi boot manager的主體邏輯。
另外要注意,BootOrder變量里并不是直接存儲各uefi程序的文件路徑的,它存儲的,其實是一些以Boot作為前綴的uefi變量名。
就像文章最開始那張圖里展示的,BootOrder變量里存儲的其實是 Boot0004, Boot0003, Boot001B, Boot0017
等uefi變量。
而這些以Boot作為前綴的uefi變量,它們里面才存儲了要執(zhí)行的uefi程序的文件路徑。
又比如文章最開始那張圖里展示的,Boot0004變量里存儲的uefi應(yīng)用程序所在路徑為
/boot/EFI/systemd/systemd-bootx64.efi,它指向的其實就是 systemd-boot。
uefi的boot manager在從BootOrder變量里挑選出一個合適的uefi程序后,它就會使用 EFI_BOOT_SERVICES.LoadImage() 函數(shù),將這個uefi程序加載到內(nèi)存, 然后再使用 EFI_BOOT_SERVICES.StartImage() 函數(shù),啟動這個uefi程序。
如果這兩步都沒有發(fā)生錯誤,說明這個uefi程序啟動成功。
此時,控制流就會跳轉(zhuǎn)到這個uefi程序的入口函數(shù),然后開始執(zhí)行這個uefi程序里面的相關(guān)代碼。
至此,uefi中boot manager的生命周期也就結(jié)束了。
最后,我們再來實際查看下真實機(jī)器上的這些uefi變量。
我們可以使用 efibootmgr 命令,來查看所有和啟動相關(guān)的uefi變量:
0acjhpaldk064039713905.png (587.72 KB, 下載次數(shù): 1)
下載附件
保存到相冊
0acjhpaldk064039713905.png
2024-11-29 06:44 上傳
當(dāng)然,我們也可以使用這個命令,來添加/修改/刪除這些uefi變量,其實就是在修改uefi boot
manager的啟動邏輯。
另外,我們還可以通過 efivar 命令,來查看或修改所有的uefi變量:
acnlydu5dnl64039714005.png (346.41 KB, 下載次數(shù): 4)
下載附件
保存到相冊
acnlydu5dnl64039714005.png
2024-11-29 06:44 上傳
因為機(jī)器上uefi變量非常多,所以這里只展示了前20條,大家如果有興趣的話,可以在自己機(jī)器上試一下。
最后再說一下,uefi boot manager選擇要執(zhí)行的uefi程序這一步,用戶是可以介入的。
我們在電腦開機(jī)后,先進(jìn)入到uefi的配置界面:
fwdblsp4uep64039714105.jpg (382.44 KB, 下載次數(shù): 3)
下載附件
保存到相冊
fwdblsp4uep64039714105.jpg
2024-11-29 06:44 上傳
然后在這里,就可以選擇你想要執(zhí)行的uefi程序。
比如上圖中的第三項,就是啟動usb里的uefi程序。
我們一般用usb安裝操作系統(tǒng)時,就是通過這種方式,來讓uefi啟動usb里的iso鏡像文件的。
0x03 從boot loader到linux內(nèi)核
上文說過,boot loader我們選擇的是systemd-boot。
因為systemd-boot是有 源碼 的,所以了解它內(nèi)部的運行機(jī)制也相對較容易些。
systemd-boot作為uefi應(yīng)用程序的入口函數(shù)是 efi_main。
在它的內(nèi)部,主要做了以下幾件事,接下來我們就根據(jù)文章最開始的全景圖來對照講解。
它先從 /boot/loader/entries/ 目錄里加載所有以 .conf 結(jié)尾的文件,每個文件是一個啟動項。
然后再從 /boot/loader/loader.conf 全局配置里,找到默認(rèn)啟動項。
看全景圖,/boot/loader/loader.conf 文件里配置的默認(rèn)啟動項是 nixos-generation-292.conf。
其實在這一步之后,systemd-boot還會顯示一個菜單,讓用戶可以選擇其他啟動項。
但因為這一過程并不影響對systemd-boot啟動流程的理解,所以就不詳細(xì)講了。
systemd-boot在獲得了一個啟動項之后,就開始嘗試運行該啟動項里配置的linux內(nèi)核。
但在此之前,它還要做一些準(zhǔn)備工作。
比如,它會先把在啟動項 nixos-generation-292.conf
里配置的initrd文件加載到內(nèi)存,然后再把內(nèi)存里的initrd數(shù)據(jù)綁定到uefi空間的一個固定設(shè)備路徑上。
這樣后續(xù)內(nèi)核啟動時,就可以通過這個uefi設(shè)備路徑,找到對應(yīng)的initrd。
initrd是一個打包文件,linux內(nèi)核在啟動時,會把它解壓到內(nèi)存根文件系統(tǒng)里的根目錄下。
然后,等linux內(nèi)核都初始化完畢之后,內(nèi)核就會開始嘗試執(zhí)行內(nèi)存根目錄下的init程序。
這個init程序其實還不是我們經(jīng)常說的,真正意義上的init程序。
它只是linux內(nèi)核執(zhí)行的第一個用戶程序。
該init程序的作用,就是找到并掛載真正的根文件系統(tǒng),這個一般是在硬盤上,然后再把控制權(quán)限轉(zhuǎn)交給真正根文件系統(tǒng)下的init程序。
第一個init程序,也就是initrd里的init程序,一般是shell腳本,當(dāng)然也可以是systemd。
第二個init程序,也就是真正根文件系統(tǒng)下的init程序,一般是systemd。
至于init程序為什么要分成兩個,在這里我們就不展開講了,等后面講linux內(nèi)核啟動流程時,再詳細(xì)講。
我們再回到systemd-boot的啟動流程。
在加載完并初始化好initrd之后,systemd-boot就會使用uefi里的 EFI_BOOT_SERVICES.LoadImage() 函數(shù),將啟動項 nixos-generation-292.conf 里配置的linux內(nèi)核加載到內(nèi)存。
然后再將啟動項 nixos-generation-292.conf
里配置的內(nèi)核參數(shù),賦值到剛加載的內(nèi)核鏡像的對應(yīng)字段上,這樣內(nèi)核在啟動時,就可以通過某些uefi函數(shù),來獲取這些內(nèi)核參數(shù)了。
最后,systemd-boot再使用uefi里的 EFI_BOOT_SERVICES.StartImage() 函數(shù),啟動這個內(nèi)核鏡像。
至此,控制流就會跳轉(zhuǎn)到linux內(nèi)核作為uefi應(yīng)用的入口函數(shù),systemd-boot的生命周期也就結(jié)束了。
從上文我們可以看到,systemd-boot的啟動流程和uefi的啟動流程是很類似的,它們都是使用uefi中boot
services里的 LoadImage 和 StartImage 函數(shù),來加載并啟動uefi應(yīng)用程序的。
由此我們可以得知,systemd-boot不僅可以用來啟動linux內(nèi)核,還可以用來啟動任意的uefi應(yīng)用程序。
這也是為什么systemd-boot的官方文檔,把它稱為uefi boot manager,而非 boot loader 的原因。
不過我們主要是用systemd-boot加載linux內(nèi)核,所以為了便于大家理解,我們還是稱之為 boot loader。
另外我們還可以看到,使用uefi直接啟動linux內(nèi)核,和使用systemd-boot間接啟動內(nèi)核,它們之間是沒有本質(zhì)區(qū)別的,最終控制流都會跳轉(zhuǎn)到linux內(nèi)核作為uefi應(yīng)用的入口函數(shù),然后開始執(zhí)行l(wèi)inux內(nèi)核的相關(guān)代碼。
至于linux內(nèi)核作為uefi應(yīng)用的入口函數(shù)是什么,這個我們在linux內(nèi)核啟動流程里再講。
0x04 樹形圖
因為從開機(jī)到第一行內(nèi)核代碼執(zhí)行這一過程,也算是linux內(nèi)核啟動流程的一部分,所以這篇文章中講的各個步驟,在 linux內(nèi)核啟動流程樹形圖 里也都有展示,大家可以前往看下。
end
一口Linux
關(guān)注,回復(fù)【1024】海量Linux資料贈送
精彩文章合集
文章推薦
?【專輯】ARM?【專輯】粉絲問答?【專輯】所有原創(chuàng)?【專輯】linux入門?【專輯】計算機(jī)網(wǎng)絡(luò)?【專輯】Linux驅(qū)動?【干貨】嵌入式驅(qū)動工程師學(xué)習(xí)路線?【干貨】Linux嵌入式所有知識點-思維導(dǎo)圖 |
|