VFS-根文件系统挂载

在linux中所有的文件系统都组成了一棵树,那么最开始的那个树是怎么来的呢?
一、根文件系统的挂点-”\”
在安装Linux的时候,需要一个分区作为根分区”/”,类型通常为ext3,这个分区中的ext3文件系统就是最初的那棵树,linux启动初始化的时候,会将这个分区的文件系统挂载在VFS中的”/”目录上,”/” 也是一个目录,那它属于什么文件系统呢?
它属于rootfs文件系统,这是特殊文件系统中的一种,存在于内存中,那是如何生成”\”的呢?
当启动时候,引导程序会将linux的内核镜像(不是全部linux)加载的内存后会执行main,main中的Start_kernel()中会调用到mnt_init函数,可以看到这个函数中其中调用了两个函数:init_rootfs()和init_mount_tree(),init_rootfs()函数的作用是通过调用register_filesystem(&rootfs_fs_type)注册rootfs文件系统,init_mount_tree()函数的作用是rootfs文件系统的挂载,并在此文件系统中建立一个”\”目录。
rootfs文件系统类型
static struct file_system_type rootfs_fs_type = {
        .name        = "rootfs",
        .get_sb        = rootfs_get_sb, //get_sb的方法为rootfs_get_sb,这个在init_mnt_tree中会用到
        .kill_sb    = kill_litter_super,
    };
二、根文件系统的挂载
首先说明一个问题,加载根文件系统是从块设备上加载的,就要读取块设备,就需要驱动,而linux对驱动的管理是以可加载模块的形式,因此,在动态加载了设备驱动后,再挂载设备上的文件系统作为根文件系统。
这个动态加载过程是通过initrd机制实现的。
initrd 的英文含义是 boot loader initialized RAM disk,就是由 boot loader 初始化的内存盘。在 linux内核启动前, boot loader 会将存储介质中的 initrd 文件加载到内存,内核启动时会在访问真正的根文件系统前先访问该内存中的 initrd中的脚本文件。在 boot loader 配置了 initrd 的情况下,内核启动被分成了两个阶段,第一阶段先执行 initrd 文件系统中的linuxrc,完成加载驱动模块等任务,第二阶段才会执行真正的根文件系统中的 /sbin/init 进程。这样做的好处是,linux的一些功能,例如对硬件的支持可以做成可加载模块,而不是直接编译到内核,通过修改配置文件,就可以在启动的时候,根据需要加载某些模块,这样就避免了linux的冗余。
initrd中的内容并不是直接拷贝的内存直接访问的,而是借助ramdisk,在initrd文件加载到内存中,然后判断是initrd格式后,在rootfs的”/”下为其建立一个initrd.image文件,接着注册一个ramdisk设备(/dev/ram0),将initrd.image文件中的内容拷贝到这个内存闪盘中,并将这个设备作为原始根文件系统,这样就可以访问initrd中的文件实现模块的加载和根文件系统的安装了,根文件系统安装后会替换调原始文件系统,用真正的文件系统中的”\”替换调rootfs的”\”。
即:
image-initrd的处理流程
1. boot loader把内核以及initrd文件加载到内存的特定位置。
2. 内核判断initrd的文件格式,如果不是cpio格式,将其作为image-initrd处理。
3. 内核将initrd的内容保存在rootfs下的/initrd.image文件中。
4. 内核将/initrd.image的内容读入/dev/ram0设备中,也就是读入了一个内存盘中。
5. 接着内核以可读写的方式把/dev/ram0设备挂载为原始的根文件系统。
6. .如果/dev/ram0被指定为真正的根文件系统,那么内核跳至最后一步正常启动。
7. 执行initrd上的/linuxrc文件,linuxrc通常是一个脚本文件,负责加载内核访问根文件系统必须的驱动, 以及加载根文件系统。
8. /linuxrc执行完毕,常规根文件系统被挂载
9. 如果常规根文件系统存在/initrd目录,那么/dev/ram0将从/移动到/initrd。否则如果/initrd目录不存在, /dev/ram0将被卸载。
10. 在常规根文件系统上进行正常启动过程 ,执行/sbin/init。
具体过程:
initrd相关代码的调用层次关系图
init函数是内核所有初始化代码的入口,代码如下,其中只保留了同initrd相关部分的代码。
砭石祛斑泥
static int init(void * unused){
[1]    populate_rootfs();
   
[2]    if (sys_access((const char __user *) "/init", 0) == 0)
        execute_command = "/init";
    else
        prepare_namespace();
[3]    if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
        printk(KERN_WARNING "Warning: unable to open an initial console.\n");
    (void) sys_dup(0);
    (void) sys_dup(0);
[4]    if (execute_command)
        run_init_process(execute_command);
    run_init_process("/sbin/init");
    run_init_process("/etc/init");
    run_init_process("/bin/init");
    run_init_process("/bin/sh");
    panic("No init found.  Try passing init= option to kernel.");
}
代码[1]:populate_rootfs函数负责加载initramfs和cpio-initrd,对于populate_rootfs函数的细节后面会讲到。
代码[2]:如果rootfs的根目录下中包含/init进程,则赋予execute_command,在init函数的末尾会被执行。否则执行prepare_namespace函数,initrd是在该函数中被加载的。
代码[3]:将控制台设置为标准输入,后续的两个sys_dup(0),则复制标准输入为标准输出和标准错误输出。
代码[4]:如果rootfs中存在init进程,就将后续的处理工作交给该init进程。其实这段代码的含义是如果加载了cpio-initrd则交给cpio-initrd中的/init处理,否则会执行realfs中的init。读者可能会问:如果加载了cpio-initrd, 那么realfs中的init进程不是没有机会运行了吗?确实,如果加载了cpio-initrd,那么内核就不负责执行realfsinit进程了,而是将这个执行任务交给了cpio-initrd的init进程。解开fedora core4initrd文件,会发现根目录的下的init文件是一个脚本,在该脚本的最后一行有这样一段代码:
………..
switchroot --movedev /sysroot
就是switchroot语句负责加载realfs,以及执行realfs的init进程。
磁悬浮鼓风机原理
cpio-initrd的处理
对cpio-initrd的处理位于populate_rootfs函数中。
void __init populate_rootfs(void){
[1]  char *err = unpack_to_rootfs(__initramfs_start,
            __initramfs_end - __initramfs_start, 0);
[2]    if (initrd_start) {
[3]        err = unpack_to_rootfs((char *)initrd_start,
            initrd_end - initrd_start, 1);
   
[4]        if (!err) {
            printk(" it is\n");
            unpack_to_rootfs((char *)initrd_start,
                initrd_end - initrd_start, 0);
            free_initrd_mem(initrd_start, initrd_end);
            return;
        }
[5]        fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 700);
        if (fd >= 0) {
            sys_write(fd, (char *)initrd_start,
                    initrd_end - initrd_start);
            sys_close(fd);
            free_initrd_mem(initrd_start, initrd_end);
        }
}
代码[1]:加载initramfs, initramfs位于地址__initramfs_start处,是内核在编译过程中生成的,initramfs的是作为内核的一部分而存在的,不是 boot loader加载的。前面提到了现在initramfs没有任何实质内容。
代码[2]:判断是否加载了initrd。无论哪种格式的initrd,都会被boot loader加载到地址initrd_start
代码[3]:判断加载的是不是cpio-initrd。实际上 unpack_to_rootfs有两个功能一个是释放cpio包,另一个就是判断是不是cpio包, 这是通过最后一个参数来区分的, 0:释放 1:查看。
代码[4]:如果是cpio-initrd则将其内容释放出来到rootfs中。
代码[5]:如果不是cpio-initrd,则认为是一个image-initrd,将其内容保存到/initrd.image。在后面的image-initrd的处理代码中会读取/initrd.image。
对image-initrd的处理在prepare_namespace函数里,包含了对image-initrd进行处理的代码,相关代码如下:
void __init prepare_namespace(void){
[1]    if (initrd_load())
        goto out;
out:
        umount_devfs("/dev");
[2]        sys_mount(".", "/", NULL, MS_MOVE, NULL);
        sys_chroot(".");
        security_sb_post_mountroot();
        mount_devfs_fs ();
}
代码[1]:执行initrd_load函数,将initrd载入,如果载入成功的话initrd_load函数会将realfs的根设置为当前目录。
代码[2]:将当前目录即realfs的根mount为Linux VFS的根。initrd_load函数执行完后,将真正的文件系统的根设置为当前目录。
initrd_load函数负责载入image-initrd,代码如下:
int __init initrd_load(void)
{
[1]    if (mount_initrd) {
透射电镜制样        create_dev("/dev/ram", Root_RAM0, NULL);
[2]        if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) {
            sys_unlink("/initrd.image");
            handle_initrd();
            return 1;
        }
    }
    sys_unlink("/initrd.image");
    return 0;
}
代码[1]:如果加载initrd则建立一个ram0设备 /dev/ram
代码[2]:/initrd.image文件保存的就是image-initrd,rd_load_image函数执行具体的加载操作,image-nitrd的文件内容释放到ram0里。判断ROOT_DEV!=Root_RAM0的含义是,如果你在grub或者lilo里配置了 root=/dev/ram0 ,则实际上真正的根设备就是initrd了,所以就不把它作为initrd处理 ,而是作为realfs处理。
handle_initrd()函数负责对initrd进行具体的处理,代码如下:
    static void __init handle_initrd(void){
[1]    real_root_dev = new_encode_dev(ROOT_DEV);
[2]    create_dev("/dev/root.old", Root_RAM0, NULL);
    mount_block_root("/dev/root.old", root_mountflags & ~MS_RDONLY);
[3]    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 */
[4]    sys_chdir("/root");
    sys_mount(".", "/", NULL, MS_MOVE, NULL);
    sys_chroot(".");
    mount_devfs_fs ();
[5]    pid = kernel_thread(do_linuxrc, "/linuxrc", SIGCHLD);
    if (pid > 0) {
        while (pid != sys_wait4(-1, &i, 0, NULL))
            yield();
热流道温控器    }
    /* move initrd to rootfs' /old */
    sys_fchdir(old_fd);
    sys_mount("/", ".", NULL, MS_MOVE, NULL);
    /* switch root and cwd back to / of rootfs */
[6]    sys_fchdir(root_fd);
最大功率点跟踪
    sys_chroot(".");
    sys_close(old_fd);
    sys_close(root_fd);
    umount_devfs("/old/dev");
[7]    if (new_decode_dev(real_root_dev) == Root_RAM0) {
        sys_chdir("/old");
        return;
    }
[8]    ROOT_DEV = new_decode_dev(real_root_dev);
    mount_root();
[9]    printk(KERN_NOTICE "Trying to move old root to /initrd ... ");
    error = sys_mount("/old", "/root/initrd", NULL, MS_MOVE, NULL);
    if (!error)
        printk("okay\n");
    else {
        int fd = sys_open("/dev/root.old", O_RDWR, 0);
        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");
    }
   

本文发布于:2024-09-25 19:24:02,感谢您对本站的认可!

本文链接:https://www.17tex.com/tex/2/104182.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:加载   内核   文件   函数   代码   执行   内存   标准
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议