VFS基础学习笔记 - 2. VFS的构建

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,并创建"/"这个根目录

  1. dcache_init:创建dentry的slab描述符,并为dentry hash table分配空间;

  2. inode_init:创建inode的slab描述符,并为inode hash table分配空间;

  3. files_init:创建file的slab描述符;

  4. files_maxfiles_init:设置创建file最大的个数,默认每个file大小粗略估计为1K, 最大不超过内存的10%;

  5. mnt_init:注册rootfs并mount rootfs;

  6. bdev_cache_init:创建bdev的slab描述符,注册bdev文件系统,并挂载;

  7. 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

  1. vfs_kern_mount:创建vfsmount,创建super block,并通过fill_super回调填充,创建出根dentry和根inode

  2. alloc_mnt_ns:分配mount name space,关于mnt name space可以参考: Mount namespaces and shared subtrees

  3. real_mount:vfsmount为mount的内嵌结构体,此处获取到mount;

  4. 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

  1. 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;

  2. 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上。

参考文档

存储技术原理分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值