根文件系统挂载过程

目录

注册挂载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;
}
时间: 2024-10-06 00:10:51

根文件系统挂载过程的相关文章

Linux内核启动及根文件系统载入过程

上接博文<u-boot之u-boot-2009.11启动过程分析> Linux内核启动及文件系统载入过程 当u-boot開始运行bootcmd命令,就进入Linux内核启动阶段.与u-boot类似,普通Linux内核的启动过程也能够分为两个阶段,但针对压缩了的内核如uImage就要包含内核自解压过程了.本文以linux-2.6.37版源代码为例分三个阶段来描写叙述内核启动全过程.第一阶段为内核自解压过程,第二阶段主要工作是设置ARM处理器工作模式.使能MMU.设置一级页表等,而第三阶段则主要为

I.MX6Q(TQIMX6Q/TQE9)学习笔记——新版BSP之根文件系统挂载

经过前面的移植,新版BSP的uboot和kernel已经能够在tqimx6q开发板上运行了,接下来我们来挂载文件系统. DTB整理 前面的文章中提到,新版BSP的DTB管理感觉不是太好,在之前移植的BSP上我发现即便有根文件系统,内核也会挂掉,由于原来的DTS文件中配置内容太多,不好确定问题的源头,本文对DTS进行了整理,整理后只需要一个DTS文件,其内容如下: /* * Copyright 2012 Freescale Semiconductor, Inc. * Copyright 2011

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

一:前言 前段时间在编译kernel的时候发现rootfs挂载不上.相同的root选项设置旧版的image却可以.为了彻底解决这个问题.研究了一下rootfs的挂载过程.特总结如下,希望能给这部份知识点比较迷茫的朋友一点帮助. 二:rootfs的种类 总的来说,rootfs分为两种:虚拟rootfs和真实rootfs.现在kernel的发展趋势是将更多的功能放到用户空间完成.以保持内核的精简.虚拟rootfs也是各linux发行厂商普遍采用的一种方式.可以将一部份的初始化工作放在虚拟的rootf

Linux挂载根文件系统

NFS根文件系统挂载 **这里只是记录自己使用NFS挂载根文件系统时出现的错误,并不涉及技术细节** 开发板:Smart210 Bootloader: u-boot-2012-10 Linux: Linux3.10.46 刚开始时在uboot中设置的参数如下: setenv bootargs root=/dev/nfs nfsroot=192.168.10.101:/home/weirdo/Share/rootfs_rtm_210 ip=192.168.10.120:192.168.10.101

Linux之搭建自己的根文件系统

Hi!大家好,我是CrazyCatJack.又和大家见面了.今天给大家带来的是构建Linux下的根文件系统.希望大家看过之后都能构建出符合自己需求的根文件系统^_^ 1.内容概述 1.构造过程 今天给大家展示的根文件系统构造过程如下图所示: 正如大家看到的,这是一个环环相扣的过程.因为在这四个方面的内容其实相互包含,有很多交集的地方,所以我用环图给大家展示.在第一部分,我会给大家讲解如何在etc/目录下编写相应的配置文件,包含etc/init.d/rcS和etc/fstab等:在第二部分,将会教

根文件系统袁丽丽

1.1.根文件系统概述 1.为什么需要根文件系统 (1)init进程的应用程序在根文件系统上 (2)根文件系统提供了根目录/ (3)内核启动后的应用层配置(etc目录)在根文件系统上 (4)shell命令程序在根文件系统上 总结:一套Linux体系,只有内核本身是不能工作,必须要根文件系统相配合,主要是要根文件系统/etc下的配置文件./bin./sbin等目录下的shell命令相配合等等,还有/lib下的库文件(静态链接库,动态链接库)等等 1.2.根文件系统的实质 (1).根文件系统是特殊用

tiny4412u-boot烧写及根文件系统制作(不进入终端问题)

http://m.blog.csdn.net/article/details?id=51400196(转) VMware12 环境:ubuntu12.4 开发板:tiny4412 首先烧写bootloader,我用一个8G的内存卡,现在不说sd卡的制作过程了,网上可以参考. 现在就把我给arm的emmc烧写过程开始说. 用sd卡启动,开发板的右下角有个开关控制启动方式,往下是sd卡启动,往上是mmc启动. 1,.先用sd卡启动 注意:把右下角的开关拨到下面.启动后的是[[email protec

嵌入式 根文件系统

根文件系统 上节讲解了Linux内核移植,这节讲如何构造根文件系统. 工具和源码在路径:F:\韦东山\CD1_主光盘\system. jffs2制作工具路径:F:\韦东山\CD1_主光盘\GUI\xwindow\X\deps 具体步骤见:http://www.cnblogs.com/pigeon84/articles/2234214.html tar xjf  busybox-1.7.0.tar.bz2 //解压 cd busybox-1.7.0 //进入目录 make  menuconfig 

嵌入式 Linux根文件系统移植(二)——根文件系统简介

嵌入式 Linux根文件系统移植(二)--根文件系统简介 根文件系统是内核启动时挂载的第一个文件系统,内核代码映像文件保存在根文件系统中,而系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和服务等加载到内存中去运行. 一.嵌入式设备文件系统 在嵌入式Linux应用中,主要的存储设备为 RAM(DRAM, SDRAM)和ROM(常采用FLASH存储器),常用的基于存储设备的文件系统类型包括:jffs2, yaffs, cramfs, romfs, ramdisk, ramfs/tm