初始 RAM 磁盤(initrd) 是在實際根文件系統可用之前掛載到系統中的一個初始根文件系統。initrd 與內核綁定在一起,並作爲內核引導過程的一部分進行加載。內核然後會將這個 initrd 文件作爲其兩階段引導過程的一部分來加載模塊,這樣才能稍後使用真正的文件系統,並掛載實際的根文件系統。
initrd 中包含了實現這個目標所需要的目錄和可執行程序的最小集合,例如將內核模塊加載到內核中所使用的 insmod
工具。
在桌面或服務器 Linux 系統中,initrd 是一個臨時的文件系統。其生存週期很短,只會用作到真實文件系統的一個橋樑。在沒有存儲設備的嵌入式系統中,initrd 是永久的根文件系統。本文將對這兩種情況進行探索。
|
initrd 映像中包含了支持 Linux 系統兩階段引導過程所需要的必要可執行程序和系統文件。
根據我們運行的 Linux 的版本不同,創建初始 RAM 磁盤的方法也可能會有所不同。在 Fedora Core 3 之前,initrd 是使用 loop 設備
來構建的。loop 設備
是一個設備驅動程序,利用它可以將文件作爲一個塊設備掛載到系統中,然後就可以查看這個文件系統中的內容了。在您的內核中可能並沒有 loop 設備,不過這可以通過內核配置工具(make menuconfig
)選擇 Device Drivers > Block Devices > Loopback Device Support
來啓用。我們可以按照下面的方法來查看 loop 設備的內容(initrd 文件的名字可能會稍有不同):
清單 1. 查看 initrd 的內容(適用於 FC3 之前的版本)
# mkdir temp ; cd temp |
現在我們就可以查看 /mnt/initrd 子目錄中的內容了,這就代表了 initrd 文件的內容。注意,即使您的 initrd 映像文件不是以 .gz 結尾,它也可能是一個壓縮文件,您可以給這個文件添加上 .gz 後綴,然後再使用 gunzip 對其進行解壓。
從 Fedora Core 3 開始,默認的 initrd 映像變成了一個經過壓縮的 cpio 歸檔文件。我們不用再使用 loop 設備來將 initrd 作爲壓縮映像進行掛載,而是可以將其作爲 cpio 歸檔文件來使用。要查看 cpio 歸檔文件的內容,可以使用下面的命令:
清單 2. 查看 initrd 的內容(適用於 FC3 及其以後的版本)
# mkdir temp ; cd temp |
結果會生成一個很小的根文件系統,如清單 3 所示。在 ./bin 目錄中有一組很少但卻非常必要的應用程序,包括 nash
(即 not a shell,是一個腳本解釋器)、insmod
(用來加載內核模塊)和 lvm
(邏輯卷管理工具)。
# ls -la |
清單 3 中比較有趣的是 init 文件就在根目錄中。與傳統的 Linux 引導過程類似,這個文件也是在將 initrd 映像解壓到 RAM 磁盤中時被調用的。在本文稍後我們將來探索這個問題。
|
|
下面讓我們回到最開始,來看一下 initrd 映像最初是如何構建的。對於傳統的 Linux 系統來說,initrd 映像是在 Linux 構建過程中創建的。有很多工具,例如 mkinitrd
,都可以用來使用必要的庫和模塊自動構建 initrd,從而用作與真實的根文件系統之間的橋樑。mkinitrd
工具實際上就是一個 shell 腳本,因此我們可以看到它究竟是如何來實現這個結果的。還有一個 YAIRD
(即 Yet Another Mkinitrd)工具,可以對 initrd 構建過程的各個方面進行定製。
|
由於在很多基於 Linux 的嵌入式系統上沒有硬盤,因此 initrd 也會作爲這種系統上的永久根文件系統使用。清單 4 顯示瞭如何創建一個 initrd 映像文件。我使用了一個標準的 Linux 桌面,這樣您即使沒有嵌入式平臺,也可以按照下面的步驟來執行了。除了交叉編譯,其他概念(也適用於 initrd 的構建)對於嵌入式平臺都是相同的。
#!/bin/bash |
|
爲了創建 initrd,我們最開始創建了一個空文件,這使用了 /dev/zero
(一個由零組成的碼流)作爲輸入,並將其寫入到 ramdisk.img 文件中。所生成的文件大小是 4MB(4000 個 1K 大小的塊)。然後使用 mke2fs
命令在這個空文件上創建了一個 ext2(即 second extended)文件系統。現在這個文件變成了一個 ext2
格式的文件系統,我們使用 loop 設備將這個文件掛載到 /mnt/initrd 上了。在這個掛載點上,我們現在就有了一個目錄,它以 ext2
文件系統的形式呈現出來,我們可以對自己的 initrd 文件進行拼裝了。接下來的腳本提供了這種功能。
下一個步驟是創建構成根文件系統所需要的子目錄:/bin、/sys、/dev 和 /proc。這裏只列出了所需要的目錄(例如沒有庫),但是其中包含了很多功能。
|
爲了可以使用根文件系統,我們使用了 BusyBox。這個工具是一個單一映像,其中包含了很多在 Linux 系統上通常可以找到的工具(例如 ash、awk、sed、insmod 等)。BusyBox 的優點是它將很多工具打包成一個文件,同時還可以共享它們的通用元素,這樣可以極大地減少映像文件的大小。這對於嵌入式系統來說非常理想。將 BusyBox 映像從自己的源目錄中拷貝到自己根目錄下的 /bin 目錄中。然後創建了很多符號鏈接,它們都指向 BusyBox 工具。BusyBox 會判斷所調用的是哪個工具,並執行這個工具的功能。我們在這個目錄中創建了幾個鏈接來支持 init 腳本(每個命令都是一個指向 BusyBox 的鏈接。)
下一個步驟是創建幾個特殊的設備文件。我從自己當前的 /dev 子目錄中直接拷貝了這些文件,這使用了 -a
選項(歸檔)來保留它們的屬性。
倒數第二個步驟是生成 linuxrc 文件。在內核掛載 RAM 磁盤之後,它會查找 init
文件來執行。如果沒有找到 init
文件,內核就會調用 linuxrc 文件作爲自己的啓動腳本。我們在這個文件中實現對環境的基本設置,例如掛載 /proc 文件系統。除了 /proc 之外,我還掛載了 /sys 文件系統,並向終端打印一條消息。最後,我們調用了 ash
(一個 Bourne Shell 的克隆),這樣就可以與根文件系統進行交互了。linuxrc 文件然後使用 chmod
命令修改成可執行的。
最後,我們的根文件系統就完成了。我們將其卸載掉,然後使用 gzip
對其進行壓縮。所生成的文件(ramdisk.img.gz)被拷貝到 /boot 子目錄中,這樣就可以通過 GNU GRUB 對其進行加載了。
要構建初始 RAM 磁盤,我們可以簡單地調用 mkird
,這樣就會自動創建這個映像文件,並將其拷貝到 /boot 目錄中。
|
|
新的 initrd 映像現在已經在 /boot 目錄中了,因此下一個步驟是使用默認的內核來對其進行測試。現在我們可以重新啓動 Linux
系統了。在出現 GRUB 界面時,按 C 鍵啓動 GRUB 中的命令行工具。我們現在可以與 GRUB 進行交互,從而定義要加載哪個內核和
initrd 映像文件。kernel
命令讓我們可以指定內核文件,initrd
命令可以用來指定 initrd 映像文件。在定義好這些參數之後,就可以使用 boot
命令來引導內核了,如清單 5 所示。
GNU GRUB version 0.95 (638K lower / 97216K upper memory) |
在內核啓動之後,它會檢查是否有 initrd 映像文件可用(稍後會更詳細介紹),然後將其加載,並將其掛載成根文件系統。在清單 6
中我們可以看到這個 Linux 啓動過程最後的樣子。在啓動之後,ash shell
就可以用來輸入命令了。在這個例子中,我們將瀏覽一下根文件系統的內容,並查看一下虛擬 proc 文件系統中的內容。我們還展示瞭如何通過
touch 命令在文件系統中創建文件。注意所創建的第一個進程是 linuxrc
(通常都是 init
)。
清單 6. 使用簡單的 initrd 引導 Linux 內核
... |
|
現在我們已經瞭解瞭如何構建並使用定製的初始 RAM 磁盤,本節將探索內核是如何識別 initrd 並將其作爲根文件系統進行掛載的。我們將介紹啓動鏈中的幾個主要函數,並解釋一下到底在進行什麼操作。
引導加載程序,例如 GRUB,定義了要加載的內核,並將這個內核映像以及相關的 initrd 拷貝到內存中。我們可以在 Linux 內核源代碼目錄中的 ./init 子目錄中找到很多這種功能。
在內核和 initrd 映像被解壓並拷貝到內存中之後,內核就會被調用了。它會執行不同的初始化操作,最終您會發現自己到了 init/main.c:init()
(subdir/file:function)函數中。這個函數執行了大量的子系統初始化操作。此處會執行一個對 init/do_mounts.c:prepare_namespace()
的調用,這個函數用來準備名稱空間(掛載 dev 文件系統、RAID 或 md、設備以及最後的 initrd)。加載 initrd 是通過調用 init/do_mounts_initrd.c:initrd_load()
實現的。
initrd_load()
函數調用了 init/do_mounts_rd.c:rd_load_image()
,它通過調用 init/do_mounts_rd.c:identify_ramdisk_image()
來確定要加載哪個 RAM 磁盤。這個函數會檢查映像文件的 magic 號來確定它是 minux、etc2、romfs、cramfs 或 gzip 格式。在返回到 initrd_load_image
之前,它還會調用 init/do_mounts_rd:crd_load()
。這個函數負責爲 RAM 磁盤分配空間,並計算循環冗餘校驗碼(CRC),然後對 RAM 磁盤映像進行解壓,並將其加載到內存中。現在,我們在一個適合掛載的塊設備中就有了這個 initrd 映像。
現在使用一個 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()
來真正掛載根文件系統,然後 chdir
到這個文件系統中。這就是我們在清單 6 中所看到的熟悉消息 VFS: Mounted root (ext2 file system).
的地方。
最後,返回到 init
函數中,並調用 init/main.c:run_init_process
。這會導致調用 execve
來啓動 init 進程(在本例中是 /linuxrc
)。linuxrc 可以是一個可執行程序,也可以是一個腳本(條件是它有腳本解釋器可用)。
這些函數的調用層次結構如清單 7 所示。儘管此處並沒有列出拷貝和掛載初始 RAM 磁盤所涉及的所有函數,但是這足以爲我們提供一個整體流程的粗略框架。
清單 7. initrd 加載和掛載過程中所使用的主要函數的層次結構
init/main.c:init |
|
與嵌入式引導的情況類似,本地磁盤(軟盤或 CD-ROM)對於引導內核和 ramdisk 根文件系統來說都不是必需的。DHCP(Dynamic Host Configuration Protocol)可以用來確定網絡參數,例如 IP 地址和子網掩碼。TFTP(Trivial File Transfer Protocol)可以用來將內核映像和初始 ramdisk 映像傳輸到本地設備上。傳輸完成之後,就可以引導 Linux 內核並掛載 initrd 了,這與本地映像引導的過程類似。
|
在構建嵌入式系統時,我們可能希望將 initrd 映像文件做得儘可能小,這其中有一些技巧需要考慮。首先是使用 BusyBox(本文中已經展示過了)。BusyBox 可以將數 MB 的工具壓縮成幾百 KB。
在這個例子中,BusyBox 映像是靜態鏈接的,因此它不需要其他庫。然而,如果我們需要標準的 C 庫(我們自己定製的二進制可能需要這個庫),除了巨大的 glibc 之外,我們還有其他選擇。第一個較小的庫是 uClibc,這是爲對空間要求非常嚴格的系統準備的一個標準 C 庫。另外一個適合空間緊張的環境的庫是 dietlib。要記住我們需要使用這些庫來重新編譯想在嵌入式系統中重新編譯的二進制文件,因此這需要額外再做一些工作(但是這是非常值得的)。
|
初始 RAM 磁盤最初是設計用來通過一個臨時根文件系統來作爲內核到最終的根文件系統之間的橋樑。initrd 對於在嵌入式系統中加載到 RAM 磁盤裏的非持久性根文件系統來說也非常有用。
學習
- 您可以參閱本文在 developerWorks 全球站點上的 英文原文
。
-
“Linux 引導過程內幕
”(developerWorks,2006 年 5 月)探索了 Linux 從最初的 bootstrap 到啓動第一個用戶空間應用程序的過程。
-
在 “從 FireWire 設備引導 Linux
”(developerWorks,2004 年 7 月)中,我們可以學習在各種平臺的各種設備上如何(使用 initrd)啓動 Linux。
-
cpio 文件格式
既簡單又簡潔。因此 Fedora 團隊選擇使用它作爲 initrd 的格式就沒什麼奇怪的了。
-
mkinitrd
工具非常適合創建 initrd 映像文件。除了創建 initrd 映像之外,它還可以確定要爲您的系統加載哪些模塊,並將這些模塊也全部加入到這個映像中。
-
loop 設備
是一個非常有用的驅動程序,可以將映像文件作爲文件系統掛載。
-
Network Boot and Exotic Root HOWTO
不但介紹了從網絡上引導 Linux 的過程,還介紹了諸如軟盤引導、CD-ROM 引導和嵌入式環境中的內容。
-
在 developerWorks Linux 專區
中可以找到爲 Linux 開發人員準備的更多資源。
- 隨時關注 developerWorks 技術事件和網絡廣播 。
獲得產品和技術
-
cpio 文件格式
(現在可以用作 Fedora Core 的一種 initrd 映像格式)具有很長的歷史,可以在很多 UNIX 系統上使用。
-
ash
shell
是 Bourne Shell 的一個克隆(它們大部分是兼容的),它雖然很小,但是完全可以正常工作。它非常適合在對空間要求非常嚴格的嵌入式系統上用作腳本解釋器。
-
BusyBox
是一種縮減您下一個嵌入式 Linux 項目內存需求的好方法。
-
要進一步縮減 initrd 文件的大小,請考慮使用 glibc 的替代庫,例如 uClibc
或
dietlib
。如果您喜歡使用 C++,那麼可以試用一下 uClibc++
庫的 Alpha 版本。
-
Minimax
是一個完全封裝在 initrd 映像文件中的 Linux 發行版!
-
訂購免費的 SEK for Linux
,這有兩張 DVD,包括最新的 IBM for Linux 的試用軟件,包括 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere®。
-
在您的下一個開發項目中採用 IBM 試用軟件
,這可以從 developerWorks 上直接下載。
討論
-
通過參與 developerWorks blogs
加入 developerWorks 社區。
Tim Jones 是一名嵌入式軟件工程師,他是 GNU/Linux Application Programming 、AI Application Programming 以及 BSD Sockets Programming from a Multilanguage Perspective 等書的作者。他的工程背景非常廣泛,從同步宇宙飛船的內核開發到嵌入式架構設計,再到網絡協議的開發。Tim 是 Emulex Corp. 的一名資深軟件工程師。 |