kernel initrd & ramfs

來源:互聯網
上載者:User


在早期的Linux系統中,一般就只有磁碟片或者硬碟被用來作為Linux的根檔案系統,因此很容易把這些裝置的驅動程式整合到核心中。但是現在根檔案系統可能儲存在各種存放裝置上,包括SCSI, SATA, 隨身碟等等。因此把這些裝置驅動程式全部編譯到核心中顯得不太方便。在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/* /mnt # umount /mnt # gzip -9 initrd.img
see:  Docummentation/ramdisk.txt
通過上面的命令,我們製作了一個4M的initrd,其中miniroot就是一個根目錄。最後我們得到一個名為initrd.img.gz的壓縮檔。 
利用initrd核心在啟動階段可以順利的載入裝置驅動程式,然而initrd存在以下缺點:
  • initrd大小是固定的,例如上面的壓縮之前的initrd大小是4M(4k*1024),假設您的根目錄(上例中的miniroot/)總大小僅僅是1M,它仍然要佔用4M的空間。如果您在dd階段指定大小為1M,後來發現不夠用的時候,必須按照上面的步驟重新來一次。

  • initrd是一個虛擬塊裝置,在上面的例子中,您可是使用fdisk對這個虛擬塊裝置進行分區。在核心中,對塊裝置的讀寫還要經過緩衝區管理模組,也就是說,當核心讀取initrd中的檔案內容時,緩衝區管理層會認為下層的塊裝置速度比較慢,因此會啟用預讀和緩衝功能。這樣initrd本身就在記憶體中,同時塊裝置緩衝區管理層還會儲存一部分內容。 為了避免上述缺點,於是出現了initramfs,它的作用和initrd類似,initramfs見第二部分。

 

initrd的解壓縮

在start_kernel()的最後,調用rest_init(),rest_init()會建立一個新的核心進程,並在這個核心進程中執行kernel_init()函數,kernel_init()會調用populate_rootfs()來探測和解壓initrd檔案。這個函數需要處理上面的幾種initrd的情況。

[kernel_init() -> populate_rootfs()]static int __init populate_rootfs(void){        /* 如果__initramfs_end - __initramfs_start不為0,就說明這是和核心檔案整合在一起的cpio的intrd。*/        char *err = unpack_to_rootfs(__initramfs_start,                         __initramfs_end - __initramfs_start, 0);        if (err)                panic(err);#ifdef CONFIG_BLK_DEV_INITRD        /* 如果initrd_start不為0,說明這是由bootloader載入的initrd,          * 那麼需要進一步判斷是cpio格式的initrd,還是老式塊裝置的initrd。         */             if (initrd_start) {#ifdef CONFIG_BLK_DEV_RAM                int fd;                /* 首先判斷是不是cpio格式的initrd,也就是這裡說的initramfs。*/                printk(KERN_INFO "checking if image is initramfs...");                /* 這裡unpack_to_rootfs()的最後一個參數為1,表示check only,不會執行解壓縮。*/                err = unpack_to_rootfs((char *)initrd_start,                        initrd_end - initrd_start, 1);                if (!err) {                        /* 如果是cpio格式的initrd,把它解壓到前面掛載的根檔案系統上,然後釋放initrd佔用的記憶體。*/                        printk(" it is/n");                        unpack_to_rootfs((char *)initrd_start,                                initrd_end - initrd_start, 0);                        free_initrd();                        return 0;                }                                /* 如果執行到這裡,說明這是舊的塊裝置格式的initrd。                 * 那麼首先在前面掛載的根目錄上建立一個initrd.image檔案,                 * 再把initrd_start到initrd_end的內容寫入到/initrd.image中,                 * 最後釋放initrd佔用的記憶體空間(它的副本已經儲存到/initrd.image中了。)。                 */                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();        /* 核心啟動時,可以通過啟動參數 rdinit=xxx 來指定啟動的最後階段,需要運行initrd中的哪一個可執行檔,         * 如果指定了這個參數,那麼ramdisk_execute_command就會指向xxx這字串,新cpio格式的initrd預設執行/init。         * 因此,如果如果ramdisk_execute_command為NULL, 就把它設定為/init。         */        if (!ramdisk_execute_command)                ramdisk_execute_command = "/init";        /* 現在,嘗試訪問ramdisk_execute_command,預設為/init,如果訪問失敗,說明根目錄上不存在這個檔案。          * 於是調用prepare_namespace(),進一步檢查是不是舊的塊裝置的initrd         * (在這種情況下,還是一個塊裝置鏡像檔案/initrd.image,所以訪問/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);        /* 建立/dev/root.old裝置檔案。*/        create_dev("/dev/root.old", Root_RAM0);        /* 把/dev/root.old mount到/root目錄。*/        /* mount initrd on rootfs' /root */        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);        /* move initrd over / and chdir/chroot in initrd root */        sys_chdir("/root");        sys_mount(".", "/", NULL, MS_MOVE, NULL);        /* chroot到/root目錄,好了,現在/root目錄成為當前的根目錄。*/        sys_chroot(".");        /*         * In case that a resume from disk is carried out by linuxrc or one of         * its children, we need to tell the freezer not to wait for us.         */        current->flags |= PF_FREEZER_SKIP;        /* 建立一個線程,執行/linuxrc,這是舊的initrd預設執行的檔案。*/        pid = kernel_thread(do_linuxrc, "/linuxrc", SIGCHLD);                ......}

第二部分

建立一個cpio格式的 ramfs, 
# find miniroot/ | cpio -c -o > initrd.img # gzip initrd.img
這樣得到的initrd.img大小是可變的,它取決於您的小型根目錄miniroot/的總大小,由於首選使用cpio把根目錄進行打包,因此這個initramfs又被稱為cpio initrd. 在系統啟動階段,bootload除了從磁碟上機制核心鏡像bzImage之外,還要載入initrd.img.gz,然後把initrd.img.gz的起始地址傳遞給核心。能不能把這兩個檔案合二為一呢?答案是肯定的,在Linux 2.6的核心中,可以把initrd.img.gz連結到核心檔案(ELF格式)的一個特殊的資料區段中,這個段的名字為.init.ramfs。其中全域變數__initramfs_start和__initramfs_end分別指向這個資料區段的起始地址和結束位址。核心啟動時會對.init.ramfs段中的資料進行解壓,然後使用它作為臨時的根檔案系統。別看這個過程複雜,您只需要在make menuconfig中配置以下選項就可以了: 
General setup  --->     [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support     (../miniroot/)    Initramfs source file(s)
 
cpio ramfs 在啟動中的處理過程:
 
cpio ramfs採用rootfs作為其載體, rootfs也是一種基於記憶體的檔案系統格式。

首選在核心啟動過程,會初始化rootfs檔案系統,rootfs和tmpfs都是記憶體中的檔案系統,其類型為ramfs. 然後會把這個rootf掛載到根目錄。 其代碼如下:

[start_kernel() -> vfs_caches_init() -> mnt_init()] void __init mnt_init(void) {        ......        init_rootfs();        init_mount_tree();}

init_rootfs()註冊rootfs檔案系統,代碼如下:

static struct file_system_type rootfs_fs_type = {        .name           = "rootfs",        .get_sb         = rootfs_get_sb,        .kill_sb        = kill_litter_super,};int __init init_rootfs(void){        err = register_filesystem(&rootfs_fs_type);        ......        return err;}

init_mount_tree會把rootfs掛載到/目錄,代碼如下:

static void __init init_mount_tree(void){        struct vfsmount *mnt;        struct mnt_namespace *ns;        mnt = do_kern_mount("rootfs", 0, "rootfs", NULL);        ......        set_fs_pwd(current->fs, ns->root, ns->root->mnt_root);        set_fs_root(current->fs, ns->root, ns->root->mnt_root);}

do_kern_mount()會調用前面註冊的rootfs檔案系統對象的rootfs_get_sb()函數,

[rootfs_get_sb() -> ramfs_fill_super() -> d_alloc_root()]struct dentry * d_alloc_root(struct inode * root_inode){        struct dentry *res = NULL;        if (root_inode) {                static const struct qstr name = { .name = "/", .len = 1 };                res = d_alloc(NULL, &name);                if (res) {                        res->d_sb = root_inode->i_sb;                        res->d_parent = res;                        d_instantiate(res, root_inode);                }        }        return res;}

從上面的代碼中的可以看出,這個rootfs的dentry對象的名字為"/",也就是根目錄了。

cpio ramfs 省去了複雜的image解壓和裝載過程所以可以提高核心的啟動速度。 Android採用cpio ramfs為根檔案系統,
 節省了進入使用者空間的時間。

"arch/arm/kernel/vmlinux.lds" 358 lines

264   __security_initcall_end = .;
265   . = ALIGN(32);
266   __initramfs_start = .;
267    usr/built-in.o(.init.ramfs)
268   __initramfs_end = .;
269   . = ALIGN(4096);


"usr/initramfs_data.S"

 10   ld -m elf_i386  --format binary --oformat elf32-i386 -r /
 11   -T initramfs_data.scr initramfs_data.cpio.gz -o initramfs_data.o
 12    ld -m elf_i386  -r -o built-in.o initramfs_data.o
 13 
 14   initramfs_data.scr looks like this:
 15 SECTIONS
 16 {
 17        .init.ramfs : { *(.data) }
 18 }

 28 .section .init.ramfs,"a"
 29 .incbin "usr/initramfs_data.cpio.gz"

 

"init/initramfs.c" 584 lines 
541 static int __init populate_rootfs(void)
542 {
543   char *err = NULL;
544 
545   printk(KERN_EMERG"************************/n"); //gwj
546       err = unpack_to_rootfs(__initramfs_start,
547        __initramfs_end - __initramfs_start, 0);

584 rootfs_initcall(populate_rootfs);


the  file  initramfs.c  only express the cpio ramfs and copy all files to rootfs filesystem, these actions implemented in kernel setting.

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.