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