busybox製作initrd.img和根文件系統
參考書籍:《深度探索Linux操作系統 系統構建和原理解析》
參考博客:https://blog.csdn.net/mao0514/article/details/51248738
(一)開發環境介紹
1.使用win7_64的筆記本安裝Virtualbox虛擬機,筆記本cpu爲i5-2450m。虛擬機上安裝Ubuntu16.04系統作爲編譯環境,同時虛擬機也作爲最小系統運行環境。
2.最小系統使用Linux內核版本選擇4.15.0,內核配置使用x86_64默認配置(即使用命令make x86_64_defconfig)。busybox選擇版本1.30.0,使用busybox製作initrd.img,並且同使用busybox製作根文件系統,busybox編譯配置相同,只需要在默認配置上修改爲靜態鏈接(setting–Build static binary )。
3.在根目錄新建文件夾/vita作爲新系統啓動分區,使用gparted分配10G(大小隨便)給新分區,新分區設備文件爲/dev/sda3。將新分區掛載在/vita目錄,在此目錄下新建/boot用於存放bzImage和initrd.img。同時/vita也存放根文件系統。製作好的目錄如下:
(二)製作initrd.img
1.下載busybox包,解壓,配置,編譯,安裝等步驟之後,會在當前目錄下生產_install目錄,裏面有一些不依賴其他庫的靜態進程,可以直接運行。
我們首先將_install拷貝到initramfs目錄用於生成initrd.img
mkdir initramfs
cp -R busybox-1.30.0/_install/* initramfs/
cd initramfs
2.測試initrd。由於內核將initrd.img鏡像解壓到根文件系統後就將運行權交於initrd.img中的/init程序,所以我們可以自行選擇是否要加載根文件系統,可以不加載根文件系統直接使用initrd.img裏的內容做根文件系統,下面進行兩種測試,單獨運行initrd.img。
(1)測試一:使用linuxrc的內容作爲啓動腳本
① 由於我們使用initramfs,所以內核運行時是找init文件而不是linuxrc,所以直接將linuxrc改名爲init
mv linuxrc init
這裏可以稍微解釋下linuxrc的運行原理,在busybox的源碼下/init/init.c有如下內容:
表明linuxrc的作用和init一樣,也就是執行linuxrc就是執行/sbin/init。我們知道busybox使用軟鏈接將一個進程鏈接成多個進程,然後通過main函數args的第一個參數(也就是進程名)來區分具體執行哪一個,我們直接把文件名該爲init其實就相當於執行了/sbin/init程序。
② 新建文件夾,添加配置文件
mkdir dev etc lib mnt proc sys
配置文件可以選擇busybox裏面的例子,在busybox-1.30.0\examples\bootfloppy\etc目錄下,可以找到相關文件
將這些文件拷貝到initramfs/etc下,於是etc結構如下。
cp -R ../busybox-1.30.0/examples/bootfloppy/etc/* etc/
之所以要添加配置文件是因爲/init程序等同於/sbin/init,瞭解linux system V啓動方式的同學肯定知道,/sbin/bin會根據/etc/inittab的配置內容進行相關配置,如果這裏不進行配置運行內核時會首先提示找不到rcS文件,也即“can’t run ‘/etc/init.d/rcS’: No such file or directory”。
這裏稍微解釋下各個文件內容
etc/inittab
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
tty2::askfirst:-/bin/sh
::ctrlaltdel:/bin/umount -a -r
簡單說就是每一行表示一種執行策略,例如第一行表示在系統啓動時執行RCS,第二行表示如果sh掛了會自動重啓sh,第三行表示使用tty2啓動的sh啓動時會首先詢問,第四行表示使用ctrl+alt+del快捷鍵時執行的程序。這裏我們改下配置內容
::sysinit:/etc/init.d/rcS
console::respawn:-/bin/sh
將sh啓動的控制程序交給console,並且刪除其他操作。
etc/fstab
proc /proc proc defaults 0 0
fstab表示系統啓動時默認掛載的文件系統,通常可以把一些特殊文件系統掛載下,例如/proc,/sysfs
etc/profile
# /etc/profile: system-wide .profile file for the Bourne shells
echo
echo -n "Processing /etc/profile... "
# no-op
echo "Done"
echo
profile通常是使用tty並進行登錄時纔會用到,profile是通常會調用~/.bashrc的配置修改用戶環境變量。
etc/init.d/rcS
#! /bin/sh
/bin/mount -a
rcS在這裏是作爲inittab的啓動腳本
③ 添加控制檯設備文件
內核在準備好initramfs後會判斷/dev/console是否存在,不存在就報錯,存在就會繼續執行/init並將以後的輸出輸出到控制檯中。
mknod dev/console c 5 1
mknod dev/null c 1 3
這裏我進行了刪除測試,發現即使沒有新建這兩個設備結點也能正常運行,在查閱《深度探索Linux操作系統》後,發現裏面有解答,除了我們自己創建的initrd.img外,內核會自己創建一個內置的initramfs文件系統,並在內置initramfs中執行命令創建console設備結點。所以添加設備結點這一步我們可以省略。
④ 配置grub,在/boot/grub.cfg中先新建菜單項,可以參考我的上一篇博客“Linux內核學習(3)最小系統制作”,再配置initrd的參數,表示initrd.img的位置,例如:
⑤ 生成initrd.img文件,並將此文件拷貝到/boot下,最後再重啓
find . |cpio -o -H newc|gzip ../initrd3.img
cp ../initrd3.img /vita/boot
⑥ 在grub菜單中選擇自己新建的菜單
啓動後結果如下,執行下ls,發現只有initrd.img裏面的內容,而不是根文件系統/vita下的內容
(2)測試二:自定義/init腳本
① 可以直接在上述基礎上進行,也就是修改/init的內容。
rm init
vim init
init:
#!/bin/sh
#
echo "exec initramfs init"
echo "mounting proc and sys"
mount -t proc proc /proc
mount -t sysfs sysfs /sys
echo "detect and export hardware info"
mdev -s
echo "start /bin/sh"
exec /bin/sh
這裏注意不能直接執行/bin/sh,需要先先掛載proc和sysfs,然後創建設備結點,否則會造成如下錯誤,表示init執行錯誤。mdev的作用是將sysfs文件系統下掃描的設備在/dev下創建設備結點。
② 修改init後的步驟和上述“測試一”一樣,重啓後顯示如下
結果雖然執行了sh,但是仍有提示“can’t access tty”,而且部分busybox功能失效,例如執行reboot無任何反映,應該是未執行相關初始化的原因。但是我們這裏只是測試,所以無需糾結。
3.使用initrd啓動根文件系統
(1)當initrd中充當掛載根文件系統的橋樑時,這時initramfs/etc下可以不需要配置(這裏我們依舊保留,其實沒有使用),而應當把配置文件移動到根文件系統中。
(2)修改initramfs/init啓動腳本
vim init
init:
#!/bin/sh
#
echo "exec initramfs init"
echo "mounting proc and sys"
mount -t proc proc /proc
mount -t sysfs sysfs /sys
echo "detect and export hardware info"
mdev -s
echo "Mount real rootfs to /mnt/sysroot..."
mount -t ext4 /dev/sda3 /mnt/sysroot
echo "Switch to read rootfs..."
exec switch_root /mnt/sysroot /sbin/init
這裏我們多執行了兩步操作,掛載根文件系統/dev/sda3到/mnt/sysroot目錄,執行switch_root切換根文件系統目錄到/mnt/sysroot下,並且執行/sbin/init。
(三)製作根文件系統
1.製作根文件系統和initrd.img步驟相差無幾,首先是一樣的拷貝基本文件。
cp -R ../busybox-1.30.0/_install/* /vita
2.配置文件
cp -R etc/* /vita/etc/
3.創建相關文件,刪除無用文件
cd /vita
mkdir -p dev etc lib mnt/sysroot proc sys
rm linuxrc
4.修改下fstab和rcS,使用fstab添加sysfs的掛載,使用rcS自動創建設備結點
vim etc/fstab
fstab:
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
修改rcS:
vim etc/init.d/rcS
rcS:
#!/bin/sh
/bin/mount -a
mdev -s
5.重啓
可以看到log都正確的打印,輸入ls後看到的目錄的確是/vita目錄,也就是正確切換到根文件系統了。
(四)常見錯誤
1.“Failed to create /dev/root:”,這種錯誤有時候會被誤解爲驅動問題,其實查看kernel源碼會發現有如下調用順序:
可以看到,如果init文件不存在(沒指定參數時默認啓動init),內核會主動掛載根文件系統,但是如果沒有進行任何先前動作就去掛載一般都會失敗,例如需要先進行掛載/proc和/sysfs。所以如果我們忘記在initrd.img中寫/init啓動腳本會出現這種錯誤。
2.“can’t run ‘/etc/init.d/rcS’: No such file or directory”,這種錯誤比較常見,但是經常不那麼容易弄懂。
我這裏說一種我出現的問題,花了比較長的時間才解決。
首先我的根文件系統明明已經寫了rcS而且也沒有任何亂碼,仔細檢查根文件系統明明沒有任何問題。這時我突然想到,如果在initrd.img/init腳本中無意間執行了initrd.img中的/sin/init程序,那麼此程序會檢查有沒有initrd.img/etc/inittab配置,如果沒有此配置文件,會默認執行initrd.img/etc/init.d/rcS腳本,所以如果initrd.img/etc下什麼文件都沒有,那麼首先報的問題就是缺少initrd.img/etc/init.d/rcS。但是後來我仔細檢查initrd.img/init內容發現並沒有執行到initrd.img/sbin/init。雖然沒有解決問題,但是我卻發現了另外一個問題,initrd.img/init文件沒有執行權限,也就是如下:
可以看到init文件的權限沒有可執行權限,但是這個問題會影響到rcS嗎,我又研究了一下kernel的源碼,發現有如下片段:
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
panic("Requested init %s failed (error %d).",
execute_command, ret);
}
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
ramdisk_execute_command變量在默認情況下是/init,這段代碼就是當/init執行不成功時會繼續執行/sbin/init,而這時就會執行到上述initrd.img/sbin/init中,所以就會出現上述錯誤。
明白錯誤原理後只需要修改init權限,給所有用戶添加執行:
chmod a+x init
重新重啓後就正常了,這個問題其實不難解決,難的是報的問題非常奇怪不容易弄懂原因。