使用初始 RAM 磁盤來引導(dǎo)系統(tǒng)
現(xiàn)在我們已經(jīng)了解了如何構(gòu)建并使用定制的初始 RAM 磁盤,本節(jié)將探索內(nèi)核是如何識別 initrd 并將其作為根文件系統(tǒng)進(jìn)行掛載的。我們將介紹啟動(dòng)鏈中的幾個(gè)主要函數(shù),并解釋一下到底在進(jìn)行什么操作。
引導(dǎo)加載程序,例如 GRUB,定義了要加載的內(nèi)核,并將這個(gè)內(nèi)核映像以及相關(guān)的 initrd 拷貝到內(nèi)存中。我們可以在 Linux 內(nèi)核源代碼目錄中的 ./init 子目錄中找到很多這種功能。
在內(nèi)核和 initrd 映像被解壓并拷貝到內(nèi)存中之后,內(nèi)核就會被調(diào)用了。它會執(zhí)行不同的初始化操作,最終您會發(fā)現(xiàn)自己到了 init/main.c:init()
(subdir/file:function)函數(shù)中。這個(gè)函數(shù)執(zhí)行了大量的子系統(tǒng)初始化操作。此處會執(zhí)行一個(gè)對 init/do_mounts.c:prepare_namespace()
的調(diào)用,這個(gè)函數(shù)用來準(zhǔn)備名稱空間(掛載 dev 文件系統(tǒng)、RAID 或 md、設(shè)備以及最后的 initrd)。加載 initrd 是通過調(diào)用 init/do_mounts_initrd.c:initrd_load()
實(shí)現(xiàn)的。
initrd_load()
函數(shù)調(diào)用了 init/do_mounts_rd.c:rd_load_image()
,它通過調(diào)用 init/do_mounts_rd.c:identify_ramdisk_image()
來確定要加載哪個(gè) RAM 磁盤。這個(gè)函數(shù)會檢查映像文件的 magic 號來確定它是 minux、etc2、romfs、cramfs 或 gzip 格式。在返回到 initrd_load_image
之前,它還會調(diào)用 init/do_mounts_rd:crd_load()
。這個(gè)函數(shù)負(fù)責(zé)為 RAM 磁盤分配空間,并計(jì)算循環(huán)冗余校驗(yàn)碼(CRC),然后對 RAM 磁盤映像進(jìn)行解壓,并將其加載到內(nèi)存中。現(xiàn)在,我們在一個(gè)適合掛載的塊設(shè)備中就有了這個(gè) initrd 映像。
現(xiàn)在使用一個(gè) init/do_mounts.c:mount_root()
調(diào)用將這個(gè)塊設(shè)備掛載到根文件系統(tǒng)上。它會創(chuàng)建根設(shè)備,并調(diào)用 init/do_mounts.c:mount_block_root()
。在這里調(diào)用 init/do_mounts.c:do_mount_root()
,后者又會調(diào)用 fs/namespace.c:sys_mount()
來真正掛載根文件系統(tǒng),然后 chdir
到這個(gè)文件系統(tǒng)中。這就是我們在清單 6 中所看到的熟悉消息 VFS: Mounted root (ext2 file system).
的地方。
最后,返回到 init
函數(shù)中,并調(diào)用 init/main.c:run_init_process
。這會導(dǎo)致調(diào)用 execve
來啟動(dòng) init 進(jìn)程(在本例中是 /linuxrc
)。linuxrc 可以是一個(gè)可執(zhí)行程序,也可以是一個(gè)腳本(條件是它有腳本解釋器可用)。
這些函數(shù)的調(diào)用層次結(jié)構(gòu)如清單 7 所示。盡管此處并沒有列出拷貝和掛載初始 RAM 磁盤所涉及的所有函數(shù),但是這足以為我們提供一個(gè)整體流程的粗略框架。
清單 7. initrd 加載和掛載過程中所使用的主要函數(shù)的層次結(jié)構(gòu) init/main.c:init init/do_mounts.c:prepare_namespace init/do_mounts_initrd.c:initrd_load init/do_mounts_rd.c:rd_load_image init/do_mounts_rd.c:identify_ramdisk_image init/do_mounts_rd.c:crd_load lib/inflate.c:gunzip init/do_mounts.c:mount_root init/do_mounts.c:mount_block_root init/do_mounts.c:do_mount_root fs/namespace.c:sys_mount init/main.c:run_init_process execve |
無盤引導(dǎo)
與嵌入式引導(dǎo)的情況類似,本地磁盤(軟盤或 CD-ROM)對于引導(dǎo)內(nèi)核和 ramdisk 根文件系統(tǒng)來說都不是必需的。DHCP(Dynamic Host Configuration Protocol)可以用來確定網(wǎng)絡(luò)參數(shù),例如 IP 地址和子網(wǎng)掩碼。TFTP(Trivial File Transfer Protocol)可以用來將內(nèi)核映像和初始 ramdisk 映像傳輸?shù)奖镜卦O(shè)備上。傳輸完成之后,就可以引導(dǎo) Linux 內(nèi)核并掛載 initrd 了,這與本地映像引導(dǎo)的過程類似。
壓縮 initrd
在構(gòu)建嵌入式系統(tǒng)時(shí),我們可能希望將 initrd 映像文件做得盡可能小,這其中有一些技巧需要考慮。首先是使用 BusyBox(本文中已經(jīng)展示過了)。BusyBox 可以將數(shù) MB 的工具壓縮成幾百 KB。
在這個(gè)例子中,BusyBox 映像是靜態(tài)鏈接的,因此它不需要其他庫。然而,如果我們需要標(biāo)準(zhǔn)的 C 庫(我們自己定制的二進(jìn)制可能需要這個(gè)庫),除了巨大的 glibc 之外,我們還有其他選擇。第一個(gè)較小的庫是 uClibc,這是為對空間要求非常嚴(yán)格的系統(tǒng)準(zhǔn)備的一個(gè)標(biāo)準(zhǔn) C 庫。另外一個(gè)適合空間緊張的環(huán)境的庫是 dietlib。要記住我們需要使用這些庫來重新編譯想在嵌入式系統(tǒng)中重新編譯的二進(jìn)制文件,因此這需要額外再做一些工作(但是這是非常值得的)。
結(jié)束語
初始 RAM 磁盤最初是設(shè)計(jì)用來通過一個(gè)臨時(shí)根文件系統(tǒng)來作為內(nèi)核到最終的根文件系統(tǒng)之間的橋梁。initrd 對于在嵌入式系統(tǒng)中加載到 RAM 磁盤里的非持久性根文件系統(tǒng)來說也非常有用。