zoukankan      html  css  js  c++  java
  • 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... ");
    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 ", 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... ");
    32.         err = unpack_to_rootfs((char *)initrd_start,
    33.             initrd_end - initrd_start);
    34.         if (err)
    35.             printk(KERN_EMERG "Initramfs unpacking failed: %s ", 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... ",
    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... ",
    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 ");
    52.     else {
    53.         int fd = sys_open("/dev/root.old", O_RDWR, 0);
    54.         if (error == -ENOENT)
    55.             printk("/initrd does not exist. Ignored. ");
    56.         else
    57.             printk("failed ");
    58.         printk(KERN_NOTICE "Unmounting old root ");
    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 " : "failed ");
    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(和用什么机制无关);
    根据采用的机制来挂载虚拟
    文件系统,完成挂载真正文件系统前的工作;
    挂载真正的文件系统;
    执行最初的应用程序。

    【作者】张昺华
    【大饼教你学系列】https://edu.csdn.net/course/detail/10393
    【新浪微博】 张昺华--sky
    【twitter】 @sky2030_
    【微信公众号】 张昺华
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    学习SpringMVC——从HelloWorld开始
    线性队列
    线性表之链表
    线性表之顺序表
    nextSibling 属性与 nextElementSibling 属性的异同
    JavaScript数组增删方法总结
    class关键字
    JS三座大山_单线程&EventLoop
    JS三座大山_闭包
    JS三座大山_原型与原型链
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/13742111.html
Copyright © 2011-2022 走看看