linux内核学习17---根文件系统

linux文件系统初始化过程主要分为三个阶段:挂载rootfs,提供第一个挂载点’’/;加载initrd,扩展VFS树;执行init程序,完成linux系统的初始化。下面会详细介绍每个阶段的主要内容。

  1. vfs_caches_init()负责挂载rootfs文件系统,并创建了第一个挂载点目录:’/’;

  2. rest_init()负责加载initrd文件,扩展VFS树,创建基本的文件系统目录拓扑;

  3. init程序负责挂载磁盘文件系统,并将文件系统的根目录从rootfs切换到磁盘文件系统;
    在这里插入图片描述

参考:https://www.cnblogs.com/shangye/p/6260471.html
https://www.cnblogs.com/alantu2018/p/8447303.html

1. 注册挂载根文件系统(初始化,内容空的)

start_kernel
  vfs_caches_init
    mnt_init
      sysfs_init 注册并挂载sysfs文件系统,sysfs先于rootfs挂载是为全面展示linux驱动模型做好准备,sysfs文件系统目前还没有挂载到rootfs的某个挂载点上,后续init程序会把sysfs挂载到rootfs的sys挂载点上
      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);
    bdev_cache_init()
    	chrdev_init()字符设备相关的初始化
  ...
  rest_init
    kernel_thread(kernel_init, NULL, CLONE_FS);

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

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

从上面可以看到注册并挂载sysfs文件系统,sysfs先于rootfs挂载是为全面展示linux驱动模型做好准备,sysfs文件系统目前还没有挂载到rootfs的某个挂载点上,后续init程序会把sysfs挂载到rootfs的sys挂载点上

在这里,将rootfs文件系统挂载。它的挂载点默认为”/”.最后切换进程的根目录和当前目录为”/”.这也就是根目录的由来。不过这里只是初始化。等挂载完具体的文件系统之后,一般都会将根目录切换到具体的文件系统。所以在系统启动之后,用mount命令是看不到rootfs的挂载信息的.

2. 虚拟文件系统的挂载(解压具体的文件系统到根文件系统里面)

参考:https://www.cnblogs.com/schips/p/13178870.html
https://blog.csdn.net/crazycoder8848/article/details/79177228
https://www.cnblogs.com/shangye/p/6260471.html

static int __init kernel_init(void * unused)
{
    
    
         ……
         ……
         do_basic_setup();
if (!ramdisk_execute_command)
                   ramdisk_execute_command = "/init";
         if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
    
    
                   ramdisk_execute_command = NULL;
                   prepare_namespace();
         }
         /*
          \* Ok, we have completed the initial bootup, and
          \* we're essentially up and running. Get rid of the
          \* initmem segments and start the user-mode stuff..
          */
         init_post();
         return 0;
}

do_basic_setup()是一个很关键的函数,所有直接编译在kernel中的模块都是由它启动的。代码片段如下:

static void __init do_basic_setup(void)
{
    
    
         /* drivers will send hotplug events */
         init_workqueues();
         usermodehelper_init();
         driver_init();
         init_irq_proc();
         do_initcalls();
}

Do_initcalls()用来启动所有在__initcall_start和__initcall_end段的函数,而静态编译进内核的modules也会将其入口放置在这段区间里。

跟根文件系统相关的初始化函数都会由rootfs_initcall()所引用。注意到有以下初始化函数:

rootfs_initcall(populate_rootfs);

也就是说会在系统初始化的时候会调用populate_rootfs进行初始化。

总流程位:

kernel_init
	do_basic_setup
		do_initcalls
			rootfs_initcall(populate_rootfs) 
	prepare_namespace 检查init是否存在,如果不在,注册其他的根文件系统

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处理好。

具体过程,就是解压压缩包,根据解压出的内容,在初始的根文件系统中创建目录、文件,然后将解压出的文件的内容部分write到创建的文件中。由于已经有根文件系统了,因此相关代码就是通过调用通用的文件操作方面的系统调用,来完成上述任务的。

prepare_namespace
是调用mount_root挂载/dev/root,将/dev/root挂载到/root上,并且将当前目前切换到/root上

3. 构建最小根文件系统所需要的东西

总结最小根文件系统所需东西

  1. 终端/dev/console 设置/dev/null (如果没有设置标准输入,输出,错误——>无底洞,输出看不到)
  2. 设置配置文化inittab(配置文件里指定的应用程序或默认配置)
  3. 需要库(我们想我们自己建立的.c文件里的各种fopen,fread都是c库)
  4. init本身,即busybox

猜你喜欢

转载自blog.csdn.net/weixin_40535588/article/details/121232419