linux的initrd機制和initramfs機制之根文件掛載流程:代碼分析【轉】


轉自:https://www.chinahadoop.cn/group/15/thread/1786

這一篇我們來講解linux的initrd機制和initramfs機制之根文件掛載流程:代碼分析,希望大家認真學習!

linux-2.6.30

kernel_init
    do_basic_setup();  // 批注1
    
    if (!ramdisk_execute_command) // 批注2
        ramdisk_execute_command = "/init";
    
    if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
        ramdisk_execute_command = NULL;
        prepare_namespace();  // 批注3
    }
    
    init_post();

批注1:所有直接編譯在kernel中的模塊都是由它啟動的。這里有一個地方涉及到根文件系統的掛載
當配置了CONFIG_BLK_DEV_INITRD,在這里會調用函數populate_rootfs;
如果沒有配置populate_rootfs,則會調用函數default_rootfs

批注2:ramdisk_execute_command值通過“rdinit=”指定,如果未指定,則采用默認的值/init。

批注3:檢查根文件系統中是否存在文件ramdisk_execute_command,如果存在的話則執行init_post(),
否則執行prepare_namespace()掛載根文件系統。
 
先來說說上面批注1可能的兩個函數 populate_rootfs和default_rootfs
 

點擊(此處)折疊或打開

  1. static int __init populate_rootfs(void)
  2. {
  3.     char *err = unpack_to_rootfs(__initramfs_start, // 批注1
  4.              __initramfs_end - __initramfs_start);
  5.     if (err)
  6.         panic(err);    /* Failed to decompress INTERNAL initramfs */
  7.     if (initrd_start) { // 批注2
  8. #ifdef CONFIG_BLK_DEV_RAM
  9.         int fd;
  10.         printk(KERN_INFO "Trying to unpack rootfs image as initramfs...\n");
  11.         err = unpack_to_rootfs((char *)initrd_start, // 批注3
  12.             initrd_end - initrd_start);
  13.         if (!err) {
  14.             free_initrd();
  15.             return 0;
  16.         } else {
  17.             clean_rootfs();
  18.             unpack_to_rootfs(__initramfs_start,
  19.                  __initramfs_end - __initramfs_start);
  20.         }
  21.         printk(KERN_INFO "rootfs image is not initramfs (%s)"
  22.                 "; looks like an initrd\n", err);
  23.         fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 0700); // 批注4
  24.         if (fd >= 0) {
  25.             sys_write(fd, (char *)initrd_start, // 批注5
  26.                     initrd_end - initrd_start);
  27.             sys_close(fd);
  28.             free_initrd(); // 批注6
  29.         }
  30. #else
  31.         printk(KERN_INFO "Unpacking initramfs...\n");
  32.         err = unpack_to_rootfs((char *)initrd_start,
  33.             initrd_end - initrd_start);
  34.         if (err)
  35.             printk(KERN_EMERG "Initramfs unpacking failed: %s\n", err);
  36.         free_initrd();
  37. #endif
  38.     }
  39.     return 0;
  40. }

批注1:unpack_to_rootfs顧名思義,就是解壓包到rootfs,其具有兩個功能,一個是檢測
是否是屬於cpio包,另外一個就是解壓cpio包,通過最后一個參數進行控制。1:檢測,0:解壓。
其實,Initramfs也是壓縮過后的CPIO文件。initramfs位於地址__initramfs_start處,
是內核在編譯過程中生成的,initramfs的是作為內核的一部分而存在的,不是 boot loader加載的。
(編譯的時候通過連接腳本arch/arm/kernel/vmlinux.lds將其編譯到__initramfs_start~__initramfs_end,
執行完unpack_to_rootfs后將被拷貝到根目錄)。

批注2:判斷是否加載了initrd,無論哪種格式的initrd,都會被bootloader加載到地址initrd_start處。 
 
批注3:判斷加載的是不是CPIO-Initrd。

批注4:如果不是CPIO-Initrd,則就是Image-Initrd,將其內容保存到文件/initrd.image中。在根文件系統中創建文件/initrd.image。 
 
批注5:將內容保存到文件/initrd.image中

批注6:釋放Initrd所占用的內存空間。
 
default_rootfs()主要往rootfs中生成兩個目錄/dev和/root以及一個設備文件/dev/console。

 

下面來繼續看下面的函數:prepare_namespace
 

點擊(此處)折疊或打開

  1. void __init prepare_namespace(void)
  2. {
  3.     int is_floppy;
  4.  
  5.     if (root_delay) { // 批注1
  6.         printk(KERN_INFO "Waiting %dsec before mounting root device...\n",
  7.          root_delay);
  8.         ssleep(root_delay);
  9.     }
  10.  
  11.     /*
  12.      * wait for the known devices to complete their probing
  13.      *
  14.      * Note: this is a potential source of long boot delays.
  15.      * For example, it is not atypical to wait 5 seconds here
  16.      * for the touchpad of a laptop to initialize.
  17.      */
  18.     wait_for_device_probe(); // 批注2
  19.  
  20.     md_run_setup();
  21.  
  22.     if (saved_root_name[0]) { // 批注3
  23.         root_device_name = saved_root_name;
  24.         if (!strncmp(root_device_name, "mtd", 3) ||
  25.          !strncmp(root_device_name, "ubi", 3)) {
  26.             mount_block_root(root_device_name, root_mountflags); // 批注4
  27.             goto out;
  28.         }
  29.         ROOT_DEV = name_to_dev_t(root_device_name); // 批注5
  30.         if (strncmp(root_device_name, "/dev/", 5) == 0)
  31.             root_device_name += 5;
  32.     }
  33.  
  34.     if (initrd_load()) // 批注6
  35.         goto out;
  36.  
  37.     /* wait for any asynchronous scanning to complete */
  38.     if ((ROOT_DEV == 0) && root_wait) { // 批注7
  39.         printk(KERN_INFO "Waiting for root device %s...\n",
  40.             saved_root_name);
  41.         while (driver_probe_done() != 0 ||
  42.             (ROOT_DEV = name_to_dev_t(saved_root_name)) == 0)
  43.             msleep(100);
  44.         async_synchronize_full();
  45.     }
  46.  
  47.     is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;
  48.  
  49.     if (is_floppy && rd_doload && rd_load_disk(0))
  50.         ROOT_DEV = Root_RAM0;
  51.  
  52.     mount_root();
  53. out:
  54.     sys_mount(".", "/", NULL, MS_MOVE, NULL); // 批注8
  55.     sys_chroot("."); // 批注9
  56. }

批注1:對於將根文件系統存放到USB或者SCSI設備上的情況,Kernel需要等待這些耗費時間比較久的設備
驅動加載完畢,所以這里存在一個Delay。

批注2:等待根文件系統所在的設備探測函數的完成。 
 
批注3:參數saved_root_name存放的是uboot參數root=所指定的設備文件。

批注4:將saved_root_nam指定的設備加載。 
 
批注5:參數ROOT_DEV存放設備節點號。

批注6:掛載initrd,見下面的詳解。 
 
批注7:如果指定mount_initrd為true,即沒有指定在函數initrd_load中mount的話,則在這里重新realfs的mount操作。 
 
批注8:將掛載點從當前目錄(實際當前的目錄在mount_root中或者在mount_block_root中指定)移到根目錄。
即是如/dev/mtdblock2。 
 
批注9:將當前目錄當作系統的根目錄,至此虛擬系統根目錄文件系統切換到了實際的根目錄文件系統。

 

 

點擊(此處)折疊或打開

  1. int __init initrd_load(void)
  2. {
  3.     if (mount_initrd) { // 批注1
  4.         create_dev("/dev/ram", Root_RAM0); // 批注2
  5.         /*
  6.          * Load the initrd data into /dev/ram0. Execute it as initrd
  7.          * unless /dev/ram0 is supposed to be our actual root device,
  8.          * in that case the ram disk is just set up here, and gets
  9.          * mounted in the normal path.
  10.          */
  11.         if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) { // 批注3
  12.             sys_unlink("/initrd.image");
  13.             handle_initrd(); // 批注4
  14.             return 1;
  15.         }
  16.     }
  17.     sys_unlink("/initrd.image");
  18.     return 0;
  19. }

批注1:可以通過Kernel的參數“noinitrd“來配置mount_initrd的值,默認為1,很少看到有項目區配置該值,
所以一般情況下,mount_initrd的值應該為1。

批注2:創建一個Root_RAM0的設備節點/dev/ram。 
 
批注3:如果根文件設備號不是Root_RAM0則程序就會執行代碼就進入執行,如指定的/dev/mtdblock4設備節點
肯定就不是Root_RAM0。另外還將文件initrd.image釋放到節點/dev/ram0,也就是對應image-initrd的操作。

批注4:函數handle_initrd主要功能是執行Initrd中的linuxrc文件,並且將realfs的根目錄設置為當前
目錄。其實前面也已經提到了,這些操作只對image-cpio的情況下才會去執行。。

 

 

點擊(此處)折疊或打開

  1. static void __init handle_initrd(void)
  2. {
  3.     int error;
  4.     int pid;
  5.  
  6.     real_root_dev = new_encode_dev(ROOT_DEV); // 批注1
  7.     create_dev("/dev/root.old", Root_RAM0); // 批注2
  8.     /* mount initrd on rootfs' /root */
  9.     mount_block_root("/dev/root.old", root_mountflags & ~MS_RDONLY);
  10.     sys_mkdir("/old", 0700); // 批注3
  11.     root_fd = sys_open("/", 0, 0);
  12.     old_fd = sys_open("/old", 0, 0);
  13.     /* move initrd over / and chdir/chroot in initrd root */
  14.     sys_chdir("/root"); // 批注4
  15.     sys_mount(".", "/", NULL, MS_MOVE, NULL);
  16.     sys_chroot(".");
  17.  
  18.     /*
  19.      * In case that a resume from disk is carried out by linuxrc or one of
  20.      * its children, we need to tell the freezer not to wait for us.
  21.      */
  22.     current->flags |= PF_FREEZER_SKIP;
  23.  
  24.     pid = kernel_thread(do_linuxrc, "/linuxrc", SIGCHLD); // 批注5
  25.     if (pid > 0)
  26.         while (pid != sys_wait4(-1, NULL, 0, NULL))
  27.             yield();
  28.  
  29.     current->flags &= ~PF_FREEZER_SKIP;
  30.  
  31.     /* move initrd to rootfs' /old */
  32.     sys_fchdir(old_fd); // 批注6
  33.     sys_mount("/", ".", NULL, MS_MOVE, NULL);
  34.     /* switch root and cwd back to / of rootfs */
  35.     sys_fchdir(root_fd);
  36.     sys_chroot(".");
  37.     sys_close(old_fd);
  38.     sys_close(root_fd);
  39.  
  40.     if (new_decode_dev(real_root_dev) == Root_RAM0) { // 批注7
  41.         sys_chdir("/old");
  42.         return;
  43.     }
  44.  
  45.     ROOT_DEV = new_decode_dev(real_root_dev); // 批注8
  46.     mount_root();
  47.  
  48.     printk(KERN_NOTICE "Trying to move old root to /initrd ... ");
  49.     error = sys_mount("/old", "/root/initrd", NULL, MS_MOVE, NULL);
  50.     if (!error)
  51.         printk("okay\n");
  52.     else {
  53.         int fd = sys_open("/dev/root.old", O_RDWR, 0);
  54.         if (error == -ENOENT)
  55.             printk("/initrd does not exist. Ignored.\n");
  56.         else
  57.             printk("failed\n");
  58.         printk(KERN_NOTICE "Unmounting old root\n");
  59.         sys_umount("/old", MNT_DETACH);
  60.         printk(KERN_NOTICE "Trying to free ramdisk memory ... ");
  61.         if (fd < 0) {
  62.             error = fd;
  63.         } else {
  64.             error = sys_ioctl(fd, BLKFLSBUF, 0);
  65.             sys_close(fd);
  66.         }
  67.         printk(!error ? "okay\n" : "failed\n");
  68.     }
  69. }

批注1:real_root_dev為一個全局變量,用來保存realfs的設備號。


批注2:調用mount_block_root將realfs加載到VFS的/root下。 
 
批注3:提取rootfs的根文件描述符並將其保存到root_fd,資料中提及其用處就是在后續調用
sys_chroot到initrd的文件系統后,處理完init請求后,還能夠再次切回到rootfs。


批注4:sys_chroot到initrd文件系統,前面已經掛載initrd到VFS的root目錄下。 
 
批注5:執行initrd中的linuxrc,並等待執行結束。


批注6:initrd執行結束后,切回到rootfs。 
 
批注7:如果real_root_dev直接配置為Root_RAM0,也即直接使用直接使用initrd作為realfs,
改變當前目錄到initrd中,並直接返回。 
 
批注8:執行完Linuxrc后,realfs已經確定,則調用mount_root將realfs掛載到VFS的/root目錄下,
並將當前的目錄配置為VFS的/root。 


再回過頭來再看上面提到的init_post,該函數實際上是在Kernel_init中最后執行的函數

static noinline int init_post(void)
__releases(kernel_lock)

    if (ramdisk_execute_command)
    
    if (execute_command)
    
    run_init_process("/sbin/init");
    run_init_process("/etc/init");
    run_init_process("/bin/init");
    run_init_process("/bin/sh");

可以看到,在該函數的最后,以此會去搜索文件並執行ramdisk_execute_command、execute_command、/sbin/init、
/etc/init、/bin/init和/bin/sh,如果發現這些文件均不存在的話,則通過panic輸出錯誤命令,並將當前的系統Halt在那里


大概總結一下流程:
內核會先創建一個基於內存的虛擬文件系統rootfs(和用什么機制無關);
根據采用的機制來掛載虛擬
文件系統,完成掛載真正文件系統前的工作;
掛載真正的文件系統;
執行最初的應用程序。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM