1. 前言
本专题我们开始学习虚拟文件系统VFS的相关内容。本专题主要参考了《存储技术原理分析》、ULA、ULK的相关内容。本文主要介绍VFS最初的模型是如何构建起来的。本节源于对start_kernel启动分析的一个片段,在执行start_kernel的最后会挂载rootfs,并将制作好的根文件系统释放到挂载的rootfs根目录,这样就可以启动根文件系统的init进程了。注意这里的rootfs和我们制作的根文件系统(又或叫做cpio initrd,或ramdisk.img)是两个不同的概念,后文在说明整个挂载流程的过程中会显示其中的差别。下面说明rootfs整个的挂载流程
kernel版本:5.10
FS: minix
平台:arm64
注:
为方便阅读,正文标题采用分级结构标识,每一级用一个"-“表示,如:两级为”|- -", 三级为”|- - -“
2. vfs_caches_init
start_kernel(void)
|--vfs_caches_init();
| //创建用于名字的slab描述符
|--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这个函数说起,vfs_caches_init最主要的工作就是挂载了第一个文件系统也就是rootfs,并创建"/"这个根目录
-
dcache_init:创建dentry的slab描述符,并为dentry hash table分配空间;
-
inode_init:创建inode的slab描述符,并为inode hash table分配空间;
-
files_init:创建file的slab描述符;
-
files_maxfiles_init:设置创建file最大的个数,默认每个file大小粗略估计为1K, 最大不超过内存的10%;
-
mnt_init:注册rootfs并mount rootfs;
-
bdev_cache_init:创建bdev的slab描述符,注册bdev文件系统,并挂载;
-
chrdev_init:创建cdev_map用于管理字符设备号与字符设备的映射关系
|- -mnt_init
void __init mnt_init(void)
| //创建mount的slab描述符
|--mnt_cache = kmem_cache_create("mnt_cache", sizeof(struct mount),
| 0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);
| //分配mount hash table空间
|--mount_hashtable = alloc_large_system_hash("Mount-cache", sizeof(struct hlist_head),...);
| //分配 mountpoint hash table空间
|--mountpoint_hashtable = alloc_large_system_hash("Mountpoint-cache",sizeof(struct hlist_head),...);
| //创建kernfs_node和kernfs_iattrs的slab描述符
|--kernfs_init();
| //注册sysfs
|--sysfs_init();
|--fs_kobj = kobject_create_and_add("fs", NULL);
| //注册并挂载shmem_fs_type文件系统
|--shmem_init();
|--init_rootfs();
|--init_mount_tree();
|- - -init_rootfs
void __init init_rootfs(void)
{
if (IS_ENABLED(CONFIG_TMPFS) && !saved_root_name[0] &&
(!root_fs_names || strstr(root_fs_names, "tmpfs")))
is_tmpfs = true;
}
此处假设is_tmpfs 为false
|- - -init_mount_tree
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);
|--ns = alloc_mnt_ns(&init_user_ns, false)
|--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);
init_mount_tree完成真正的rootfs的mount动作,创建superblock并填充,创建出根dentry和indoe,并构建mount tree
-
vfs_kern_mount:创建vfsmount,创建super block,并通过fill_super回调填充,创建出根dentry和根inode
-
alloc_mnt_ns:分配mount name space,关于mnt name space可以参考: Mount namespaces and shared subtrees
-
real_mount:vfsmount为mount的内嵌结构体,此处获取到mount;
-
set_fs_pwd/set_fs_root:设置当前进程(也就是init_task,0号进程,之后会退化为idle进程)的当前目录pwd为根dentry,同时将当前进程的根目录也设置为根dentry,正因为把当前目录设为根dentry,因此后文中在将initrd释放到rootfs时会释放到/目录下。
vfs_kern_mount(&rootfs_fs_type, 0, "rootfs", NULL);
|--fc = fs_context_for_mount(type, flags);
| |--alloc_fs_context(fs_type, NULL, sb_flags, 0, FS_CONTEXT_FOR_MOUNT);
| |--fc = kzalloc(sizeof(struct fs_context), GFP_KERNEL);
| |--init_fs_context = fc->fs_type->init_fs_context;
| |--init_fs_context(fc);
|--vfs_parse_fs_string(fc, "source", name, strlen(name));
|--parse_monolithic_mount_data(fc, data);
|--fc_mount(fc);
| |--vfs_get_tree(fc)
| | |--sb = sget_fc(fc, test, set_anon_super_fc);
| | |--ramfs_fill_super(sb, fc);
| |--vfs_create_mount(fc)
| | |--mnt = alloc_vfsmnt(fc->source ?: "none");
| | |--mnt->mnt.mnt_root = dget(fc->root);
| | | // 将挂载点设为自身的根dentry
| | | mnt->mnt_mountpoint = mnt->mnt.mnt_root;
| | | //将父vfsmount也设为自身的vfsmount
| | | mnt->mnt_parent = mnt;
|--put_fs_context(fc);
vfs_kern_mount创建vfsmount,创建super block,并通过fill_super回调填充,创建出根dentry和根inode
-
fs_context_for_mount
会分配fs_context并初始化,其中会执行fc->fs_type->init_fs_context,即rootfs_init_fs_context,以进一步初始化fc,此处由于is_tmpfs为NULL,因此rootfs_init_fs_context会调用ramfs_init_fs_context来初始化fc->ops = &ramfs_context_ops; -
fc_mount
最终会调用到fc->ops->get_tree->ramfs_get_tree->get_tree_nodev来创建superblock并填充,返回vfsmount;
(1) vfs_get_tree:创建super block, root denry, root inode
(a) fc->ops->get_tree->ramfs_get_tree->get_tree_nodev->vfs_get_super->sget_fc会创建super_block;
(b) fc->ops->get_tree->ramfs_get_tree->get_tree_nodev->vfs_get_super->fill_super回调也就是ramfs_fill_super来填充super_block,ramfs_fill_super->ramfs_get_inode创建出根inode, ramfs_fill_super->d_make_root创建出根dentry;
(2) vfs_create_mount:分配mount,将挂载点设为自身的根dentry,将父vfsmount也设为自身的vfsmount,这样就构建起内核的一棵mount tree,之后其它的文件系统将挂载到这棵mount tree上。
参考文档
存储技术原理分析