一、开篇
对于linux内核,文件系统可以说是给内核增添了无尽的“乐趣”。在linux运行情况下,对于一个文件系统来说,只有挂载到内存中目录树的一个目录下,文件系统才会被linux内核所访问。linux内核中很多地方都运用“父—子”概念。在文件系统部分,也同样使用了该概念。对于linux内核中第一个文件系统,不能通过mount命令或者系统调用来挂载。这时候内核是通过以下两种机制来挂载根文件系统。
根文件系统的概念这里从linux内核的角度和用户的角度来看,先从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
内核参数指定根文件系统的类型,从而挂载用户的根文件系统。在linux内核源码中,具体实现由prepare_namespace()
函数来完成。
四、结尾
该篇文章内容的源码出自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);
}
转载请注明:XAMPP中文组官网 » 扒开linux内核根文件系统的“外衣”