英文含義
什麼是初始 RAM 磁碟
初始RAM磁碟(initrd)是在實際
根檔案系統可用之前掛載到系統中的一個初始根檔案系統。initrd與
核心綁定在一起,並作為核心引導過程的一部分進行載入。核心然後會將這個 initrd檔案作為其兩階段引導過程的一部分來載入模組,這樣才能稍後使用真正的
檔案系統,並掛載實際的根檔案系統。
initrd 中包含了實現這個目標所需要的目錄和可執行程式的最小集合,例如將核心模組載入到核心中所使用的 insmod 工具。
在桌面或
伺服器Linux 系統中,initrd 是一個
臨時的檔案系統。其生存周期很短,只會用作到真實檔案系統的一個橋樑。在沒有存儲設備的嵌入式系統中,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
cp /boot/initrd.img.gz .
gunzip initrd.img.gz
mount -t ext -oloopinitrd.img /mnt/initrd
ls -la /mnt/initrd
這樣我們就可以查看 /mnt/initrd 子目錄中的內容了,這就代表了 initrd檔案的內容。注意,即使您的 initrd
映像檔案不是以 .gz 結尾,它也可能是一個
壓縮檔案,您可以給這個檔案添加上 .gz後綴,然後再使用 gunzip 對其進行解壓。
從 Fedora Core 3 開始,默認的 initrd 映像變成了一個經過壓縮的cpio 歸檔檔案。我們不用再使用
loop設備來將 initrd 作為壓縮映像進行掛載,而是可以將其作為 cpio 歸檔檔案來使用。要查看cpio 歸檔檔案的內容,可以使用下面的命令:
清單 2. 查看 initrd 的內容(適用於 FC3 及其以後的版本)
mkdir temp ; cd temp
cp /boot/initrd-2.6.14.2.img initrd-2.6.14.2.img.gz
gunzip initrd-2.6.14.2.img.gz
cpio -i -d < initrd-2.6.14.2.img
結果會生成一個很小的
根檔案系統,如清單 3 所示。在 ./bin 目錄中有一組很少但卻非常必要的應用程式,包括 nash(即 not a shell,是一個腳本解釋器)、insmod(用來載入
核心模組)和 lvm(邏輯
卷管理工具)。
清單 3. 默認的 Linux initrd
目錄結構ls -la
drwxr-xr-x 10 root root 4096 May 7 02:48 .
drwxr-x--- 15 root root 4096 May 7 00:54 ..
drwxr-xr-x 2 root root 4096 May 7 02:48 bin
drwxr-xr-x 2 root root 4096 May 7 02:48 dev
drwxr-xr-x 4 root root 4096 May 7 02:48 etc
-rwxr-xr-x 1 root root 812 May 7 02:48 init
-rw-r--r-- 1 root root 1723392 May 7 02:45 initrd-2.6.14.2.img
drwxr-xr-x 2 root root 4096 May 7 02:48 lib
drwxr-xr-x 2 root root 4096 May 7 02:48
loopfs
drwxr-xr-x 2 root root 4096 May 7 02:48 proc
lrwxrwxrwx 1 root root 3 May 7 02:48 sbin -> bin
drwxr-xr-x 2 root root 4096 May 7 02:48 sys
drwxr-xr-x 2 root root 4096 May 7 02:48 sysroot
#
清單 3 中比較有趣的是 init 檔案就在根目錄中。與傳統的 Linux 引導過程類似,這個檔案也是在將 initrd 映像解壓到 RAM磁碟中時被調用的。在本文稍後我們將來探索這個問題。
所用工具
cpio 命令
使用 cpio 命令,我們可以對 cpio 檔案
進行操作。cpio是一種檔案格式,它簡單地使用
檔案頭將一組檔案串接在一起。cpio 檔案格式可以使用 ASCII 和
二進制檔案。為了保證可移植性,我們可以使用ASCII 格式。為了減小檔案大小,我們可以使用二進制的版本。
下面讓我們回到最開始,來看一下 initrd 映像最初是如何構建的。對於傳統的Linux系統來說,initrd 映像是在 Linux 構建過程中創建的。有很多工具,例如
mkinitrd,都可以用來使用必要的庫和模組自動構建 initrd,從而用作與真實的
根檔案系統之間的橋樑。mkinitrd工具實際上就是一個 shell腳本,因此我們可以看到它究竟是如何來實現這個結果的。還有一個 YAIRD(即 Yet AnotherMkinitrd)工具,可以對 initrd 構建過程的各個方面進行定製。
手工構建定製的初始 RAM 磁碟
由於在很多基於 Linux 的
嵌入式系統上沒有硬碟,因此 initrd也會作為這種系統上的永久
根檔案系統使用。清單 4 顯示了如何創建一個 initrd 映像檔案。我使用了一個標準的 Linux桌面,這樣您即使沒有嵌入式平台,也可以按照下面的步驟來執行了。除了
交叉編譯,其他概念(也適用於 initrd 的構建)對於嵌入式平台都是相同的。
清單 4. 創建定製 initrd 的工具(mkird)
#!/bin/bash
# Housekeeping...
rm -f /tmp/ramdisk.img.gz
# Ramdisk Constants
RDSIZE=4000
BLKSIZE=1024
# Create an emptyramdisk image
dd if=/dev/zero of=/tmp/ramdisk.img bs=$BLKSIZE count=$RDSIZE
# Make it an ext2 mountable file system
/sbin/mke2fs -F -m 0 -b $BLKSIZE /tmp/
ramdisk.img $RDSIZE
# Mount it so that we can populate
mount /tmp/ramdisk.img /mnt/initrd -t ext2 -o
loop=/dev/loop0
# Populate the filesystem (subdirectories)
mkdir /mnt/initrd/bin
mkdir /mnt/initrd/sys
mkdir /mnt/initrd/dev
mkdir /mnt/initrd/proc
# Grab
busyboxand create the symbolic links
pushd /mnt/initrd/bin
cp /usr/local/src/busybox-1.1.1/busybox.
ln -sbusyboxash
ln -sbusyboxmount
ln -sbusyboxecho
ln -sbusyboxls
ln -sbusyboxcat
ln -sbusyboxps
ln -sbusyboxsysctl
popd
# Grab the necessary dev files
cp -a /dev/console /mnt/initrd/dev
cp -a /dev/
ramdisk /mnt/initrd/dev
cp -a /dev/ram0 /mnt/initrd/dev
cp -a /dev/null /mnt/initrd/dev
cp -a /dev/tty1 /mnt/initrd/dev
cp -a /dev/tty2 /mnt/initrd/dev
# Equate sbin with bin
pushd /mnt/initrd
ln -s bin sbin
popd
# Create the init file
cat >> /mnt/initrd/
linuxrc << EOF
#!/bin/ash
echo
echo "Simple initrd is active"
echo
mount -t proc /proc /proc
mount -t sysfs none /sys
/bin/ash --login
EOF
chmod +x /mnt/initrd/linuxrc
# Finish up...
umount /mnt/initrd
cp /tmp/ramdisk.img.gz /boot/ramdisk.img.gz
initrd Linux 發行版
Minimax 是一個開放源碼項目,其設計目標是成為一個全部封裝在 initrd 中的Linux 發行版。它的大小是 32MB,為了儘量小,它使用了 BusyBox 和 uClibc。除了非常小之外,它還使用了 2.6 版本的Linux
核心,並提供了很多有用的工具。
為了創建 initrd,我們最開始創建了一個空檔案,這使用了/dev/zero(一個由零組成的碼流)作為輸入,並將其寫入到
ramdisk.img 檔案中。所生成的檔案大小是 4MB(4000 個 1K大小的塊)。然後使用 mke2fs 命令在這個空檔案上創建了一個 ext2(即 secondextended)檔案系統。現在這個檔案變成了一個 ext2 格式的
檔案系統,我們使用
loop設備將這個檔案掛載到 /mnt/initrd上了。在這個
掛載點上,我們現在就有了一個目錄,它以 ext2檔案系統的形式呈現出來,我們可以對自己的 initrd檔案進行拼裝了。接下來的腳本提供了這種功能。
下一個步驟是創建構成
根檔案系統所需要的子目錄:/bin、/sys、/dev 和 /proc。這裡只列出了所需要的目錄(例如沒有庫),但是其中包含了很多功能。
ext2檔案系統的替代品
儘管 ext2 是一種通用的 Linux
檔案系統格式,但是還有一些替代品可以減小initrd
映像檔案以及所掛載上來的檔案系統的大小。這種檔案系統的例子有 romfs(ROM檔案系統)、c
ramfs(壓縮 ROM檔案系統)和 squashfs(高度壓縮唯讀檔案系統)。如果我們需要暫時將數據寫入檔案系統中,ext2可以很好地實現這種功能。最後,e2compr 是 ext2檔案系統驅動程式的一個擴展,可以支持線上壓縮。
為了可以使用
根檔案系統,我們使用了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 目錄中。
測試定製的初始 RAM 磁碟
對於 Linux核心來說,要支持初始 RAM磁碟,核心必須要使用 CONFIG_BLK_DEV_RAM 和 CONFIG_BLK_DEV_INITRD 選項進行編譯。
新的 initrd 映像現在已經在 /boot目錄中了,因此下一個步驟是使用默認的核心來對其進行測試。現在我們可以重新啟動 Linux 系統了。在出現 GRUB 界面時,按 C 鍵啟動GRUB 中的命令行工具。我們現在可以與 GRUB 進行互動,從而定義要載入哪個核心和 initrd
映像檔案。kernel命令讓我們可以指定
核心檔案,initrd 命令可以用來指定 initrd 映像檔案。在定義好這些參數之後,就可以使用 boot命令來引導核心了,如清單 5 所示。
清單 5. 使用 GRUB 手工引導核心和 initrd
GNU GRUB version 0.95 (638K lower / 97216K upper memory)
[ Minimal BASH-like line editing is supported. For the first word, TAB
lists possible command completions. Anywhere else TAB lists the possible
completions of a device/filename. ESC at any time exits.]
grub> kernel /bzImage-2.6.1
[Linux-bzImage, setup=0x1400, size=0x29672e]
grub> initrd /
ramdisk.img.gz
[Linux-initrd @ 0x5f2a000, 0xb5108 bytes]
grub> boot
Uncompressing Linux... OK, booting the kernel.
在
核心啟動之後,它會檢查是否有 initrd
映像檔案可用(稍後會更詳細介紹),然後將其載入,並將其掛載成根檔案系統。在清單 6 中我們可以看到這個 Linux啟動過程最後的樣子。在啟動之後,ash shell 就可以用來輸入命令了。在這個例子中,我們將瀏覽一下
根檔案系統的內容,並查看一下虛擬proc 檔案系統中的內容。我們還展示了如何通過 touch 命令在
檔案系統中創建檔案。注意所創建的第一個進程是
linuxrc(通常都是init)。
清單 6. 使用簡單的 initrd 引導 Linux 核心
...
md: Autodetecting RAID arrays
md: autorun
md: ... autorun DONE.
RAMDISK: Compressed image found at block 0
VFS: Mounted root (ext2 file system).
Freeing unused kernel memory: 208k freed
/ $ ls
dev lib lost+found sbin
/ $ cat /proc/1/cmdline
/bin/ash/linuxrc
/ $ cd bin
/bin $ ls
ash cat echo mount sysctl
busybox dmesg ls ps
/bin $ touch zfile
/bin $ ls
ash cat echo mount sysctl
busybox dmesg ls ps zfile
使用初始 RAM 磁碟來引導系統
現在我們已經了解了如何構建並使用定製的初始 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
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
無盤引導
與嵌入式引導的情況類似,
本地磁碟(
軟碟或 CD-ROM)對於引導
核心和 ramdisk
根檔案系統來說都不是必需的。DHCP(Dynamic Host Configuration Protocol)可以用來確定網路參數,例如 IP位址和
子網掩碼。TFTP(Trivial File Transfer Protocol)可以用來將核心映像和初始
ramdisk映像傳輸到本地設備上。傳輸完成之後,就可以引導 Linux核心並掛載 initrd 了,這與本地映像引導的過程類似。
壓縮 initrd
在構建嵌入式系統時,我們可能希望將 initrd
映像檔案做得儘可能小,這其中有一些技巧需要考慮。首先是使用 BusyBox(本文中已經展示過了)。BusyBox 可以將數 MB 的工具壓縮成幾百 KB。
在這個例子中,BusyBox映像是
靜態連結的,因此它不需要其他庫。然而,如果我們需要標準的 C 庫(我們自己定製的二進制可能需要這個庫),除了巨大的 glibc之外,我們還有其他選擇。第一個較小的庫是 uClibc,這是為對空間要求非常嚴格的系統準備的一個標準 C 庫。另外一個適合空間緊張的環境的庫是dietlib。要記住我們需要使用這些庫來重新編譯想在
嵌入式系統中重新編譯的
二進制檔案,因此這需要額外再做一些工作(但是這是非常值得的)。
結束語
初始 RAM磁碟最初是設計用來通過一個臨時根檔案系統來作為
核心到最終的根檔案系統之間的橋樑。initrd 對於在
嵌入式系統中載入到 RAM磁碟里的非持久性
根檔案系統來說也非常有用