根文件系统挂载过程

Posted 一肩担风月

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了根文件系统挂载过程相关的知识,希望对你有一定的参考价值。

目录

注册挂载rootfs文件系统

解压initramfs到rootfs中

prepare_namespace挂载磁盘上的文件系统


注册挂载rootfs文件系统

首先是rootfs的注册和挂载,rootfs作为一切后续文件操作的基石。

start_kernel
  vfs_caches_init
    mnt_init
      init_rootfs注册rootfs文件系统
      init_mount_tree 挂载rootfs文件系统
        vfs_kern_mount
          mount_fs
            type->mount其实是rootfs_mount 
              mount_nodev
                fill_super 其实是ramfs_fill_super
                  inode = ramfs_get_inode(sb, NULL, S_IFDIR | fsi->mount_opts.mode, 0);
                  sb->s_root = d_make_root(inode);
                    static const struct qstr name = QSTR_INIT("/", 1);[1*]
                    __d_alloc(root_inode->i_sb, &name);
          ...
          mnt->mnt.mnt_root = root;[2*]
          mnt->mnt.mnt_sb = root->d_sb;[3*]
          mnt->mnt_mountpoint = mnt->mnt.mnt_root;[4*]
          mnt->mnt_parent = mnt;[5*]
                 root.mnt = mnt;
        root.dentry = mnt->mnt_root;
        mnt->mnt_flags |= MNT_LOCKED;
        set_fs_pwd(current->fs, &root);
        set_fs_root(current->fs, &root);
  ...
  rest_init
    kernel_thread(kernel_init, NULL, CLONE_FS);

 

在执行kernel_init之前,会建立roofs文件系统。

  1. [1*]处设置了根目录的名字为“/”。
  2. [2*]处设置了vfsmount中的root目录
  3. [3*]处设置了vfsmount中的超级块
  4. [4*]处设置了vfsmount中的文件挂载点,指向了自己
  5. [5*]处设置了vfsmount中的父文件系统的vfsmount为自己

解压initramfs到rootfs中

根目录有了,接下来就可以按照传递给内核的参数与内核编译选项来决定如何建立根文件系统。

内核编译选项可以选择是否支持initramfs,是不是指定了initramfs目录。不管配置内核的时候是不是支持initramfs,内核要保证在__initramfs_start处放着一个initramfs文件系统。

分三种情况讨论内核对initramfs的支持

  1. 配置内核支持initramfs,并指定了initramfs所在目录,那么内核会把这个目录压缩到__initramfs_start指向的段”.init.ramfs”。此种情况在grub引导的时候不必要指定外部文件系统,此时initramfs就作为根文件系统来使用了。当然也可以指定。
  2. 配置内核支持initramfs,但是没有指定initramfs所在目录。内核会执行default_initramfs()来创建一个最小的initramfs到__initramfs_start指向的段”.init.ramfs”,包含/dev目录、/dev/console设备节点和/root目录。此种情况需要告诉grub外部文件系统。
  3. 配置内核不支持initramfs。内核会执行default_rootfs()来创建一个最小的initramfs到__initramfs_start指向的段”.init.ramfs”,包含/dev目录、/dev/console设备节点和/root目录。此种情况需要告诉grub外部文件系统。

如何告诉grub外部文件系统所在,就是initrd参数。

外部文件系统可以是initrd格式,也可以是cpio格式。如何处理外部文件系统,是在populate_rootfs函数中。

内核编译的时候rootfs_initcall(populate_rootfs);会将populate_rootfs函数加入到初始化区段。会在kernel_init中被调用。

kernel_init
    kernel_init_freeable
        do_basic_setup
            do_initcalls
                populate_rootfs

populate_rootfs函数的作用就是将编译的initramfs文件系统解压到rootfs的根目录中。

static int __init populate_rootfs(void)
{
    char *err = unpack_to_rootfs(__initramfs_start, __initramfs_size);//将__initramfs_start处的文件系统解压出来,上面说过了,内核编译时候保证至少会有一个initramfs在此处
    if (err)
        panic("%s", err); /* Failed to decompress INTERNAL initramfs */
    if (initrd_start) {//如果配置grub时候指定了外部文件系统,grub会将外部文件数据加载到initrd_start
#ifdef CONFIG_BLK_DEV_RAM//如果配置内核支持initrd格式的文件系统
        int fd;
        printk(KERN_INFO "Trying to unpack rootfs image as initramfs...\n");
        err = unpack_to_rootfs((char *)initrd_start,
            initrd_end - initrd_start);//首先还是按照initramfs格式解压grub加载的文件系统
        if (!err) {
            free_initrd();
            goto done;
        } else {
            clean_rootfs();
            unpack_to_rootfs(__initramfs_start, __initramfs_size);//如果grub加载的文件系统不是initramfs格式,那么清除rootfs中的数据,重新解压__initramfs_start,因为目录可能被破坏
        }
        printk(KERN_INFO "rootfs image is not initramfs (%s)"
                "; looks like an initrd\n", err);
        fd = sys_open("/initrd.image",
                  O_WRONLY|O_CREAT, 0700);
        if (fd >= 0) {
            ssize_t written = xwrite(fd, (char *)initrd_start,
                        initrd_end - initrd_start);//将grub加载的文件系统写入到/initrd.image文件中

            if (written != initrd_end - initrd_start)
                pr_err("/initrd.image: incomplete write (%zd != %ld)\n",
                       written, initrd_end - initrd_start);

            sys_close(fd);
            free_initrd();
        }
    done:
#else
        printk(KERN_INFO "Unpacking initramfs...\n");
        err = unpack_to_rootfs((char *)initrd_start,
            initrd_end - initrd_start);//如果配置内核不支持initrd格式文件系统,那么统一按照initramfs格式解压
        if (err)
            printk(KERN_EMERG "Initramfs unpacking failed: %s\n", err);
        free_initrd();
#endif
        /*
         * Try loading default modules from initramfs.  This gives
         * us a chance to load before device_initcalls.
         */
        load_default_modules();
    }
    return 0;
}

此时rootfs文件系统中的基本的目录结构已经被populate_rootfs处理好。返回到kernel_init_freeable

static noinline void __init kernel_init_freeable(void)
{
    ...
    do_basic_setup();
    ...
    if (!ramdisk_execute_command)
        ramdisk_execute_command = "/init";//内核默认最开始执行的脚本是init脚本

    if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
        ramdisk_execute_command = NULL;//如果内核没有在此时的rootfs的根目录下发现init文件,就会执行prepare_namespace函数,说明grub加载的initrd格式的文件系统
        prepare_namespace();//函数的主要功能就是加载真实的根文件系统。
    }

假设此时正确加载了根文件系统,返回到kernel_init

static int __ref kernel_init(void *unused)
{
    ...
    kernel_init_freeable();
    ...
    if (ramdisk_execute_command) {//如果在kernel_init_freeable函数中找到init脚本,那么就执行这个脚本
        ret = run_init_process(ramdisk_execute_command);
        if (!ret)
            return 0;
        pr_err("Failed to execute %s (error %d)\n",
               ramdisk_execute_command, ret);
    }

    /*
     * We try each of these until one succeeds.
     *
     * The Bourne shell can be used instead of init if we are
     * trying to recover a really broken machine.
     */
    if (execute_command) {//这个命令不知道是怎么传递过来的,应该也是一个脚本
        ret = run_init_process(execute_command);
        if (!ret)
            return 0;
        panic("Requested init %s failed (error %d).",
              execute_command, ret);
    }
    if (!try_to_run_init_process("/sbin/init") ||//上面都没有找到的话,依次尝试几个目录下的启动脚本
        !try_to_run_init_process("/etc/init") ||
        !try_to_run_init_process("/bin/init") ||
        !try_to_run_init_process("/bin/sh"))
        return 0;

    panic("No working init found.  Try passing init= option to kernel. "
          "See Linux Documentation/init.txt for guidance.");
}

prepare_namespace挂载磁盘上的文件系统

来看看prepare_namespace函数如何挂载真实文件系统。

void __init prepare_namespace(void)
{
    ...
    if (saved_root_name[0]) {
        root_device_name = saved_root_name;//这个是grub配置文件中的root参数指定。
        if (!strncmp(root_device_name, "mtd", 3) ||
            !strncmp(root_device_name, "ubi", 3)) {
            mount_block_root(root_device_name, root_mountflags);
            goto out;
        }
        ROOT_DEV = name_to_dev_t(root_device_name);//通过指定的根文件系统所在设备匹配出ROOT_DEV号。此时sysfs文件系统已经建立了,各个硬件设备已经被扫描过并在sysfs下建立对应的层次结构了。
        if (strncmp(root_device_name, "/dev/", 5) == 0)
            root_device_name += 5;
    }

    if (initrd_load())
        goto out;
    ...
out:
    devtmpfs_mount("dev");
    sys_mount(".", "/", NULL, MS_MOVE, NULL);//将当前目录挂载到“/”目录
    sys_chroot(".");

找到ROOT_DEV号之后,initrd_load接着处理挂载事务。

int __init initrd_load(void)
{
    if (mount_initrd) {
        create_dev("/dev/ram", Root_RAM0);//创建一个/dev/ram0设备节点
        /*
         * Load the initrd data into /dev/ram0. Execute it as initrd
         * unless /dev/ram0 is supposed to be our actual root device,
         * in that case the ram disk is just set up here, and gets
         * mounted in the normal path.
         */
        if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) {//rd_load_image函数将/initrd.image文件写入/dev/ram0中
            sys_unlink("/initrd.image");
            handle_initrd();//如果grub配置文件中指定的根设备不是Root_RAM0就调用handle_initrd处理
            return 1;
        }
    }
    sys_unlink("/initrd.image");
    return 0;
}

rd_load_image函数主要流程,就是将/initrd.image写入/dev/ram0

int __init rd_load_image(char *from)
{
    ...
    out_fd = sys_open("/dev/ram", O_RDWR, 0);
    if (out_fd < 0)
        goto out;

    in_fd = sys_open(from, O_RDONLY, 0);
    if (in_fd < 0)
        goto noclose_input;
    ...
        sys_read(in_fd, buf, BLOCK_SIZE);
        sys_write(out_fd, buf, BLOCK_SIZE);

实际处理落入handle_initrd函数中

static void __init handle_initrd(void)
{
    struct subprocess_info *info;
    static char *argv[] = { "linuxrc", NULL, };
    extern char *envp_init[];
    int error;

    real_root_dev = new_encode_dev(ROOT_DEV);
    create_dev("/dev/root.old", Root_RAM0);//以相同的设备号建立一个设备节点,其实还是/dev/ram0,还是/initrd.image,还是grub加载的外部文件系统
    /* mount initrd on rootfs‘ /root */
    mount_block_root("/dev/root.old", root_mountflags & ~MS_RDONLY);//将/dev/root.old挂载到/root上
    sys_mkdir("/old", 0700);
    sys_chdir("/old");//切换当前目录到/old

    /* try loading default modules from initrd */
    load_default_modules();

    /*
     * 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;

    info = call_usermodehelper_setup("/linuxrc", argv, envp_init,//linuxrc脚本用来真正做一些初始化工作
                     GFP_KERNEL, init_linuxrc, NULL, NULL);//创建一个工作队列,用来调用执行/linuxrc脚本,init_linuxrc函数会保证将执行/linuxrc的线程根目录切换到/root上
    if (!info)
        return;
    call_usermodehelper_exec(info, UMH_WAIT_PROC);

    current->flags &= ~PF_FREEZER_SKIP;

    /* move initrd to rootfs‘ /old */
    sys_mount("..", ".", NULL, MS_MOVE, NULL);//“..”是rootfs的“/”目录,将该目录挂载到/old目录
    /* switch root and cwd back to / of rootfs */
    sys_chroot("..");

    if (new_decode_dev(real_root_dev) == Root_RAM0) {
        sys_chdir("/old");
        return;
    }

    sys_chdir("/");
    ROOT_DEV = new_decode_dev(real_root_dev);
    mount_root();//创建/dev/root,将/dev/root挂载到/root上。此时看出为什么【将"/"目录挂载到/old目录】,因为这一步会覆盖掉原来挂载到root上的root.old

    printk(KERN_NOTICE "Trying to move old root to /initrd ... ");
    error = sys_mount("/old", "/root/initrd", NULL, MS_MOVE, NULL);//将/old挂载到现在root下的initrd,如果root目录下没有initrd目录则释放/old
    if (!error)
        printk("okay\n");
    else {
        int fd = sys_open("/dev/root.old", O_RDWR, 0);
        if (error == -ENOENT)
            printk("/initrd does not exist. Ignored.\n");
        else
            printk("failed\n");
        printk(KERN_NOTICE "Unmounting old root\n");
        sys_umount("/old", MNT_DETACH);
        printk(KERN_NOTICE "Trying to free ramdisk memory ... ");
        if (fd < 0) {
            error = fd;
        } else {
            error = sys_ioctl(fd, BLKFLSBUF, 0);
            sys_close(fd);
        }
        printk(!error ? "okay\n" : "failed\n");
    }
}

mount_root创建/dev/root节点,挂载到/root目录上。

void __init mount_root(void)
{
...
#ifdef CONFIG_BLOCK
    {
        int err = create_dev("/dev/root", ROOT_DEV);

        if (err < 0)
            pr_emerg("Failed to create /dev/root: %d\n", err);
        mount_block_root("/dev/root", root_mountflags);
    }
#endif
}
void __init mount_block_root(char *name, int flags)
{
    ...
    get_fs_names(fs_names);
retry:
    for (p = fs_names; *p; p += strlen(p)+1) {
        int err = do_mount_root(name, p, flags, root_mount_data);
    ...
}
static int __init do_mount_root(char *name, char *fs, int flags, void *data)
{
    struct super_block *s;
    int err = sys_mount(name, "/root", fs, flags, data);
    if (err)
        return err;

    sys_chdir("/root");
    s = current->fs->pwd.dentry->d_sb;
    ROOT_DEV = s->s_dev;
    printk(KERN_INFO
           "VFS: Mounted root (%s filesystem)%s on device %u:%u.\n",
           s->s_type->name,
           s->s_flags & MS_RDONLY ?  " readonly" : "",
           MAJOR(ROOT_DEV), MINOR(ROOT_DEV));
    return 0;
}

 

以上是关于根文件系统挂载过程的主要内容,如果未能解决你的问题,请参考以下文章

linux根文件系统的挂载过程详解

linux挂载概念简述:

Linux0.11 根文件系统挂载

Linux系统——通过tftp下载zImage和nfs网络方式挂载根文件系统

Linux系统——通过tftp下载zImage和nfs网络方式挂载根文件系统

根文件系统熟悉根文件系统构建过程记录