構建initramfs文件系統

(一)hello world
一、initramfs是什麼

在2.6版本的linux內核中,都包含一個壓縮過的cpio格式的打包文件。當內核啓動時,會從這個打包文件中導出文件到內核的rootfs文件系統,然後內核檢查rootfs中是否包含有init文件,如果有則執行它,作爲PID爲1的第一個進程。這個init進程負責啓動系統後續的工作,包括定位、掛載“真正的”根文件系統設備(如果有的話)。如果內核沒有在 rootfs中找到init文件,則內核會按以前版本的方式定位、掛載根分區,然後執行/sbin/init程序完成系統的後續初始化工作。
這個壓縮過的cpio格式的打包文件就是initramfs。編譯2.6版本的linux內核時,編譯系統總會創建initramfs,然後把它與編譯好的內核連接在一起。內核源代碼樹中的usr目錄就是專門用於構建內核中的initramfs的,其中的initramfs_data.cpio.gz文件就是initramfs。缺省情況下,initramfs是空的,X86架構下的文件大小是134個字節。
二、構建第一個initramfs:hello world
從C語言開始,學習計算機編程語言的第一個程序幾乎都是hello world,因此我們也構建一個最簡單的hello world式的initramfs,以說明initramfs的基本構建方法。
initramfs的靈魂是init文件(或者叫程序,因爲它會被內核第一個執行),我們先寫一個簡單的init程序,它會在內核的console中打印出經典的hello world信息。
hello.c:
#include
#include
int main(int argc,char argv[])
{
printf("hello world, from initramfs.\n");
sleep(9999999);
return 0;
}
其中的sleep()函數語句是爲了避免執行時內核很快打出panic的信息,並非功能上的需要。
接着把hello.c編譯成靜態連接程序:
gcc -o hello_static -static -s hello.c
命令行中的-s參數表示編譯後的程序不包含調試定位信息,目的是減少編譯出來的程序文件的大小。
再創建一個initramfs的構建源文件目錄p_w_picpath,把hello_static程序拷入這個目錄,並改名爲init。
在p_w_picpath目錄下,創建一個dev/console的設備文件,否init程序無法在內核console中輸出信息:
mknod -m 600 dev/console c 5 1
注意,執行這個命令需要有root權限。
好了,現在可以設置內核配置參數,進行initramfs的構建了:
在general setup配置目錄下的initramfs sources配置項下輸入p_w_picpath的路徑名,比如我的路徑就是/home/wyk/initramfs-test/p_w_picpath。因爲我們的init程序是ELF格式的,所以內核需要支持ELF的可執行文件,否則啓動這個init程序會失敗。在內核的 Executable file formats配置目錄下,選擇 kernel support for ELF binaries,則可使內核支持ELF格式的可執行文件。其他內核配置參數根據實際需要設置即可,不過,爲了減少內核編譯時間,可參考這篇文章
http://linuxman.blog.ccidnet.com/blog-htm-do-showone-uid-60710-type-blog-itemid-293122.html
設置一個最簡單的內核配置。
內核配置參數設置完成後,按常規的內核編譯方法進行編譯,initramfs就自動連接到編譯好的內核映像文件中了。
三、試驗環境搭建
試驗initramfs需要經常重啓系統,所以使用CPU模擬器是不錯的選擇。我們可以選用qemu,它支持直接啓動linux內核,無需在模擬器中安裝OS。從方便使用的角度考慮,我們採用qemu launcher設置qemu的各項參數,它的安裝可參考
http://linuxman.blog.ccidnet.com/blog-htm-do-showone-uid-60710-type-blog-itemid-612280.html

在qemu launcher的linux配置標籤中,打勾直接啓動linux,然後在下面的文本框中填上剛纔編譯好的內核映像文件的路徑名。因爲qemu的運行還需要設置硬盤映像文件,所以還需要在左邊的配置標籤中新建一個硬盤映像文件,但實際上我們並不使用硬盤。
配置好qemu的參數後,點擊launcher按鈕,內核就開始在qemu中運行了。內核輸出一堆內核運行信息後,最後打出了
hello world, from initramfs.
哈哈,我們構建的initramfs已經能夠正常工作了!
(二)initramfs的前世今生
四、什麼是rootfs和ramfs

所有的2.6版本linux內核都有一個特殊的文件系統 rootfs,是內核啓動的初始始根文件系統,initramfs的文件會複製到rootfs。如果把initramfs比作種子,那麼rootfs就是它生長的土壤。大部分linux系統正常運行後都會安裝另外的文件系統,然後忽略rootfs。
rootfs是ramfs文件系統的一個特殊實例。ramfs是一種非常簡單的文件系統,是基於內存的文件系統。ramfs文件系統沒有容量大小的限制,它可以根據需要動態增加容量。
ramfs 直接利用了內核的磁盤高速緩存機制。所有的文件的讀寫數據都會在內存中做高速緩存(cache),當系統再次使用文件數據時,可以直接從內存中讀寫,以提供系統的I/O性能。高速緩存中的寫入數據會在適當的時候回寫到對應的文件系統設備(如磁盤等)中,這時它的狀態就標識爲clean,這樣系統在必要時可以釋放掉這些內存。ramfs沒有對應文件系統設備,所以它的數據永遠都不會回寫回去,也就不會標識爲clean,因此係統也永遠不會釋放ramfs所佔用的內存。
因爲ramfs直接使用了內核已有的磁盤高速緩存機制,所以它的實現代碼非常小。也由於這個原因,ramfs特性不能通過內核配置參數刪除,它是內核的天然特性。
五、ramfs不是ramdisk
ramdisk 是在一塊內存區域中創建的塊設備,用於存放文件系統。ramdisk的容量是固定的,不能象ramfs一樣動態增長。ramdisk需要內核的文件系統驅動程序(如ext2)來操作其上的數據,而ramfs則是內核的天然特性,無需額外的驅動程序。ramdisk也象其他文件系統設備一樣,需要在塊設備和內存中的磁盤高速緩存之間複製數據,而這種數據複製實際不必要的。
六、從ramfs派生的文件系統tmpfs
ramfs 的一個缺點是它可能不停的動態增長直到耗盡系統的全部內存,所以只有root或授權用戶允許使用ramfs。爲了解決這個問題,從ramfs派生出了 tmpfs文件系統,增加了容量大小的限制,而且允許把數據寫入交換分區。由於增加了這兩個特性,所以tmpfs允許普通用戶使用。
關於tmpfs文件系統更多的信息,可以看內核源碼中的 Documentation/filesystems/tmpfs.txt 文檔。
綜上所述,initramfs是一種ramfs文件系統,在內核啓動完成後把它複製到rootfs中,作爲內核初始的根文件系統,它的任務是掛載系統真正的根文件系統。這就是initramfs的前世今生。

(三):busybox
七、什麼是busybox

busybox號稱是嵌入式Linux中的瑞士軍刀——小巧、功能齊全。它把許多常用的Linux命令都集成到一個單一的可執行程序中,只用這一個可執行程序(即busybox)加上Linux內核就可以構建一個基本的 Linux系統。busybox程序非常小巧,包含全部命令可執行文件大小也只有750多K。busybox是完全模塊化的,可以很容易地在編譯時增加、刪除其中包含的命令。
由於busybox的這些特點,它廣泛應用於LiveCD、應急修復盤、安裝盤等系統中。我們也是以它爲基礎,構建initramfs。
八、busybox的配置、編譯和安裝
(1)去
http://busybox.net
去下載最新的源碼,解壓展開。
(2)用
make menuconfig
命令啓動配置界面配置,配置busybox的特性、選擇要包含在busybox的命令(busybox稱爲applet);
也可以用
make defconfig
命令做缺省配置,包含全部的applet。
另外兩個配置命令是
make allyesconfig——最大配置
make allnoconfig——最小配置
它們和make defconfig命令都可以用來作爲自定義配置的初始配置,然後再用make menuconfing命令做定製化配置。
爲了簡單,我們用make defconfig做缺省配置。
(3)用
make
命令編譯busybox軟件。
(4)用
make CONFIG_PREFIX= install
命令安裝。如果在命令行中省略CONFIG_PREFIX變量的賦值,則會安裝缺省值 ./_install 目錄下。CONFIG_PREFIX可以在make menuconfig的配置界面中修改。
我們用make CONFIG_PREFIX=~/initramfs-test/p_w_picpath 命令把busybox安裝到initramfs的構建目錄中。
(5)缺省配置下,busybox動態鏈接到glibc,所以要把它用到的動態庫複製到initramfs的構建目錄中。用ldd命令查看busybox用到了哪些動態庫文件及相應的文件路徑,然後把它們複製到相應的目錄下即可。
我們編譯的busybox需要向p_w_picpath/lib目錄下複製
ld-linux.so.2
libc.so.6
libcrypt.so.1
libm.so.6
動態庫文件。
九、在p_w_picpath下創建必要的目錄和設備文件
(1)在imgae目錄下創建
proc , sys , etc ,mnt
四個目錄
(2)hello world 已經創建了console 設備文件,我們再用
mknod -m 600 dev/null c 1 3
命令創建另一個基本的設備文件。
十、試驗一下
busybox的構建和準備工作做完了,我們試驗一下吧:
在p_w_picpath目錄下以root用戶權限——
(1)用
mount -vt proc proc =proc
mount -vt sysfs sysfs =sys
命令安裝內核虛擬文件系統
(2)用
mount -v -o bind /dev dev
命令綁定/dev的設備文件到p_w_picpath/dev
(3)用
chroot . /bin/sh
命令進入busybox的環境。出現shell的命令提示符,可以試着輸入幾個命令,看看執行結果。例如,輸入 fdisk -l 命令看看是否能顯示硬盤的分區。

(四):mini linux
十一、自動生成/dev下的設備文件

上節用chroot方法試驗busybox時,爲了簡單,是用“綁定”的方式把主機的/dev中的設備文件映射到p_w_picpath目錄下的dev目錄。在initramfs上,這種方法顯然不能使用。
生成系統的設備文件,現在通常都是用udev動態生成,而initramfs爲了做到通用,動態生成的要求是必須的。在busybox中有一個mdev命令,就是用來動態生成設備文件,填充到/dev目錄的。
在系統啓動時,用
mdev -s
命令可以根據內核的sysfs文件系統在/dev目錄中自動生成相應的設備文件。命令執行前,需要先掛載內核的proc和sysfs虛擬文件系統。
十二、初始身手
解決了自動生成設備文件的問題後,我們可以試着做一個最簡單的可運行的linux系統了:
(1)在p_w_picpath目錄下寫一個最簡單的init腳本。
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mdev -s
/bin/sh
(2)爲init腳本設置可執行權限,否則內核不會去執行它。
chmod +x init
(3)有些busybox配置中,mdev命令需要讀取/etc/mdev.conf文件,爲了避免出錯信息,我們創建一個空文件。
touch etc/mdev.conf
(4)在內核源碼目錄下,執行
make
命令,重新編譯內核,生成新的initramfs。
好了,在QEMU模擬環境下啓動這個新的內核,系統初始化後,會進入SHELL環境。在這個SHELL環境下,試驗一些常用命令,看看是否可以正常運行。
十三、can't access tty
上一步創建的簡單linux系統在進入SHELL環境時,會打出下面這一句出錯信息:
/bin/sh: can't access tty; job controll off
雖然不影響使用,但終究不夠完美。
產生這個錯誤的原因是我們的SHELL是直接運行在內核的console上的,而console是不能提供控制終端(terminal)功能的,所以必須把 SHELL運行在tty設備上,才能消除這個錯誤。解決問題的辦法是使用正規init機制,在執行SHELL前打開tty設備。
另外,這個簡單系統的reboot、halt等命令是不起作用的,也必須通過init方式解決。
十四、busybox的缺省init模式
busybox支持init功能,當系統沒有/etc/inittab文件時,它有一套缺省的模式,按下面配置執行:
::sysinit:/etc/init.d/rcS
::askfirst:/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init
如果busybox檢測到/dev/console不是串口控制檯,init還要執行下面的動作:
tty2::askfirst:/bin/sh
tty3::askfirst:/bin/sh
tty4::askfirst:/bin/sh
我們試試這種模式是否可以解決我們的問題。
(1)寫/etc/init.d/rcS腳本
這個腳本實際是要執行系統的初始化操作。我們把前面的init腳本改造一下,將最後的/bin/sh命令刪除,然後移到 etc/init.d目錄下,改名爲rcS。
(2)initramfs不需要linuxrc,而且如果沒有init文件,內核就不認爲它是一個有效的initramfs,因而不安裝它,導致內核panic。於是,我們在p_w_picpath目錄下,把busybox安裝的linuxrc改名爲init
mv linuxrc init
(3)重新編譯內核,生成新的initramfs
(4)用QEMU試驗一下新編譯的內核。系統啓動後,會打出一句話“please press Enter to active this console”——感覺還不錯。但是按下回車鍵後,系統依然會打出錯誤信息“-/bin/sh:
can't access tty; job controll off ”。用tty命令看看當前的終端設備文件名:
# tty
/dev/console
它還是console,不是tty設備,所以問題沒有解決。不過,reboot和halt命令倒是可以正常工作了。
經過驗證,busybox的缺省init模式無法滿足我們的要求,我們還是要寫inittab,定製自己的init初始化流程。
十五、busybox的inittab文件格式說明
要寫自己的inittab,需要理解busybox的inittab文件格式。
busybox的inittab文件與通常的inittab不同,它沒有runlevel的概念,語句功能上也有限制。inittab語句的標準格式是
:::
各字段的含義如下
:
id字段與通常的inittab中的含義不同,它代表的是這個語句中process執行所在的tty設備,內容就是/dev目錄中tty設備的文件名。由於是運行process的tty設備的文件名,所以也不能象通常的inittab那樣要求每條語句id的值唯一。
:
busybox不支持runlevel,所以此字段完全被忽略。
:
爲下列這些值之一:
sysinit, respawn, askfirst, wait,once, restart, ctrlaltdel, shutdown
其含義與通常的inittab的定義相同。特別提一下askfirst,它的含義與respawn相同,只是在運行process前,會打出一句話 “please press Enter to active this console”,然後等用戶在終端上敲入回車鍵後才運行process。

指定要運行的process的命令行。
十六、寫mini linux的inittab
理解了busybox的inittab格式,我們就可以寫mini linux的inittab:
::sysinit:/etc/init.d/rcS
tty1::askfirst:/bin/sh
tty2::askfirst:/bin/sh
tty3::askfirst:/bin/sh
tty4::askfirst:/bin/sh
tty5::askfirst:/bin/sh
tty6::askfirst:/bin/sh
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
把這個文件放到p_w_picpath的etc目錄下。爲了執行reboot命令時避免提示找不到/etc/fstab文件,我們再在etc目錄下創建一個空文件
touch fstab
做好了這些,就可以重新編譯內核,生成新的initramfs了。在QEMU試驗環境下驗證新生成的mini linux,系統運行正常,而且象通常的linux系統一樣,用ALT+F1~F6鍵可以在6個終端間切換。

(五)initrd
十七、配置內核支持initrd

到目前爲止,我們的initramfs都由內核編譯系統生成的,並鏈接到內核中。其實我們也可以用cpio命令生成單獨的initramfs,與內核編譯脫鉤,在內核運行時以initrd的形式加載到內核,以增加靈活性。
首先配置內核使用單獨的initrd:在 Device Driver / Block device / 配置目錄下,選擇 RAM filesystem and RAMdisk ( initramfs/initrd ) support 配置項;再到 General Setup 配置目錄項下,將 initramfs source file(s) 配置項原有的內容清空。然後把內核源碼樹的usr目錄下已由內核編譯生成的initramfs文件initramfs_data.cpio.gz拷貝到 ~/initramfs-test 目錄下,我們先直接用這個文件試驗一下 initrd 方式的initramfs的效果。最後,執行make命令重新編譯內核後,在QEMU試驗環境中,把initrd配置框(linux配置框的下面)的內容寫爲 ~/initramfs-test/initramfs_data.cpio.gz,指定initrd的文件路徑。
好了,試驗一下新的initrd方式的initramfs吧,效果跟先前的完全一樣。
十八、用cpio命令生成initramfs
cpio 命令有三種操作模式:copy-out、copy-in、copy-pass,生成initramfs用的是它的copy-out模式,即把文件打包的操作模式。cpio的copy-out操作模式使用 -o 命令行選項指定。缺省情況下,cpio從標準輸入讀取輸入數據,向標準輸出寫入輸出數據。使用 -I 選項可以指定文件名代替標準輸入,使用 -O 選項可以指定文件名代替標準輸出,而 -F 選項指定的文件名則根據cpio操作模式的不同可代替標準輸入或標準輸出。
把~/initramfs-test/p_w_picpath目錄下的文件打包成initramfs,執行下面的命令:
find . | cpio -o -H newc | gzip > ../p_w_picpath.cpio.gz
命令執行完畢後,在~/initramfs-test目錄下就會生成文件名爲imgae.cpio.gz的initramfs。
上面cpio命令的 -H 選項指定打包文件的具體格式,要生成initramfs,只能用newc 格式,如果使用其他格式,內核會打出這樣的出錯信息:Unpacking initramfs... kernel panic - not syncing: no cpio magic
在QEMU試驗環境下試驗一下新的initrd方式的initramfs,效果跟先前的完全一樣。
十九、cpio命令的其他用法
如果我們要解開一個cpio格式的打包文件,則要使用cpio命令的copy-in操作模式。cpio的copy-out操作模式使用 -i 命令行選項指定。例如,我們想把前一步從內核源碼樹 usr目錄下拷貝的initramfs_data.cpio.gz 展開到~/initramfs-test/initramfs_data目錄下,則使用下列命令:
mkdir ~/initramfs-test/initramfs_data
cd ~/initramfs-test/initramfs_data
cpio -i -F ../initramfs_data.cpio.gz --no-absolute-filename
命令執行完畢後,initramfs_data目錄下出現多個目錄和文件,用diff命令比較initramfs_data與p_w_picpath目錄,兩者的完全一樣。
上面cpio命令的 --no-absolute-filename 選項的作用是展開文件時,去掉文件路徑最前面的"/",把絕對路徑名變爲相對路徑名。內核編譯時生成的initramfs使用了絕對路徑名,所以這個選項必須使用,否則initramfs內文件展開到"/"目錄去了,如果你是root用戶或有"/"目錄的寫權限,那麼展開的文件就有可能覆蓋同名的文件(在文件修改時間新於原有文件),那就糟糕了!
展開文件前,你可能會想先看看打包文件裏都有哪些文件,這時就要用 -t 選項了。例如,我們想看看內核編譯時生成的initramfs_data.cpio.gz中都有哪些文件,我們就可以用下面的命令:
zcat initramfs_data.cpio.gz | cpio -t
在標準輸出中打出文件名列表。
使用 -v 選項可以在cpio命令執行時輸出詳細信息:在打包或展開文件時,輸出已處理的文件名;與 -t 選項連用時,則顯示文件的詳細信息,類似 ls -l 的輸出內容。-V 選項則用打點的方式,顯示cpio命令的執行進度信息,一個點代表處理一個文件。
(六)switch_root
二十、switch_root 命令

除了基於initramfs的系統(如第四節的mini linux),通常initramfs都是爲安裝最終的根文件系統做準備工作,它的最後一步需要安裝最終的根文件系統,然後切換到新根文件系統上去。以往的基於ramdisk 的initrd 使用pivot_root命令切換到新的根文件系統,然後卸載ramdisk。但是initramfs是rootfs,而rootfs既不能 pivot_root,也不能umount。爲了從initramfs中切換到新根文件系統,需要作如下處理:
(1)刪除rootfs的全部內容,釋放空間
find -xdev / -exec rm '{}' ';'
(2)安裝新的根文件系統,並切換
cd /newmount; mount --move . /; chroot .
(3)把stdin/stdout/stderr 附加到新的/dev/console,然後執行新文件系統的init程序
上述步驟比較麻煩,而且要解決一個重要的問題:第一步刪除rootfs的所有內容也刪除了所有的命令,那麼後續如何再使用這些命令完成其他步驟?busybox的解決方案是,提供了switch_root命令,完成全部的處理過程,使用起來非常方便。
switch_root命令的格式是:
switch_root [-c /dev/console] NEW_ROOT NEW_INIT [ARGUMENTS_TO_INIT]
其中NEW_ROOT是實際的根文件系統的掛載目錄,執行switch_root命令前需要掛載到系統中;NEW_INIT是實際根文件系統的init程序的路徑,一般是/sbin/init;-c /dev/console是可選參數,用於重定向實際的根文件系統的設備文件,一般情況我們不會使用;而ARGUMENTS_TO_INIT則是傳遞給實際的根文件系統的init程序的參數,也是可選的。

需要特別注意的是:switch_root命令必須由PID=1的進程調用,也就是必須由initramfs的init程序直接調用,不能由init派生的其他進程調用,否則會出錯,提示:
switch_root: not rootfs
也是同樣的原因,init腳本調用switch_root命令必須用exec命令調用,否則也會出錯,提示:
switch_root: not rootfs
二十一、實踐:用initramfs安裝CLFS根文件系統
現在實踐一下switch_root命令,用它切換一個CLFS的根文件系統硬盤分區。我的CLFS安裝在/dev/sda8硬盤分區,我們就以此爲例說明。
我們還是在以前的p_w_picpath目錄中構建
(1)改寫init腳本
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mdev -s
mount /dev/sda8 /mnt (注意:爲了簡單,我們直接把CLFS分區寫死在init腳本中了)
exec switch_root /mnt /sbin/init
(2)生成新的initrd
按上一節
“精通initramfs構建step by step (五):initrd”
描述的cpio命令生成新的initrd。
(3)把新的initrd拷貝到CLFS分區的/boot目錄下,改名爲clfs-initrd
(4)在GRUB的menu.lst配置文件中增加一個啓動項
#test for initramfs of CLFS
title test for initramfs of CLFS (on /dev/sda8)
root (hd0,7)
kernel /boot/clfskernel-2.6.17.13 (注意:並沒有向內核傳遞root參數信息)
initrd /boot/clfs-initrd
全部做完後,重啓機器,選擇 test for initramfs of CLFS 啓動項,機器順利進入了CLFS系統,我們構建的initramfs用switch_root命令完成了CLFS實際根文件系統的安裝和切換。

(七)modules
二十二、內核模塊支持

到目前爲止,我們在構建initramfs時還沒有涉及內核模塊的支持,所用到的硬件驅動程序都是直接編譯到內核中。現在我們就看看如何使initramfs支持內核模塊。
首先,內核配置要支持模塊,並支持內核模塊的自動加載功能:在內核配置菜單中的激活下面的配置項,編譯進內核 Load module support / Enable loadable module support / Automatic kernel loading ;
然後把需要的硬件驅動程序配置模塊形式,比如把我的機器上的硬盤控制器的驅動編譯成模塊,則選擇
Device Driver
|---->SCSI device support
|---->SCSI disk support
|----->verbose SCSI error reporting (不是必須的,但可方便問題定位)
|----->SCSI low-level drivers
|---->Serial ATA (SATA) support
|---->intel PIIX/ICH SATA support
把它們配置成模塊。
最後,編譯內核,並把編譯好的內核模塊安裝到p_w_picpath的目錄下:
make
make INSTALL_MOD_PATH=~/initramfs-test/p_w_picpath modules_install
命令執行完畢後,在p_w_picpath/lib/modules/2.6.17.13/kernel/drivers/scsi目錄下安裝了4個內核模文件:scsi_mod.ko、sd_mod.ko、ata_piix.ko、libata.ko,它們就是所需的硬盤控制器的驅動程序。
好了,都準備好了,可以用cpio命令生成inintramfs了。不過,爲了方便後面的試驗,我們再把init腳本改成
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mdev -s
exec /bin/sh
使系統啓動後進入shell環境,並且用exec調用的方式,使shell的pid爲1,能夠執行switch_root命令。
二十三、試驗:用initramfs中的內核模塊安裝硬盤文件系統
用新生成的initramfs啓動系統,內核並沒有自動加載硬盤控制器的驅動程序,所以 /dev目錄下也沒有sda等硬盤設備文件。好吧,我們自己加載內核模塊文件。不幸的是,busybox的modprobe命令執行不正常,不能加載內核模塊。懷疑是busybox的modprobe命令配置或編譯有問題,後續再花時間定位吧,先用insmod命令依次加載。查看/lib/modules /2.6.17.13/modules.dep,弄清楚了4個模塊的依賴關係,執行下面的命令加載:
insmod scsi_mod
insmod libata
insmod ata_piix
insmod sd_mod
然後再用
mdev -s
命令生成硬盤的設備文件。
好了,可以安裝CLFS的硬盤分區,並把根文件系統切換到CLFS的硬盤分區:
mount /dev/sda8 /mnt
exec switch_root /mnt /sbin/init
系統正常啓動到了CLFS,我們可以做到用initramfs中的硬盤控制器的驅動模塊安裝硬盤分區了。
二十四、mdev的hotplug模式
上面的試驗中,我們在加載完驅動模塊後調用了mdev -s 命令來生成硬盤的設備文件。其實,可以使用mdev的hotplug模式在加載內核時自動生成對應的設備文件:
在執行insmod命令前,用
echo /sbin/mdev > /proc/sys/kernel/hotplug
命令設置系統的hotplug程序爲mdev。
後續使用insmod命令加載模塊時,系統自動調用mdev生成相應的設備文件。
注意:內核必須配置支持hotplug功能,而前面提到的CLFS最簡內核配置方案是沒有配置hotplug支持的。
(八)coldplug
二十五、udev的coldplug模式

內核在啓動時已經檢測到了系統的硬件設備,並把硬件設備信息通過sysfs內核虛擬文件系統導出。udev掃描sysfs文件系統,根據硬件設備信息生成熱插拔(hotplug)事件,udev再讀取這些事件,生成對應的硬件設備文件。由於沒有實際的硬件插拔動作,所以這一過程被稱爲coldplug。我們的initramfs就是利用這一機制,加載硬件設備的驅動程序模塊。
udev完成coldplug操作,需要下面三個程序:
udevd——作爲deamon,記錄hotplug事件,然後排隊後再發送給udev,避免事件衝突(race conditions)。
udevtrigger——掃描sysfs文件系統,生成相應的硬件設備hotplug事件。
udevsettle——查看udev事件隊列,等隊列內事件全部處理完畢才退出。
在initramfs的init腳本中可以執行下面的語句實現coldplug功能:
mkdir -p /dev/.udev/db
udevd --daemon
mkdir -p /dev/.udev/queue
udevtrigger
udevsettle
許多文檔提到的在udevd --daemon 命令前要執行
echo > /proc/sys/kernel/hotplug
命令,經驗證,在我們的initramfs環境下的coldplug功能中並不需要。
二十六、試驗:用udev自動加載設備驅動模塊
瞭解了udev的coldplug的機理,我們就試驗一下用udev自動加載設備驅動模塊,並生成硬件設備文件。
(1)從 /sbin 目錄下拷貝udevd、udevtrigger、udevsettle程序到p_w_picpath目錄下的sbin目錄下,並用ldd命令找到它們所需要的動態庫文件,拷貝到p_w_picpath目錄下的lib目錄下。
(2)修改init腳本,增加coldplug功能:
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mdev -s
#using udev autoload hard disk driver module
mkdir -p /dev/.udev/db
udevd --daemon
mkdir -p /dev/.udev/queue
udevtrigger
udevsettle
mount /dev/sda8 /mnt
killall udevd
exec switch_root /mnt /sbin/init
注意:在切換到真正根文件系統前,要把udevd進程殺掉,否則會和真正根文件系統中的udev腳本的執行相沖突。這就是上面killall udevd 語句的作用。
(3)編寫udev規則文件
規則文件是udev的靈魂,沒有規則文件,udev無法自動加載硬件設備的驅動模塊。爲了簡單,我們直接使用CLFS中的40- modprobe.rules,把它拷貝到p_w_picpath目錄下的etc/udev/rules.d目錄。有關udev的規則文件編寫,已超出了本文的範圍,後續我有可能專文描述。
########################################################################
#
# Description : 40-modprobe.rules
#
# Authors : Based on Open Suse Udev Rules
#
[email protected]

#
# Adapted to : Jim Gifford
# LFS : Alexander E. Patrakov
#
# Version : 00.01
#
# Notes :
#
########################################################################
# hotplug
ENV{MODALIAS}=="?*", RUN+="/sbin/modprobe $env{MODALIAS}"
# scsi
SUBSYSTEM=="scsi_device", ACTION=="add", SYSFS{device/type}=="0|7|14", RUN+="/sbin/modprobe sd_mod"
SUBSYSTEM=="scsi_device", ACTION=="add", SYSFS{device/type}=="1", SYSFS{device/vendor}=="On[sS]tream", RUN+="/sbin/modprobe osst"
SUBSYSTEM=="scsi_device", ACTION=="add", SYSFS{device/type}=="1", RUN+="/sbin/modprobe st"
SUBSYSTEM=="scsi_device", ACTION=="add", SYSFS{device/type}=="[45]", RUN+="/sbin/modprobe sr_mod"
SUBSYSTEM=="scsi_device", ACTION=="add", RUN+="/sbin/modprobe sg"
# floppy
KERNEL=="nvram", ACTION=="add", RUN+="load_floppy_module.sh"
注意:上面的
ENV{MODALIAS}=="?*", RUN+="/sbin/modprobe $env{MODALIAS}"
語句是實現自動加載硬件設備驅動模塊功能的關鍵,它根據sysfs文件系統中記錄的模塊aliases數據,用modprobe命令加載對應的內核模塊。有關模塊aliases的進一步說明,可參考CLFS手冊(CLFS-1.0.0-x86)中的11.5.2.4. Module Loading一節的描述。
(4)拷貝modprobe命令
前一節提到過,busybox的modprobe 命令不能正常使用,所以我們需要拷貝 /sbin 目錄下的modprobe命令到p_w_picpath目錄下的sbin目錄,供udev加載內核模塊使用。再用ldd命令檢查一下 /sbin/modprobe 命令所需的動態庫文件,如果有則拷貝到p_w_picpath/lib目錄下。(我的檢查結果是,除了libc6外,不需要其他動態庫,所以不需要拷貝)
好了,重新生成initramfs,啓動CLFS系統,initramfs能夠自動加載硬盤設備的驅動模塊,系統順利地從initramfs切換到了真正的CLFS的根文件系統。

(九)內核編譯時構建initramfs補遺
二十七、直接把cpio打包文件編譯進內核

如果我們有一個已經做好的cpio格式的initramfs,可以在內核編譯時直接編譯進內核。回憶一下
第一節
的內容,我們在內核配置參數中的initramfs sources配置項下輸入構建initramfs的目錄路徑。其實我們也可以直接輸出現成的initramfs的文件名,這樣在內核編譯時,就可以把它編譯進內核了。
使用這種方法,有兩點需要注意:
(1)cpio文件不能壓縮。一般作爲initrd的cpio文件都經過了壓縮,所以編譯前需要先把壓縮過的文件解壓。
(2)cpio文件的後綴名必須是 .cpio。內核編譯通過 .cpio的後綴名來識別此文件是cpio打包文件,而其他文件後綴名則會被認爲是initramfs構建的描述文件(關於描述文件,下面後詳細說明)。
二十八、用描述文件構建initramfs
用內核編譯工具構建initramfs的第三種方法是使用描述文件。在內核配置參數中的initramfs sources配置項下可以輸入initramfs構建描述文件的文件名,內核編譯工具根據描述文件完成initramfs的構建。
描述文件的語法格式的說明如下:
# a comment
file
dir
nod
slink
pipe
sock
name of the file/dir/nod/etc in the archive
location of the file in the current filesystem
link target
mode/permissions of the file
user id (0=root)
group id (0=root)
device type (b=block, c=character)
major number of nod
minor number of nod
例子:
我們用描述文件的方式,構建第一節中的hello world的initramfs。
hello-init.desp:
dir /dev 0755 0 0
nod /dev/console 0600 0 0 c 5 1
file /init /home/wyk/initramfs-test/hello_static 0755 0 0
在內核配置項initramfs sources中指定描述文件hello-init.desp,編譯內核時就會生成hello world的initramfs,運行效果與第一節用指定構建目錄的方法構建的initramfs的完全相同。
注意:在內核幫助文件中,提到initramfs sources配置項可以指定多個目錄或描述文件,內核會彙集這些目錄或文件生成一個initramfs。但從我的試驗來看,initramfs sources只接受單一的目錄名或文件名,輸出多個目錄名或文件名(之間用空格分隔),內核編譯時就會出錯。也許是我的方法有誤,還望讀者指正。

(十)uclibc
二十九、toolchain

在initramfs中使用uclibc庫,關鍵是構建uclibc的工具鏈toolchain。構建uclibc 的 toolchain 有兩種主要方式:(1)用buildroot工具(
http://buildroot.uclibc.org/
)自動構建,這也是uclibc的官方標準做法。(2)用CLFS Embedded手冊的方法手工創建。目前CLFS Embedded還在開發中,可在
http://cross-lfs.org/view/clfs-embedded/x86/
中查閱。
我們簡單地說明用buildroot工具構建uclbic的toolchain的步驟:
(1)獲取buildroot。
推薦用svn命令從它的版本庫中下載:
svn co svn://uclibc.org/trunk/buildroot
要求使用svn命令,需要先安裝subversion軟件包。下載過程中,可能會出現連接異常中斷的情況,這時重新執行上述命令,繼續進行下載,有可能要重複多次。
(2)配置buildroot
因爲我們只是創建toolchain,所以需要做相應的配置。在buildroot的頂層目錄下,執行
make menuconfig
命令,在缺省配置的基礎上做如下配置
Target Architecture: i386
Target Architecture Variant: i686
Package Selection for the target: 取消BusyBox的選項(缺省是選中的)
Target filesystem options: 取消 ext2 root filesystem(缺省是選中的)
Toolchain --> Toolchain type: Buildroot toolchain
(3)編譯
執行
make
命令,buildroot工具會自動下載所需要的源文件並自動編譯,等一兩個小時後,toolchain就編譯好了。編譯好的toolchain位於
buildroot/build_i686/staging_dir/usr/bin/
目錄下。工具命令的前綴是 i686-linux- 。
三十、編譯Busybox靜態連接uclibc庫
一般而言,使用uclibc庫是爲了把它靜態連接到busybox中。具體步驟是:
(1)把uclibc toolchain的目錄添加到PATH中。
在~/.bash_profile文件中添加:
#set PATH so it includes uclibc toolchain if it exist
if [ -d ~/buildroot/build_i686/staging_dir/usr/bin ] ; then
PATH="${PATH}":~/buildroot/build_i686/staging_dir/usr/bin
fi
(2)配置busybox靜態連接庫。
在busybox的配置界面中,選擇:
Build Options --> Build BusyBox as a static binary (no shared libs)
(3)編譯
執行
make CROSS_COMPILE=i686-linux-
命令“交叉編譯”busybox。
最後編譯生成的是靜態連接的可執行文件,不需要在initramfs中拷貝庫文件。
三十一、用buildroot自動構建initramfs
buildroot 工具實際是一個功能強大的根文件系統構建工具,它以uclibc和busybox作爲系統構建的基礎,toolchain只是它構建系統的中間產品。 initramfs是一種特殊的根文件系統,當然也可以用buildroot工具自動構建,下面是構建方法的簡要描述:
(1)配置
在buildroot的配置界面下做如下的配置:
Package Selection for the target: 選擇
Busybox
Run Busybox's own full installation
Use minimal target skeleton
Target filesystem options --> cpio the root filesystem --> comprassion method: gzip
(2)編譯
執行
make
命令,進行編譯。
(3)輸出
構建好的cpio文件是
buildroot/binaries/rootfs.i686.cpio.gz
同一目錄下還包含一個未壓縮的文件:rootfs.i686.cpio
構建目錄則是
buildroot/project_build_i686/uclibc/root
可以在這個目錄下對原始的initramfs進行修改調整,然後自己用cpio命令打包生成新的initramfs。
(4)調整
直接用buildroot生成的root.i686.cpio.gz作爲initramfs,運行時會出現
can't open /dev/tty1: No such file or directory
can't open /dev/tty2: No such file or directory
can't open /dev/tty3: No such file or directory
錯誤信息的循環輸出,系統不能正常運行。
錯誤的原因是沒有在initramfs的/dev目錄下生成相應的設備文件。需要做如下的調整:
1)在構建目錄(buildroot/project_build_i686/uclibc/root)下的etc/init.d目錄中新增一個初始化腳本文件S10mountfs
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mdev -s
2)更改busybox的setuid屬性,否則無法執行mount命令。在構建目錄(buildroot/project_build_i686/uclibc/root)下執行
chmod -s bin/busybox
命令。
這兩項調整工作做完後,在構建目錄(buildroot/project_build_i686/uclibc/root)下執行
find . | cpio -o -H newc |gzip > ../initramfs.cpio.gz
命令,重新生成initramfs的cpio打包文件。
(5)運行效果
運行新的initramfs,系統出現登錄提示。輸入用戶名 root,密碼爲空,即可進入一個mini的linux系統。
buildroot是功能強大、配置靈活的自動化構建工具,它的詳細使用和配置方法超出了本文的範圍,後續可能會專文描述,此處就從略了。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章