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

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

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

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

[復(fù)制鏈接]

317

主題

317

帖子

3149

積分

四級(jí)會(huì)員

Rank: 4

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

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



0x02 從開(kāi)機(jī)到boot loader
電腦開(kāi)機(jī)后,內(nèi)嵌到主板上的uefi系統(tǒng)固件就會(huì)開(kāi)始執(zhí)行。

固件的英文是firmware, 它是一種介于軟件software和硬件hardware之間的,內(nèi)嵌到硬件上的軟件。

uefi固件開(kāi)始執(zhí)行后,會(huì)先檢測(cè)并初始化系統(tǒng)硬件,然后在它內(nèi)部一個(gè)叫做boot manager的組件就開(kāi)始執(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í)行的。

不過(guò)通常情況下我們不會(huì)這么做,我們一般會(huì)在uefi和linux內(nèi)核之間,添加一個(gè)boot loader,然后讓boot
    loader啟動(dòng)linux內(nèi)核。

這樣做的好處是,我們可以非常方便的配置要傳遞給內(nèi)核的initrd文件,以及各種參數(shù)等。

總之,增加一個(gè)boot loader層,給我們帶來(lái)了更多的靈活性。

現(xiàn)在主流的boot loader有兩個(gè),一個(gè)是grub,一個(gè)是systemd-boot。

grub雖然功能更強(qiáng)大些,但它配置方式太復(fù)雜了,所以對(duì)于日常使用,我更推薦功能足夠但配置非常簡(jiǎn)單的systemd-boot。

而且systemd-boot是被集成到了systemd里的,也就是說(shuō),只要你機(jī)器上裝了systemd,systemd-boot也就自動(dòng)裝好了,是可以直接使用的。

因?yàn)楝F(xiàn)在主流的linux發(fā)行版都使用systemd作為init程序,所以默認(rèn)情況下,systemd都是已經(jīng)安裝了的,所以systemd-boot也是已經(jīng)安裝了的。

另外說(shuō)一句,systemd真的是一個(gè)大而全的重型武器,非常好用。

鑒于systemd-boot的各種優(yōu)點(diǎn),本文就以systemd-boot作為boot loader,來(lái)講解啟動(dòng)流程。

systemd-boot作為一個(gè)boot
    loader,是要被uefi啟動(dòng)的,所以它也是以PE32+文件格式存儲(chǔ)的,即它也是一個(gè)uefi應(yīng)用程序。

不過(guò)對(duì)于uefi的boot
    manager來(lái)說(shuō),它并不管它要啟動(dòng)的應(yīng)用程序是什么,它只要求被啟動(dòng)的應(yīng)用程序,是一個(gè)符合uefi規(guī)范的應(yīng)用程序就好。

下面我們來(lái)講下,uefi中boot manager的執(zhí)行邏輯。

在uefi空間里面,除了有uefi固件代碼,還有很多的uefi變量。

每一個(gè)uefi變量就是一個(gè)類似于硬盤(pán)的存儲(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變量名。

就像文章最開(kāi)始那張圖里展示的,BootOrder變量里存儲(chǔ)的其實(shí)是 Boot0004, Boot0003, Boot001B, Boot0017
    等uefi變量。

而這些以Boot作為前綴的uefi變量,它們里面才存儲(chǔ)了要執(zhí)行的uefi程序的文件路徑。

又比如文章最開(kāi)始那張圖里展示的,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程序。

如果這兩步都沒(méi)有發(fā)生錯(cuò)誤,說(shuō)明這個(gè)uefi程序啟動(dòng)成功。

此時(shí),控制流就會(huì)跳轉(zhuǎn)到這個(gè)uefi程序的入口函數(shù),然后開(kāi)始執(zhí)行這個(gè)uefi程序里面的相關(guān)代碼。

至此,uefi中boot manager的生命周期也就結(jié)束了。

最后,我們?cè)賮?lái)實(shí)際查看下真實(shí)機(jī)器上的這些uefi變量。

我們可以使用 efibootmgr 命令,來(lái)查看所有和啟動(dòng)相關(guān)的uefi變量:




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

另外,我們還可以通過(guò) efivar 命令,來(lái)查看或修改所有的uefi變量:




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

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

我們?cè)陔娔X開(kāi)機(jī)后,先進(jìn)入到uefi的配置界面:




然后在這里,就可以選擇你想要執(zhí)行的uefi程序。

比如上圖中的第三項(xiàng),就是啟動(dòng)usb里的uefi程序。

我們一般用usb安裝操作系統(tǒng)時(shí),就是通過(guò)這種方式,來(lái)讓uefi啟動(dòng)usb里的iso鏡像文件的。

0x03 從boot loader到linux內(nèi)核
上文說(shuō)過(guò),boot loader我們選擇的是systemd-boot。

因?yàn)閟ystemd-boot是有 源碼 的,所以了解它內(nèi)部的運(yùn)行機(jī)制也相對(duì)較容易些。

systemd-boot作為uefi應(yīng)用程序的入口函數(shù)是 efi_main。

在它的內(nèi)部,主要做了以下幾件事,接下來(lái)我們就根據(jù)文章最開(kāi)始的全景圖來(lái)對(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)檫@一過(guò)程并不影響對(duì)systemd-boot啟動(dòng)流程的理解,所以就不詳細(xì)講了。

systemd-boot在獲得了一個(gè)啟動(dòng)項(xiàng)之后,就開(kāi)始嘗試運(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í),就可以通過(guò)這個(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ì)開(kāi)始嘗試執(zhí)行內(nèi)存根目錄下的init程序。

這個(gè)init程序其實(shí)還不是我們經(jīng)常說(shuō)的,真正意義上的init程序。

它只是linux內(nèi)核執(zhí)行的第一個(gè)用戶程序。

該init程序的作用,就是找到并掛載真正的根文件系統(tǒng),這個(gè)一般是在硬盤(pán)上,然后再把控制權(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è),在這里我們就不展開(kāi)講了,等后面講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í),就可以通過(guò)某些uefi函數(shù),來(lái)獲取這些內(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ù),來(lái)加載并啟動(dòng)uefi應(yīng)用程序的。

由此我們可以得知,systemd-boot不僅可以用來(lái)啟動(dòng)linux內(nèi)核,還可以用來(lái)啟動(dòng)任意的uefi應(yīng)用程序。

這也是為什么systemd-boot的官方文檔,把它稱為uefi boot manager,而非 boot loader 的原因。

不過(guò)我們主要是用systemd-boot加載linux內(nèi)核,所以為了便于大家理解,我們還是稱之為 boot loader。

另外我們還可以看到,使用uefi直接啟動(dòng)linux內(nèi)核,和使用systemd-boot間接啟動(dòng)內(nèi)核,它們之間是沒(méi)有本質(zhì)區(qū)別的,最終控制流都會(huì)跳轉(zhuǎn)到linux內(nèi)核作為uefi應(yīng)用的入口函數(shù),然后開(kāi)始執(zhí)行l(wèi)inux內(nèi)核的相關(guān)代碼。

至于linux內(nèi)核作為uefi應(yīng)用的入口函數(shù)是什么,這個(gè)我們?cè)趌inux內(nèi)核啟動(dòng)流程里再講。

0x04 樹(shù)形圖
因?yàn)閺拈_(kāi)機(jī)到第一行內(nèi)核代碼執(zhí)行這一過(guò)程,也算是linux內(nèi)核啟動(dòng)流程的一部分,所以這篇文章中講的各個(gè)步驟,在 linux內(nèi)核啟動(dòng)流程樹(shù)形圖 里也都有展示,大家可以前往看下。

end

一口Linux

關(guān)注,回復(fù)【1024】海量Linux資料贈(zèng)送
精彩文章合集
文章推薦
?【專輯】ARM?【專輯】粉絲問(wèn)答?【專輯】所有原創(chuàng)?【專輯】linux入門?【專輯】計(jì)算機(jī)網(wǎng)絡(luò)?【專輯】Linux驅(qū)動(dòng)?【干貨】嵌入式驅(qū)動(dòng)工程師學(xué)習(xí)路線?【干貨】Linux嵌入式所有知識(shí)點(diǎn)-思維導(dǎo)圖

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

本版積分規(guī)則


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