300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > 【linux kernel】挂载根文件系统之rootfs

【linux kernel】挂载根文件系统之rootfs

时间:2020-06-18 16:56:59

相关推荐

【linux kernel】挂载根文件系统之rootfs

挂载根文件系统之rootfs

文章目录

挂载根文件系统之rootfs一、开篇二、rootfs根文件系统(2-1)初始化rootfs(2-2)挂载rootfs文件系统(2-3)创建简单的rootfs根文件系统目录和文件(2-4)打开0、1、2文件描述符三、挂载用户指定的根文件系统四、结尾

一、开篇

​ 对于linux内核,文件系统可以说是给内核增添了无尽的“乐趣”。在linux运行情况下,对于一个文件系统来说,只有挂载到内存中目录树的一个目录下,文件系统才会被linux所访问。linux内核中很多地方都运用“父—子”概念。在文件系统部分,也同样使用了该概念。对于linux内核中第一个文件系统,不能通过mount命令或者系统调用来挂载。这时候内核是通过以下两种机制来挂载根文件系统。

​ 根文件系统的概念这里从内核的角度和用户的角度来看,先从linux内核角度来看,根文件系统是rootfs;从用户的角度来看,根文件系统是用户指定的根文件系统,在linux引导时通过内核参数root=指定。二者的关系是:在linux内核启动流程的后续会把用户指定的根文件系统挂载到rootfs文件系统的根目录下。

二、rootfs根文件系统

​ 在linux内核启动过程中,最先挂载的根文件系统是rootfs文件系统,该文件系统是一个内存文件系统,即是基于内存的,而且对用户隐藏。该文件系统非常重要,每个进程所使用的标准输入、标准输出和标准错误,对应文件描述符0、1和2,这3个文件描述符都对应rootfs文件系统中的字符设备文件"/dev/console"。下面将从源码的角度来看看rootfs

(2-1)初始化rootfs

初始化rootfs文件系统由init_rootfs()函数完成,定义如下:

int __init init_rootfs(void){int err = register_filesystem(&rootfs_fs_type);if (err)return err;if (IS_ENABLED(CONFIG_TMPFS) && !saved_root_name[0] &&(!root_fs_names || strstr(root_fs_names, "tmpfs"))) {err = shmem_init();is_tmpfs = true;} else {err = init_ramfs_fs();}if (err)unregister_filesystem(&rootfs_fs_type);return err;}

在以上代码中,会调用register_filesystem()向linux内核注册rootfs文件系统类型rootfs_fs_type。定义如下:

static struct dentry *rootfs_mount(struct file_system_type *fs_type,int flags, const char *dev_name, void *data){static unsigned long once;void *fill = ramfs_fill_super;if (test_and_set_bit(0, &once))return ERR_PTR(-ENODEV);if (IS_ENABLED(CONFIG_TMPFS) && is_tmpfs)fill = shmem_fill_super;return mount_nodev(fs_type, flags, data, fill);}static struct file_system_type rootfs_fs_type = {.name= "rootfs",.mount= rootfs_mount,.kill_sb= kill_litter_super,};

(2-2)挂载rootfs文件系统

挂载rootfs文件系统由init_mount_tree()函数完成,从该函数名可形象的知道该函数的功能是:初始化挂载树。函数定义如下:

static void __init init_mount_tree(void){struct vfsmount *mnt;struct mnt_namespace *ns;struct path root;struct file_system_type *type;type = get_fs_type("rootfs");if (!type)panic("Can't find rootfs type");mnt = vfs_kern_mount(type, 0, "rootfs", NULL);put_filesystem(type);if (IS_ERR(mnt))panic("Can't create rootfs");ns = create_mnt_ns(mnt);if (IS_ERR(ns))panic("Can't allocate initial namespace");init_task.nsproxy->mnt_ns = ns;get_mnt_ns(ns);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);}

上述第11行代码,使用vfs_kern_mount()挂载rootfs文件系统。

第16行代码,使用create_mnt_ns(mnt)函数创建第一个挂载命令空间。

上述第20行代码,将设置0号线程的挂载命名空间。

第27和28行代码,将0号线程的当前工作目录和根目录设置为rootfs文件系统的根目录。

(2-3)创建简单的rootfs根文件系统目录和文件

在内核后续的启动过程中,default_rootfs()函数会在rootfs文件系统中创建必须的目录和文件节点。如下代码所示(/init/noinitramfs.c):

static int __init default_rootfs(void){int err;err = ksys_mkdir((const char __user __force *) "/dev", 0755);if (err < 0)goto out;err = ksys_mknod((const char __user __force *) "/dev/console",S_IFCHR | S_IRUSR | S_IWUSR,new_encode_dev(MKDEV(5, 1)));if (err < 0)goto out;err = ksys_mkdir((const char __user __force *) "/root", 0700);if (err < 0)goto out;return 0;out:printk(KERN_WARNING "Failed to create a rootfs\n");return err;}rootfs_initcall(default_rootfs);

从以上代码可知,default_rootfs()将创建/dev目录;创建控制台的字符设备文件/dev/console,主设备号是5,从设备号是1.

还将创建/root目录。最后会使用rootfs_initcall宏将default_rootfs函数加入到初始化节段中。

【特别注意】对于default_rootfs函数的编译将在没有设置initrd/initramfs的条件下进行。如果设置了【Initial RAM filesystem and RAMdisk(initramfs/initrd)support】选项。那么default_rootfs()函数将不会编译进linux内核!

(2-4)打开0、1、2文件描述符

在linux内核1号线程的线程函数中,会调用kernel_init_freeable()函数,在该函数中会打开控制台的字符设备文件/dev/console,得到文件描述符0,然后两次复制文件描述符0,得到文件描述符1和2。如下代码片段:

/* Open the /dev/console on the rootfs, this should never fail */if (ksys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)pr_err("Warning: unable to open an initial console.\n");(void) ksys_dup(0);(void) ksys_dup(0);

​ linux内核的1号线程会使用try_to_run_init_process()试图加载用户空间的程序,从而使内核向用户空间转换,在这个过程中,1号线程将作为其他线程的父进程,从而子线程将继承打开的文件表,从而也将继承文件描述符0、1、2,这就是每个进程所使用的标准输入、标准输出和标准错误具有统一性的原因。

三、挂载用户指定的根文件系统

​ 在引导linux内核时,可以使用内核参数root来指定存储设备的名称,使用rootfstype内核参数指定根文件系统的类型,从而挂载用户的根文件系统。在源码中,具体的实现由prepare_namespace()函数来完成,这部分内容见该篇文章:《【linux kernel】linux内核如何挂载根文件系统》

四、结尾

该篇文章内容的源码出自linux内核版本:4.19.4,小生对比了linux内核2.6版本的源码,发现init_rootfs()init_mount_tree()这两个函数内容没有太大变化,可见linux内核设计的深厚功力啦!!下面附上这两个函数的定义:

2.6版本的linux内核(/fs/ramfs/inode.c)init_rootfs函数定义:

int __init init_rootfs(void){int err;err = bdi_init(&ramfs_backing_dev_info);if (err)return err;err = register_filesystem(&rootfs_fs_type);if (err)bdi_destroy(&ramfs_backing_dev_info);return err;}

2.6版本的linux内核(/fs/namespace.c)init_mount_tree函数定义:

static void __init init_mount_tree(void){struct vfsmount *mnt;struct mnt_namespace *ns;struct path root;mnt = do_kern_mount("rootfs", 0, "rootfs", NULL);if (IS_ERR(mnt))panic("Can't create rootfs");ns = create_mnt_ns(mnt);if (IS_ERR(ns))panic("Can't allocate initial namespace");init_task.nsproxy->mnt_ns = ns;get_mnt_ns(ns);root.mnt = ns->root;root.dentry = ns->root->mnt_root;set_fs_pwd(current->fs, &root);set_fs_root(current->fs, &root);}

小生由于知识和精力有限,如若文章存在有不妥的地方,欢迎批评指正或与我讨论,哈哈!!

搜索关注【嵌入式小生】wx公众号获取更多精彩内容>>>>

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。