電子產(chǎn)業(yè)一站式賦能平臺

PCB聯(lián)盟網(wǎng)

搜索
查看: 138|回復(fù): 0
收起左側(cè)

從開機(jī)到第一行l(wèi)inux內(nèi)核代碼執(zhí)行之間的全部過程

[復(fù)制鏈接]

317

主題

317

帖子

3149

積分

四級會員

Rank: 4

積分
3149
跳轉(zhuǎn)到指定樓層
樓主
發(fā)表于 2024-11-28 12:09:00 | 只看該作者 |只看大圖 回帖獎勵 |倒序瀏覽 |閱讀模式
0x00 聲明
因為微信公眾號不能引用外部鏈接,所以為了更好的閱讀體驗,推薦到我的網(wǎng)站:https://ytcoode.io 閱讀這篇文章。

0x01 全景圖
本篇文章會根據(jù)下面這張全景圖,來講解從開機(jī)到第一行l(wèi)inux內(nèi)核代碼執(zhí)行,之間的全部過程。



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變量:




當(dāng)然,我們也可以使用這個命令,來添加/修改/刪除這些uefi變量,其實就是在修改uefi boot
    manager的啟動邏輯。

另外,我們還可以通過 efivar 命令,來查看或修改所有的uefi變量:




因為機(jī)器上uefi變量非常多,所以這里只展示了前20條,大家如果有興趣的話,可以在自己機(jī)器上試一下。

最后再說一下,uefi boot manager選擇要執(zhí)行的uefi程序這一步,用戶是可以介入的。

我們在電腦開機(jī)后,先進(jìn)入到uefi的配置界面:




然后在這里,就可以選擇你想要執(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)圖
回復(fù)

使用道具 舉報

發(fā)表回復(fù)

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規(guī)則


聯(lián)系客服 關(guān)注微信 下載APP 返回頂部 返回列表