摘要:對于Linux使用者來說,Ramdisk并不陌生,可是為什麼需要它呢?本文對Ramdisk在核心啟動過程中的作用,以及它的内部機制進行深入介紹。
标題
initrd 和 initramfs在核心中的處理
臨時的根目錄rootfs的挂載
initrd的解壓縮
老式的initrd的處理
cpio格式的initrd的處理
initrd執行個體分析
在早期的Linux系統中,一般就隻有軟碟或者硬碟被用來作為Linux的根檔案系統,是以很容易把這些裝置的驅動程式內建到核心中。但是現在根檔案系統可能儲存在各種儲存設備上,包括SCSI, SATA, U盤等等。是以把這些裝置驅動程式全部編譯到核心中顯得不太友善。在Linux核心子產品自動加載機制的介紹中,我們看到利用udevd可以實作實作核心子產品的自動加載,是以我們希望根檔案系統的裝置驅動程式也能夠實作自動加載。但是這裡有一個沖突,udevd是一個可執行檔案,在根檔案系統被挂載前,是不可能執行udevd的,但是如果udevd沒有啟動,那就無法自動加載根根據系統裝置的驅動程式,同時也無法在/dev目錄下建立相應的裝置節點。為了解決這個沖突,于是出現了initrd(boot loader initialized RAM disk)。initrd是一個被壓縮過的小型根目錄,這個目錄中包含了啟動階段中必須的驅動子產品,可執行檔案和啟動腳本。包括上面提到的udevd,當系統啟動的時候,booload會把initrd檔案讀到記憶體中,然後把initrd的起始位址告訴核心。核心在運作過程中會解壓initrd,然後把 initrd挂載為根目錄,然後執行根目錄中的/initrc腳本,您可以在這個腳本中運作initrd中的udevd,讓它來自動加載裝置驅動程式以及在/dev目錄下建立必要的裝置節點。在udevd自動加載磁盤驅動程式之後,就可以mount真正的根目錄,并切換到這個根目錄中。
您可以通過下面的方法來制作一個initrd檔案。
# dd if=/dev/zero of=initrd.img bs=4k count=1024
# mkfs.ext2 -F initrd.img
# mount -o loop initrd.img /mnt
# cp -r miniroot char *err = unpack_to_rootfs(__initramfs_start, __initramfs_end - __initramfs_start, 0); if (err) panic(err);#ifdef CONFIG_BLK_DEV_INITRD if (initrd_start) {#ifdef CONFIG_BLK_DEV_RAM int fd; printk(KERN_INFO "checking if image is initramfs..."); err = unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start, 1); if (!err) { printk(" it is/n"); unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start, 0); free_initrd(); return 0; } printk("it isn't (%s); looks like an initrd/n", err); fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 0700); if (fd >= 0) { sys_write(fd, (char *)initrd_start, initrd_end - initrd_start); sys_close(fd); free_initrd(); } ...... return 0;}rootfs_initcall(populate_rootfs);經過populate_rootfs()函數的處理之後,如果是cpio格式的initrd,那麼unpack_to_rootfs()函數已經把目錄解壓縮到之前mount的根目錄上面了。但是如果是舊的塊裝置的initrd,unpack_to_rootfs()函數解壓縮後得到的是一個塊虛拟的裝置鏡像檔案/initrd.image,對于這種情況,還需要進一步處理才能使用。接下來,kernel_init()就要處理這種情況。
static int __init kernel_init(void * unused){ ...... do_basic_setup(); if (!ramdisk_execute_command) ramdisk_execute_command = "/init"; if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) { ramdisk_execute_command = NULL; prepare_namespace(); } init_post(); return 0;}老式的initrd的處理
prepare_namespace()用于處理老式的initrd。
[kernel_init() -> prepare_namespace() -> initrd_load()]int __init initrd_load(void){ if (mount_initrd) { create_dev("/dev/ram", Root_RAM0); if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) { sys_unlink("/initrd.image"); handle_initrd(); return 1; } } sys_unlink("/initrd.image"); return 0;}initrd_load()執行以下步驟:
調用create_dev()建立裝置檔案節點/dev/ram,其實這也是一個ramfs檔案系統。
調用rd_load_image()把/initrd.image加載到/dev/ram中。
調用handle_initrd()把把塊裝置檔案/dev/ram挂載到/root。
其中handle_initrd()代碼如下:
[kernel_init() -> prepare_namespace() -> initrd_load() -> handle_initrd()]static void __init handle_initrd(void){ ...... real_root_dev = new_encode_dev(ROOT_DEV); create_dev("/dev/root.old", Root_RAM0); mount_block_root("/dev/root.old", root_mountflags & ~MS_RDONLY); sys_mkdir("/old", 0700); root_fd = sys_open("/", 0, 0); old_fd = sys_open("/old", 0, 0); sys_chdir("/root"); sys_mount(".", "/", NULL, MS_MOVE, NULL); sys_chroot("."); current->flags |= PF_FREEZER_SKIP; pid = kernel_thread(do_linuxrc, "/linuxrc", SIGCHLD); ......}cpio格式的initrd的處理
對于新的cpio格式的initrd不需要額外的處理,是以kernel_init()繼續執行:
[kernel_init() -> init_post()]static int noinline init_post(void){ ...... if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) printk(KERN_WARNING "Warning: unable to open an initial console./n"); (void) sys_dup(0); (void) sys_dup(0); if (ramdisk_execute_command) { run_init_process(ramdisk_execute_command); printk(KERN_WARNING "Failed to execute %s/n", ramdisk_execute_command); } ...... run_init_process("/sbin/init"); run_init_process("/etc/init"); run_init_process("/bin/init"); run_init_process("/bin/sh"); panic("No init found. Try passing init= option to kernel.");}在調用run_init_process()執行/init之後,這個函數就不會傳回了,一般的發行版本的Linux中,initrd中的/init腳本會啟動udevd,加載必要的裝置驅動程式,然後挂載真正的根檔案系統,最後在執行真正的根檔案系統上的initrd,這樣就這個啟動過程就順利的交接了。
塊裝置的initrd不僅使用不友善,而且在核心中的處理過程也更加複雜,是以cpio的initrd肯定會取代它,推薦使用cpio格式的initrd.
initrd執行個體分析
如果您使用的是ubuntu,您可以執行以下的指令來看看它的initrd中的内容。
# mkdir /tmp/initrd# cp /boot/initrd.img-xxx /tmp/initrd/initrd.img.gz# cd /tmp/initrd# gunzip initrd.img.gz# cat initrd.img | cpio -ivmd 現在,可以來看看這個根目錄的init腳本到底做了什麼。
# cat init#!/bin/sh# ubuntu使用者一定很熟悉這個消息。echo "Loading, please wait..."......exec run-init ${rootmnt} ${init} "$@" <${rootmnt}/dev/console >${rootmnt}/dev/console 2>&1這個init腳本最後執行initrd中的run-init切換到真正的根檔案系統中。
您可以對這個腳本進行修改,加入相關的列印資訊,然後使用本文開頭介紹的方法,重新制作一個cpio的initrd,然後使用這個initrd啟動核心,快看看試驗效果吧。