天天看點

Linux核心Ramdisk(initrd)機制

摘要:對于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啟動核心,快看看試驗效果吧。

繼續閱讀