rootfs是如何挂载的?

日期 内核版本 架构
2022-10-09 Linux5.4.200 ARM64

前言

前不久项目上遇到了rootfs无法挂载的问题,就着这个问题顺便了解一下rootfs的挂载流程,总结于此分享给大家。

首先帮大家复习几个概念。

术语 解析
rootfs 根文件系统,对应/目录节点。分为虚拟rootfs真实rootfs
虚拟rootfs由内核自己创建和加载,仅仅存在于内存之中,其文件系统是tmpfs类型或者ramfs类型
真实rootfs则是指根文件系统存在于存储设备上,内核在启动过程中会在虚拟rootfs上挂载这个存储设备,然后将/目录节点切换到这个存储设备上,这样存储设备上的文件系统就会被作为根文件系统使用
ramdisk 将系统一部分内存区域实现为/dev/ram,把/dev/ram作为作为一个存储设备,最终将根目录切换到/dev/ram的挂载,实现将/dev/ram作为根文件系统的目的。
使用ext2格式的文件系统
initramfs kernel 2.5中引入的技术,在内核镜像中附加一个cpio包,这个cpio包中包含了一个小型的文件系统,当内核启动时,内核将这个 cpio包解开,并且将其中包含的文件系统释放到rootfs中,内核中的一部分初始化代码会放到这个文件系统中,作为用户层进程来执行。

调用关系链

下面总结了一下rootfs挂载的调用过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
start_kernel
|->vfs_caches_init
| |->mnt_init
| |->sysfs_init
| |->init_rootfs
| |->init_mount_tree
|
|->rest_init
| |->kernel_init
| |->kernel_init_freeable
| |->do_basic_setup
| |->do_initcalls
| |->do_initcall_level
| |->do_one_initcall
| |->rootfs_initcall(populate_rootfs);
|->init

在挂载rootfs前会先挂载sysfs,确保sysfs能够完整的记录下设备驱动模型。

sysfs_init()完成注册和挂载sysfs文件系统的功能;init_rootfs()负责注册rootfsinit_mount_tree()负责挂载rootfs,并将init_task的命名空间与之联系起来。

所以在了解rootfs的挂载之前,我们先学习一下Linux文件系统初始化以及sysfs如何挂载。

Linux文件系统初始化

展开vfs_caches_init来看。

1
2
3
4
5
6
7
8
9
10
11
12
13
void __init vfs_caches_init(void)
{
names_cachep = kmem_cache_create_usercopy("names_cache", PATH_MAX, 0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC, 0, PATH_MAX, NULL);

dcache_init();
inode_init();
files_init();
files_maxfiles_init();
mnt_init();
bdev_cache_init();
chrdev_init();
}
  • vfs_caches_init()首先建立并初始化:

    • 目录hash表 - dentry_hashtable
    • 索引节点hash表 - inode_hashtable
    • 注:Linux使用哈希表存储目录和索引节点,以提高目录和索引节点的查找效率
  • 设置内核可以打开的最大文件数

  • 通过mnt_init完成sysfsrootfs文件系统的注册和挂载

sysfs挂载流程

sysfs负责记录Linux设备驱动模型.

https://elixir.bootlin.com/linux/v5.4.200/source/fs/namespace.c#L3766

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void __init mnt_init(void)
{
int err;

mnt_cache = kmem_cache_create("mnt_cache", sizeof(struct mount),
0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);

mount_hashtable = alloc_large_system_hash("Mount-cache",
sizeof(struct hlist_head),
mhash_entries, 19,
HASH_ZERO,
&m_hash_shift, &m_hash_mask, 0, 0);
mountpoint_hashtable = alloc_large_system_hash("Mountpoint-cache",
sizeof(struct hlist_head),
mphash_entries, 19,
HASH_ZERO,
&mp_hash_shift, &mp_hash_mask, 0, 0);

if (!mount_hashtable || !mountpoint_hashtable)
panic("Failed to allocate mount hash table\n");

kernfs_init();

err = sysfs_init(); // 注册并挂载sysfs文件系统
if (err)
printk(KERN_WARNING "%s: sysfs_init error: %d\n",
__func__, err);
fs_kobj = kobject_create_and_add("fs", NULL); // 创建fs目录
if (!fs_kobj)
printk(KERN_WARNING "%s: kobj create error\n", __func__);
shmem_init();
init_rootfs();
init_mount_tree();
}

mnt_init()调用sysfs_init()注册并挂载sysfs文件系统,然后调用kobject_create_and_add()创建fs目录。

https://elixir.bootlin.com/linux/v5.4.200/source/fs/sysfs/mount.c#L97

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __init sysfs_init(void)
{
int err;

sysfs_root = kernfs_create_root(NULL, KERNFS_ROOT_EXTRA_OPEN_PERM_CHECK,
NULL);
if (IS_ERR(sysfs_root))
return PTR_ERR(sysfs_root);

sysfs_root_kn = sysfs_root->kn;

err = register_filesystem(&sysfs_fs_type);
if (err) {
kernfs_destroy_root(sysfs_root);
return err;
}

return 0;
}

第一步:创建根节点sysfs根节点。

第二步:building block of kernfs hierarchy,每一个节点都代表一个kernfs_node.

第三步sysfs_init()调用register_filesystem()注册文件系统类型sysfs_fs_type,并加入到全局单链表file_systems中。

sysfs_fs_type定义如下,.mount成员函数负责超级块、根目录和索引节点的创建和初始化工作.

1
2
3
4
5
6
static struct file_system_type sysfs_fs_type = {
.name = "sysfs",
.init_fs_context = sysfs_init_fs_context,
.kill_sb = sysfs_kill_sb,
.fs_flags = FS_USERNS_MOUNT,
};

xJly2n.png

通过以上步骤,sysfs文件系统在VFS中的视图如下:

挂载点指向超级块和根目录;超级块处在super_blocks单链表中,并且链接起所有属于该文件系统的索引节点;根目录’/'和目录"fs"指向各自的索引节点;为了提高查找效率,索引节点保存在hash表中。

xJ8KbV.png

rootfs挂载过程

mnt_init()调用init_rootfs()注册文件系统类型rootfs_fs_type,并加入到全局单链表file_systems中。

rootfs_fs_type定义如下,mount成员函数负责超级块、根目录和索引节点的建立和初始化工作。

1
2
3
4
5
struct file_system_type rootfs_fs_type = {
.name = "rootfs",
.init_fs_context = rootfs_init_fs_context,
.kill_sb = kill_litter_super,
};
  1. init_mount_tree()调用vfs_kern_mount()挂载rootfs文件系统.

  2. init_mount_tree()创建命名空间,并设置该命名空间的挂载点为rootfs的挂载点,同时将rootfs的挂载点链接到该命名空间的双向链表中。

  3. init_mount_tree()设置init_task的命名空间,同时调用set_fs_pwd()set_fs_root()设置init_task任务的当前目录和根目录为rootfs的根目录’/’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static void __init init_mount_tree(void)
{
struct vfsmount *mnt;
struct mount *m;
struct mnt_namespace *ns;
struct path root;

mnt = vfs_kern_mount(&rootfs_fs_type, 0, "rootfs", NULL);
if (IS_ERR(mnt))
panic("Can't create rootfs");

ns = alloc_mnt_ns(&init_user_ns, false);// 创建namespace
if (IS_ERR(ns))
panic("Can't allocate initial namespace");
m = real_mount(mnt);
m->mnt_ns = ns;
ns->root = m;
ns->mounts = 1;
list_add(&m->mnt_list, &ns->list);
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);
}

通过以上分析,我们发现sysfsrootfs的区别在于:虽然系统同时挂载了sysfsrootfs文件系统,但是只有rootfs处于init_task进程的命名空间内,也就是说系统启动和初始化阶段使用的是rootfs文件系统。

总结

sysfsrootfsVFS中的视图如下:

xJl6vq.png

  • Linux文件系统在初始化时,同时挂载了sysfsrootfs文件系统
  • 只有rootfs处于进程的命名空间中,且进程的root目录和pwd目录都指向rootfs的根目录
  • Linux的VFS已经准备好了根目录,用户可以使用系统调用对VFS树进行扩展
-------------本文结束感谢您的阅读-------------

本文标题:rootfs是如何挂载的?

文章作者:孤岛violet

发布时间:2022年10月09日 - 18:00

最后更新:2022年10月09日 - 12:13

原始链接:http://yoursite.com/2022/10/09/ramdisk/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

undefined