從無盤啟動看 Linux 啟動原理


0. 故事的開始
0.1 為什么和做什么
最近家里買了對音響,我需要一個數(shù)字播放器。一凡研究后我看上了 volumio(https://volumio.org/) 這是一個基于 Debian 二次開發(fā)的 HIFI 播放器系統(tǒng),可以運(yùn)行下 x86 和樹莓派上。
我打算讓 volumio 運(yùn)行在我 2009 年購買的老爺機(jī)筆記本上,也讓它發(fā)揮一點(diǎn)余溫?zé)帷U2僮魇菍?volumio 的系統(tǒng)鏡像刷到 U 盤上,連接電腦后使用 U 盤啟動系統(tǒng)即可。但是家里沒有找到合適的 U 盤(窮~~),加上前段時間聽了同事關(guān)于 linux 內(nèi)核的分享,感慨自己對系統(tǒng)的理解不夠。因此我決定使用無盤啟動 volumio 順便研究一下 linux 啟動原理。
目標(biāo):無盤啟動 volumio 系統(tǒng)
0.2 方案
正常 Linux 啟動流程大體如下:
BIOS 啟動,完成自檢,選擇啟動硬件 如果是磁盤系統(tǒng)讀取 MBR 從 MBR 指示,找到 GRUB 所在分區(qū),加載 GRUB 顯示菜單 加載 Linux 內(nèi)核到內(nèi)存中 執(zhí)行 INIT 程序 進(jìn)入用戶界面

由于我需要從網(wǎng)絡(luò)啟動,過程會變得復(fù)雜一些,主要變化如下
在 MBR 引導(dǎo)前,需要執(zhí)行一系列的 PXE 流程,目的是掛載 iscsi 磁盤。 在加載 linux 內(nèi)核后,由于之前 iPXE 固件已經(jīng)退出,還需要再次掛載 iscsi 磁盤。

0.3 準(zhǔn)備工作
無盤啟動并不是說完全沒有磁盤,只是客戶端本身沒有磁盤,我們需要在遠(yuǎn)端給機(jī)器提供一種文件存儲和磁盤共享的方案。我這里選擇的是 iscsi 共享,相比于 NFS 和 samba 共享,它更底層,對系統(tǒng)的兼容性更好。
iSCSI 利用了 TCP/IP 作為溝通的渠道。透過兩部計算機(jī)之間利用 iSCSI 的協(xié)議來交換 SCSI 命令,讓計算機(jī)可以透過高速的局域網(wǎng)集線來把 SAN 模擬成為本地的儲存設(shè)備。
關(guān)于 iscsi 的配置不是本文重點(diǎn),這里就不詳細(xì)描述了,要完成 iscsi 磁盤的掛載需要接信息。
iscsi 服務(wù)器地址:我這里是 nas 服務(wù)的地址 192.168.3.5
target 名稱:這個是服務(wù)端用來區(qū)分目標(biāo)的,通常一個 target 服務(wù)一個客戶端,并關(guān)聯(lián)一塊共享存儲,例如:iqn.2005-10.org.freenas.ctl:yong-pc.volumio
initiator 名稱:這個是客戶端名稱,用來告訴服務(wù)端誰來請求了。
1 BIOS 和 UEFI
準(zhǔn)備工作做完,我們先來了解一下計算機(jī)的啟動原理,這里就要說到 BIOS 和 UEFI,他們是計算機(jī)按下電源后最先被執(zhí)行的程序。
1.1 BIOS (Basic Input/Output System)
上個世紀(jì) 70 年代初,"只讀內(nèi)存"(read-only memory,縮寫為 ROM)發(fā)明,開機(jī)程序被刷入 ROM 芯片,計算機(jī)通電后,第一件事就是讀取它。這塊芯片里的程序叫做"基本輸入輸出系統(tǒng)"(Basic Input/Output System),簡稱為 BIOS。

BIOS 程序首先檢查,計算機(jī)硬件能否滿足運(yùn)行的基本條件,這叫做"硬件自檢"(Power-On Self-Test),縮寫為 POST。硬件自檢完成后,BIOS 把控制權(quán)轉(zhuǎn)交給下一階段的啟動程序。
這時,BIOS 需要知道,"下一階段的啟動程序"具體存放在哪一個設(shè)備。也就是說,BIOS 需要有一個外部儲存設(shè)備的排序,排在前面的設(shè)備就是優(yōu)先轉(zhuǎn)交控制權(quán)的設(shè)備。這種排序叫做"啟動順序"(Boot Sequence)。

1.2 UEFI (Unified Extensible Firmware Interface)
不知道大家是否發(fā)現(xiàn),這些年已經(jīng)很難看到 BIOS 的身影了。
ROM 的存儲能力有限,BIOS 能驅(qū)動的硬件類型和數(shù)量大大受限。導(dǎo)致大量新硬件無法在 PC 啟動時被加載。最明顯就是你無法在 BIOS 時使用鼠標(biāo)。此外 BIOS 的代碼歷史悠久難以維護(hù)。
在 2005 年年中時候,包括 BIOS 供應(yīng)商、OS 供應(yīng)商、系統(tǒng)制造商以及芯片生產(chǎn)公司在內(nèi)的行業(yè)參與者統(tǒng)一建立了統(tǒng)一的 EFI 聯(lián)盟(UEFI,Unified Extensible Firmware Interface)并在 2006 年一月發(fā)行了 UEFI 規(guī)范 2.0。
從此你可以愉快的在 PC 啟動初期使用鼠標(biāo),甚至像蘋果一樣加載網(wǎng)絡(luò),實現(xiàn)聯(lián)網(wǎng)下載并安裝操作系統(tǒng)。

UEFI 的啟動流程和 BIOS 的啟動流程不同,由于我 2009 年購買的老爺機(jī)還是 BIOS 結(jié)構(gòu),這里不詳細(xì)展開,簡單提一下。
系統(tǒng)開機(jī) - 上電自檢(Power On Self Test 或 POST)。 UEFI 固件被加載,并由它初始化啟動要用的硬件。 固件讀取其引導(dǎo)管理器以確定從何處(比如,從哪個硬盤及分區(qū))加載哪個 UEFI 應(yīng)用。 固件按照引導(dǎo)管理器中的啟動項目,加載 UEFI 應(yīng)用。 已啟動的 UEFI 應(yīng)用還可以啟動其他應(yīng)用(對應(yīng)于 UEFI shell 或 rEFInd 之類的引導(dǎo)管理器的情況)或者啟動內(nèi)核及 initramfs(對應(yīng)于 GRUB 之類引導(dǎo)器的情況),這取決于 UEFI 應(yīng)用的配置
2. PXE
回到我的 BIOS 老爺機(jī),上電自檢完成后 BIOS 按照設(shè)置的啟動順序應(yīng)該交棒磁盤,但是 但是 但是 這個機(jī)器沒有硬盤,也沒有插入 U 盤,找不到任何啟動設(shè)備的 BIOS 將控制權(quán)交給了網(wǎng)卡,BIOS 光榮退場進(jìn)入了 PXE 階段。
預(yù)啟動執(zhí)行環(huán)境(Preboot eXecution Environment,PXE,也被稱為預(yù)執(zhí)行環(huán)境) 提供了一種使用網(wǎng)絡(luò)接口啟動計算機(jī)的機(jī)制。這種機(jī)制讓計算機(jī)的啟動可以不依賴本地數(shù)據(jù)存儲設(shè)備(如硬盤)或本地已安裝的操作系統(tǒng)。

2.1 PXE 原理
Client 向 DHCP 發(fā)送 IP 地址請求消息,DHCP 返回 Client 的 IP 地址,同時將啟動文件(如:pxelinux.0)的位置信息(通常是 TFTP 路徑)一并傳送給 Client Client 向 TFTP 發(fā)送獲取啟動文件請求消息,TFTP 接收到消息之后再向 Client 發(fā)送啟動文件大小信息,試探 Client 是否滿意,當(dāng) TFTP 收到 Client 發(fā)回的同意大小信息之后,正式向 Client 發(fā)送啟動文件 Client 執(zhí)行接收文件 Client 向 TFTP 發(fā)送針對本機(jī)的配置信息文件請求,TFTP 將配置文件發(fā)回 Client,繼而 Client 根據(jù)配置文件執(zhí)行后續(xù)操作。 Client 會加載啟動文件,之后根據(jù)配置執(zhí)行動作。這里有多重方案進(jìn)行下一步操作。 可以直接通過 Http 協(xié)議獲取 Linux kernel 和 ramdisk 然后啟動 或者加載一塊 iscsi 磁盤,將 linux kernel 和 ramdisk 等信息放在 iscsi 磁盤中,走正常磁盤引導(dǎo)。我用的是這種方案

2.2 iPXE
上面說到了啟動文件,普通的 pxe 啟動文件功能有限,通常只能從 tftp 服務(wù)器上獲取文件,不支持 HTTP 協(xié)議和其他共享協(xié)議,更別說我們要支持的 iscsi 磁盤掛載了。這里推薦一個高端開源 pxe 啟動文件:iPXE(https://ipxe.org/)。它支持從 HTTP、iscsi SAN、 Fibre Channel SAN、AoE SAN 等多種方式啟動,甚至還支持無線網(wǎng)卡。此外它還可以定制一個啟動腳本和菜單。
iPXE 需要根據(jù)自己硬件對應(yīng)的平臺進(jìn)行編譯,編譯前需要搞清楚幾個要點(diǎn):
啟動方式:BIOS 或者 EFI 前面已經(jīng)說了。 平臺:X86 或 ARM,如果用樹莓派等產(chǎn)品就是 ARM,PC 是 x86 CPU 位:32 或 64,32 位機(jī)器只支持 32 位固件,64 位機(jī)器可以兼容 32 位和 64 位固件。注意:如果使用 64 位固件需要保證后續(xù)所有環(huán)節(jié)使用兼容 64 位的軟件,我就遇到了 SysLinux 不支持 64 位,導(dǎo)致卡死的問題。
使用如下命令編譯(更多細(xì)節(jié)見:https://ipxe.org/appnote/buildtargets):
git?clone?git://git.ipxe.org/ipxe.git
make?[platform]/[driver].[extension]
Platform 支持如下:按照上面說的啟動方式、平臺、CPU 情況選擇。
bin (alias for bin-i386-pcbios) bin-i386-pcbios bin-i386-efi bin-i386-linux bin-x86_64-efi bin-x86_64-linux bin-x86_64-pcbios bin-arm32-efi bin-arm64-efi
Driver:主要選擇支持的網(wǎng)卡驅(qū)動類型,一般選 ipxe(表示所有支持的網(wǎng)卡,但可能導(dǎo)致生成的啟動文件過大,如果過大可以酌情選其它)
Boot type:和啟動方式、啟動介質(zhì)有關(guān),參考下表:

編譯時添加 EMBED={腳本名稱} 可以關(guān)聯(lián)一個啟動腳本。推薦一個大佬做好的腳本 http://boot.netboot.xyz/menu.ipxe 可以直接使用。
我最終命令如下:
git?clone?git://git.ipxe.org/ipxe.git
cd?./ipxe/src
wget?http://boot.netboot.xyz/menu.ipxe
make?bin-i386-pcbios/ipxe.pxe?EMBED=menu.ipxe
完成之后在/data/ipxe/src/bin-i386-pcbios/ipxe.pxe 可以拿到最終的啟動文件。
2.3 DHCP、TFTP 配置
如何配置 DHCP 和 TFTP 服務(wù)器不是本文重點(diǎn),如果需要命令行方式配置可以參考這篇文章的前半部分https://blog.51cto.com/dyc2005/2068188
如今大部分高端路由器或開源路由器固件都內(nèi)置了 DHCP 和 TFTP 配置功能。我家的 LEDE 路由器配置界面如下。

TFTP 服務(wù)器根目錄:這個是啟動文件、配置文件存放的目錄路徑(是在路由器上的路徑,可以放在 u 盤掛上去,也可以直接放在路由器存儲的目錄) 網(wǎng)絡(luò)啟動鏡像:這是對客戶端下發(fā)的啟動文件名稱。(不同 CPU 架構(gòu),不同平臺的文件名不同)
拷貝之前編譯好的 ipxe.pxe 和 menu.ipxe 文件到/www/pxe/目錄下,并設(shè)置網(wǎng)絡(luò)啟動鏡像為:ipxe.pxe
配置正確,啟動后就可以看到如下選擇界面了:

3. 分區(qū):MBR 和 GPT
ipxe 完成使命后,正式交棒給磁盤,如果你是硬盤啟動,可以直接跳過第 2 部分,直接到這一步。這一階段系統(tǒng)需要從磁盤上找到啟動文件并加載。在說如何找到啟動文件前,先要說說硬盤是如何劃分區(qū)塊的,主要有兩大方式 MBR 和 GPT。我們先來聊一下機(jī)械硬盤的工作原理。
機(jī)械硬盤由堅硬金屬材料制成的涂以磁性介質(zhì)的盤片,盤片兩面稱為盤面或扇面。
假設(shè)磁頭不動,硬盤旋轉(zhuǎn),那么磁頭就會在磁盤表面畫出一個圓形軌跡并將之磁化,數(shù)據(jù)就保存在這些磁化區(qū)中,稱之為磁道,將每個磁道分段,一個弧段就是一個扇區(qū)。一個硬盤可以包含多個扇面,扇面同軸重疊放置,每個盤面磁道數(shù)相同,具有相同周長的磁道所形成的圓柱稱之為柱面,柱面數(shù)與磁道數(shù)相等。如下圖:

最初的尋址方式稱為 CHS,所謂 CHS 即柱面(cylinder),磁頭(header),扇區(qū)(sector),通過這三個變量描述磁盤地址。
3.1 MBR
說了這么多還是沒說明白到底計算機(jī)怎么從磁盤上找到引導(dǎo)程序。答案是:它被固定寫死在了 0 柱面,0 磁頭,1 扇區(qū)的位置通常是 512byte,這個位置被稱為主扇區(qū)(Master Boot Record, MBR)。
MBR 主要包含如下數(shù)據(jù):
主引導(dǎo)記錄(bootloader),負(fù)責(zé)從活動分區(qū)加載并運(yùn)行系統(tǒng)引導(dǎo)程序。446 字節(jié) 硬盤分區(qū)表項(DPT——disk partition table),由四個分區(qū)表項組成,負(fù)責(zé)記錄磁盤的分區(qū)情況。64 字節(jié)。 硬盤有效標(biāo)志(magic number),代表引導(dǎo)扇區(qū)結(jié)束,占用 2 字節(jié)。

Bootloader:這部分記錄了一段較小引導(dǎo)代碼,用于去啟動硬盤其他分區(qū)位置上更大的引導(dǎo)文件,例如 linux 操作系統(tǒng)的 grub 目錄。

我們知道一個硬盤的每個分區(qū)的第一個扇區(qū)叫做 boot sector,這個扇區(qū)存放的就是操作系統(tǒng)的 loader。如上圖,第一個分區(qū)的 boot sector 存放著 windows 的 loader,第二個分區(qū)放著 Linux 的 loader,第三個第四個由于沒有安裝操作系統(tǒng)所以空著。至于 MBR 的 bootloader 是干嘛呢, bootloader 有三個功能:
提供選單:讓用戶選擇進(jìn)入哪個系統(tǒng)。 讀取內(nèi)核文件:默認(rèn)啟動的 loader 會被拷貝一份到 MBR 中,這樣就可以直接讀取內(nèi)核了,圖中 1 部分 轉(zhuǎn)交給其他 loader:圖中 2,3 部分
Disk Partition table:這一部分 64 字節(jié)大小被均分為 4 份,每份大小 16 字節(jié),每當(dāng)我們在硬盤上創(chuàng)建出一個新的主分區(qū)或者擴(kuò)展分區(qū)時,便會占用 1 個 16 字節(jié)的大小用于記錄這個分區(qū)的相關(guān)信息(例如起始和截止柱面位置、分區(qū)文件系統(tǒng)類型等等)。這就是為什么 mbr 分區(qū)模式最多只能有 4 個主分區(qū)的原因。
MBR 的局限:
最多只支持 4 個主分區(qū),超過 4 個就需要使用擴(kuò)展分區(qū)。 磁盤的最大容量只能到 2.2TB
如今我家的硬盤都 4T 了,MBR 早就不能滿足需求了。你也不能怪 MBR,畢竟人家 1983 年就提出了,比我的年紀(jì)還大。
3.2 GPT
為了解決 MBR 的問題,GPT 分區(qū)誕生,GPT 全稱 Globally Unique Identifier Partition Table,也叫 GUID 分區(qū)表,它是 UEFI 規(guī)范的一部分(但這并不是說它只支持 UEFI,它也支持 BIOS 方式的引導(dǎo))。

GPT 分區(qū)結(jié)構(gòu)如下:
Protective MBR:GPT 分區(qū)表的最前面部分也保存了和 MBR 相同的格式和內(nèi)容稱為 Protective MBR,這極大的提高了 GPT 分區(qū)表的兼容性。 主 GPT Header:這里記錄了分區(qū)表項目數(shù)和每項目大小。 主 GPT 分區(qū)表:包含分區(qū)的類型 GUID,名稱,起始終止位置,該分區(qū)的 GUID 以及分區(qū)屬性 實際分區(qū) 備份 GPT 分區(qū)表: 用于提高安全性,防止主 GPT 分區(qū)表損壞 備份 GPT Header: 用于提高安全性,防止主 GPT Header 損壞
3.3 Bootloader 寫入
使用 dd 命令結(jié)合 hexdump 可以輸出 MBR 信息
dd?if=~/Desktop/volumio-2.799-2020-07-16-x86.img?ibs=512?count=1?|?hexdump?-C

同樣的使用 dd 命令可以拷貝 MBR 信息從 img 文件到物理磁盤。(之前我是分分區(qū)寫入到磁盤的,導(dǎo)致 MBR 信息丟失無法引導(dǎo))
dd?if=~/Desktop/volumio-2.799-2020-07-16-x86.img?ibs=512?count=1?of=/dev/sda
也可以使用下載的 syslinux 中的 mbr.bin 寫入
dd?bs=440?count=1?conv=notrunc?if=/usr/lib/syslinux/bios/mbr.bin?of=/dev/sda?//MBR分區(qū)表
dd?bs=440?count=1?conv=notrunc?if=/usr/lib/syslinux/bios/gptmbr.bin?of=/dev/sda?//GPT分區(qū)表
4. 引導(dǎo)加載程序:Syslinux 和 GRUB
前文說到 MBR 的 bootloader 主要功能是交棒內(nèi)核,但是 bootloader 不會直接拉起 linux 內(nèi)核,400K 太小,它沒有能力將 linux 內(nèi)核直接加載到內(nèi)存。這時需要引導(dǎo)加載程序登場,它的主要目的就是將系統(tǒng)內(nèi)核鏡像和 initrd 鏡像加載到內(nèi)存并將控制權(quán)交給它們。目前常用的有兩種 Syslinux 和 GRUB:
Syslinux 是一個啟動加載器集合,可以從硬盤、光盤或通過 PXE 的網(wǎng)絡(luò)引導(dǎo)啟動系統(tǒng)。支持的文件系統(tǒng)包括 FAT,ext2,ext3,ext4 和非壓縮單設(shè)備 Btrfs 文件系統(tǒng)。 GRUB?,即 GRand Unified Bootloader(大一統(tǒng)啟動加載器),是一個多重啟動加載器,承自 PUPA 項目。今的 GRUB 也被稱作 GRUB 2,而 GRUB Legacy 表示 0.9x 版本。
對于普通用戶來說他們有什么用呢?它可以提供選單選擇 Linux 內(nèi)核版本,此外加載程序使得我們可以向 Linux 內(nèi)核傳遞參數(shù)。這點(diǎn)很重要,在我的案例中 volumio 就是通過 Syslinux 向內(nèi)核傳遞啟動參數(shù)的。
Syslinux 已經(jīng)不支持 bios64 位系統(tǒng)了,目前使用 GRUB2 的比較多。由于 volumio 使用的是 Syslinux 我沒有對 GRUB 展開研究。
下圖是 volumio 的默認(rèn) syslinux 配置。

LINUX 命令:指定了當(dāng)前內(nèi)核文件為 vmlinuz-3.18.5 版本; INITRD 命令:指定了 initrd 文件為 volumio.initrd(之后修改 initrd 也就是修改這個文件); APPEND 命令:是向內(nèi)核傳遞的參數(shù),在下文 initrd 的 init shell 中可以通過 cat /proc/cmdline 讀取到。
這里指定了 imgpart,bootpart 的 uuid 用于掛載分區(qū),imgfile 名字用于確定當(dāng)前真實 root 分區(qū)的文件名,還有 loglvevel、USE_KMSG 等參數(shù)。
5. 內(nèi)核:vmlinuz 和 initrd
引導(dǎo)加載程序交棒之后系統(tǒng)進(jìn)入內(nèi)核引導(dǎo)階段。這一步會在內(nèi)存中運(yùn)行系統(tǒng)內(nèi)核和根文件系統(tǒng)。之后根目錄下的 init shell 會被調(diào)用執(zhí)行,完成進(jìn)一步的初始化操作。
5.1 vmlinuz 和 initrd
vmlinuz 是可引導(dǎo)的、壓縮的內(nèi)核。“vm”代表“Virtual Memory”。Linux 能夠使用硬盤空間作為虛擬內(nèi)存,因此得名“vm”。vmlinuz 是可執(zhí)行的 Linux 內(nèi)核。
initrd 是“initial ramdisk”的簡寫。initrd 一般被用來臨時的引導(dǎo)硬件到實際內(nèi)核 vmlinuz 能夠接管并繼續(xù)引導(dǎo)的狀態(tài)。initrd 字面上的意思就是"boot loader initialized RAM disk",換言之,這是一塊特殊的 RAM disk,在載入 Linux kernel 前,由 boot loader 予以初始化,啟動過程會優(yōu)先執(zhí)行 initrd 的 init 程序,initrd 完成階段性目標(biāo)后,kernel 會掛載真正的 root file system ,并執(zhí)行/sbin/init 程序。
采用這種分離的方式,使得我們有機(jī)會在內(nèi)核引導(dǎo)階段做一些我們自己的事情。簡單讀了 volumio.initrd 中的 init shell 發(fā)現(xiàn)它至少做了幾件事情:
讀取 syslinux 傳遞來的環(huán)境變量 根據(jù)變量決定是否在屏幕打印日志。USE_KMSG 參數(shù)決定 加載各種內(nèi)核驅(qū)動模塊 掛載 boot 分區(qū) 使用 fdisk 處理磁盤,img 文件寫入磁盤后大小不一致,首次啟動需要使用 fdisk 命令調(diào)整分區(qū)大小 掛載一個 imgpart 分區(qū),這個不是真正的 root 分區(qū),這里面的 volumio_current.sqsh 文件才是,這樣做的目的是方便系統(tǒng)升級,在系統(tǒng)內(nèi)替換 imgpart 分區(qū)的 volumio_current.sqsh 文件即可完成系統(tǒng)升級。volumio_current.sqsh 文件名也是通過 imgfile 參數(shù)決定的。 處理 volumio_current.sqsh 升級問題,發(fā)現(xiàn)有新的 volumio.sqsh 文件會重命名舊的,然后將新的重命名 volumio_current.sqsh 使用 overlay 方式結(jié)合 volumio_current.sqsh 文件掛載真正的 root 分區(qū)。 執(zhí)行 switch_root 命令,重定向新的根分區(qū)并執(zhí)行/sbin/init 命令。
5.2 initrd 編輯
由于 linux 內(nèi)核啟動后,之前 ipxe 對應(yīng)的環(huán)境已經(jīng)退出,因此之前掛載的 iscsi 磁盤也無法訪問,需要在 initrd 的 init shell 中重新掛載 iscsi 磁盤。因此我需要在上文的 4 步驟之前掛載 iscsi 磁盤,修改如下:
加載網(wǎng)卡內(nèi)核驅(qū)動 啟動網(wǎng)絡(luò) 啟動 iscsi 客戶端掛載網(wǎng)絡(luò)磁盤。
可以使用如下方式編輯已經(jīng)生成好的 initrd 文件。
mount?-o?loop,offset=1048576?./wrt/Build/Volumio2.799-2020-09-29-x86.img?./vboot/?//掛載img鏡像的boot分區(qū)到目錄
cp?../vboot/volumio.initrd?volumio.initrd.gz?//拷貝initrd文件,重命名一下
gunzip?./volumio.initrd.gz?//解壓gz文件
cpio?-ivmd?
vim?init?//編輯init?shell
find?.?|?cpio?-c?-o?>?../volumio.initrd.img?//重新打包成新的initrd
gzip?volumio.initrd.img
mv?volumio.initrd.img.gz?volumio.initrd
還有另外一種方案,由于 volumio 是開源項目,編譯 volumio 的腳本在 github 開源。我可以編輯編譯腳本,直接修改 init 之后編譯成新的 initrd 文件。
git?clone?https://github.com/volumio/Build.git
ls?-la?scripts/initramfs/init-x86
ls?-la?scripts/x86config.sh
x86config.sh 這是編譯生成 x86 版本 volumio 鏡像的腳本,在這個文件中,我們需要添加命令,使得生成的 initrd 文件中包含 iscsi 客戶端 init-x86 這個文件是 initrd 文件在系統(tǒng)啟動后,需要執(zhí)行的 init shell 腳本。這里我們需要添加 網(wǎng)卡驅(qū)動、初始化 iscsi 客戶端。
首先處理 x86config.sh 腳本,我們需要在 initrd 中添加 iscsi 客戶端下圖中:193-195 行安裝 iscsi 客戶端 231-232 行向 initrd 中添加 iscsi 模塊

之后處理 init-x86,在 118 行左右的位置,腳本讀取了配置在/proc/cmdline 中的根目錄 uuid 并在之后掛載磁盤。這里的 cmdline 就是之前說到的在 syslinux 階段向內(nèi)核傳遞的參數(shù)。所以我們要在掛載磁盤前加載網(wǎng)卡驅(qū)動、啟動網(wǎng)絡(luò)、啟動 iscsi 客戶端、掛載 iscsi 磁盤。

103 行 加載網(wǎng)卡驅(qū)動 104 行 加載 iscsi 內(nèi)核模塊 105 行 加載 iscsi ibft 模塊 107-108 行 通過 ibft 配置網(wǎng)絡(luò) 114-116 行 使用 ibft 配置連接 iscsi 服務(wù)器并掛載磁盤

這里要說一下 ibft 這是一種將 iscsi 配置信息傳遞到系統(tǒng)的方式,我們在 iPxe 階段已經(jīng)配置網(wǎng)絡(luò)信息、iscsi 服務(wù)器地址、iscsi target 等信息了,這里可以使用 ibft 直接讀取并使用。當(dāng)然你也可以在這里再次手動啟用 DHCP,手動初始化 iscsi 客戶端。
修改完成后,iscsi 磁盤就可以像正常本地磁盤一樣被掛載,之后的操作就和正常硬盤安裝一樣了,正常啟動進(jìn)入 volumio 系統(tǒng)。
6. init 進(jìn)程
內(nèi)核引導(dǎo)階段完成以后,系統(tǒng)會掛載真實的 root 分區(qū),執(zhí)行/sbin/init 程序初始化系統(tǒng)環(huán)境。這一階段已經(jīng)和是否網(wǎng)絡(luò)啟動沒有關(guān)系了,不過啟動原理都研究到現(xiàn)在了就順便一起看一下吧。
/sbin/init 會首先確定運(yùn)行級別,這個配置在/etc/inittab 中,一般 Linux 有 7 種運(yùn)行級別(0-6)。一般來說,0 是關(guān)機(jī),1 是單用戶模式(也就是維護(hù)模式),6 是重啟。運(yùn)行級別 2-5,各個發(fā)行版不太一樣,對于 Debian 來說,都是同樣的多用戶模式(也就是正常模式)。確定運(yùn)行級別后會訪問/etc/rcN.d(這里的 N 就是運(yùn)行級別)。

這里的文件都采用“字母 S 或 K+兩位數(shù)字+程序名”的命名方式。其中 S 開頭的表示在這個級別需要執(zhí)行 start 命令,K 開頭需要執(zhí)行 Stop 命令,數(shù)字越小越優(yōu)先執(zhí)行。系統(tǒng)會依次執(zhí)行相應(yīng)的軟件和服務(wù),負(fù)責(zé)用戶界面的程序也被啟動你就有了 X11 界面,然后是 SSH 服務(wù)你就可以使用 ssh 登錄。這樣系統(tǒng)就完成了啟動。
當(dāng)然啦現(xiàn)在這種方式已經(jīng)過時了,目前基本使用 systemd 方式用 systemctl 命令管理。篇幅已經(jīng)很長了,這塊有興趣的同學(xué)自己搜索一下。
7. 尾巴
7.1 其他遇到的問題
syslinux 卡死這個問題前面說到了,掛載 iscsi 磁盤后 ipxe 交棒磁盤引導(dǎo),但是就卡死了。
經(jīng)過很多的 google 和嘗試之后最終發(fā)現(xiàn),我使用了 64 位的 iPxe 引導(dǎo)固件,但是 syslinux 只有 32 位版本導(dǎo)致卡死,更換了 32 位的 iPxe 固件后解決。
可以啟動無法關(guān)閉這個問題困擾了我很久,系統(tǒng)可以正常啟動,但是在關(guān)機(jī)或者重啟時會死機(jī),按鍵沒有任何反應(yīng)但是系統(tǒng)應(yīng)該還是活的(大小寫燈正常切換)只能強(qiáng)制關(guān)機(jī)退出。經(jīng)過排查原因可能是:關(guān)機(jī)時網(wǎng)絡(luò)服務(wù)會關(guān)閉導(dǎo)致網(wǎng)卡關(guān)閉,進(jìn)而導(dǎo)致 iscsi 網(wǎng)盤斷開。但是此時系統(tǒng)根分區(qū)還沒有 umount 導(dǎo)致系統(tǒng)無響應(yīng)。
我禁用了網(wǎng)絡(luò)服務(wù)的關(guān)機(jī)關(guān)閉,把 K06networking 從 rc0.d 目錄中去掉就好了。
Airplay 服務(wù)無法找到Volumio 自帶 shairport-sync 服務(wù),手機(jī)可以通過 airplay 鏈接 volumio 系統(tǒng)播放音樂,但是在我折騰完以后發(fā)現(xiàn)怎么也搜不到。經(jīng)過排查 shairport-sync 使用 mDns 發(fā)布組播告訴局域網(wǎng)內(nèi)的所有設(shè)備自己的地址,使用的是 avahi-daemon 程序。排查日志發(fā)現(xiàn)它啟動時沒有識別到網(wǎng)卡。我猜原因應(yīng)該是我們的網(wǎng)卡是在內(nèi)核引導(dǎo)階段自己拉起的,并不是進(jìn)入系統(tǒng)后由 networking 服務(wù)拉起的,所以 avahi-daemon 無法查找到它對應(yīng)的 ip。
我沒有找到很好的解決方案,還好老爺機(jī)還有一塊無線網(wǎng)卡,最后使用了無線網(wǎng)卡綁定 shairport-sync 服務(wù)。
7.2 最終效果

7.3 總結(jié)
總結(jié):為了省掉一塊 U 盤,我開始折騰 iscsi 無盤啟動沒想到這一折騰就是好久,前后研究了好多資料好好的學(xué)習(xí)了一下 linux 的啟動原理。
實際過程并沒有文中展現(xiàn)的那么順利,很多研究的彎路沒有在文中一一展現(xiàn)出來。在不同的節(jié)點(diǎn)也有很多方案可以選擇,比如:iPxe 本可以直接 http 下載 vmlinuz 和 initrd 引導(dǎo),這樣就可以省去 MBR 和 syslinux 引導(dǎo)。但是后來想想都研究了還是整理給大家。再比如 initrd 中 iscsi 客戶端的啟動和初始化有很多種方式,一開始我都手動初始化網(wǎng)卡,設(shè)置 dhcp 和 ip 路由。最后還是覺得太麻煩發(fā)現(xiàn) ibft 的方案最簡單,果斷選擇了它。
水平有限如果發(fā)現(xiàn)那里總結(jié)的不對歡迎指正。
你都看到這了點(diǎn)個贊再走吧~ 對了前幾天 99 公益日同事 10 塊錢買了塊 U 盤好像挺香的~
參考文獻(xiàn)
計算機(jī)是如何啟動的? UEFI 引導(dǎo)與 BIOS 引導(dǎo)在原理上有什么區(qū)別? PXE 批量部署安裝 Linux 系統(tǒng) MBR 與 GPT iPXE MBR vs. GPT Guide: What's The Difference and Which One Is Better Syslinux GRUB Using the initial RAM disk (initrd) Linux initrd 學(xué)習(xí)筆記 iSCSI/Boot
推薦閱讀:
你已經(jīng)是個成熟的 985 大學(xué)了,請不要在大一教 C 語言!
他,生物系畢業(yè),剛?cè)肼氝BJava都沒聽過,卻在馬云的要求下,三周寫出淘寶網(wǎng)雛形
5T技術(shù)資源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,單片機(jī),樹莓派,等等。在公眾號內(nèi)回復(fù)「1024」,即可免費(fèi)獲取!!
