Linux vfs流程分析 阶段一:安装 rootfs 文件系统

1 vfs整体流程

Linux版本:linux4.14

vfs函数调用流程

start_kernel()
|----vfs_caches_init_early()
|----vfs_caches_init()
		|----dcache_init()
		|----inode_init()
		|----files_init()
		|----files_maxfiles_init()
		|----mnt_init()
				|----init_rootfs()
						|----register_filesystem(&rootfs_fs_type)
				|----init_mount_tree()
		|----bdev_cache_init()
		|----chrdev_init()

2 vfs_cache_init()

void __init vfs_caches_init(void)
{
	names_cachep = kmem_cache_create("names_cache", PATH_MAX, 0,
			SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);

	dcache_init();
	inode_init();
	files_init();
	files_maxfiles_init();
	mnt_init();
	bdev_cache_init();
	chrdev_init();
}

此函数的主要作用就是初始化一些 vfs 会用到的 cache。关于 cache 的内容可参考深入理解LINUX内核第八章内存管理。

以下几个加粗的变量都是全局变量。很关键。

  • dcache_init() 初始化 dentry_cachedentry_hashtable
  • inode_init() 初始化 inode_cacheinode_hashtable
  • files_init() 初始化 filp_cache
  • files_maxfiles_init() 初始化内存中可以打开的最大的文件数。
  • mnt_init() 初始化 mount_cachemount_hashtablemountpoint_hashtable。还会调用其它的函数,放到下一节具体介绍。
  • bdev_cache_init() 初始化 bdev_cachep ,注册 bdev 文件系统,挂载 bdev 文件系统。
  • chrdev_init() 初始化 cdev_map

3 mnt_init()

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();
	if (err)
		printk(KERN_WARNING "%s: sysfs_init error: %d\n",
			__func__, err);
	fs_kobj = kobject_create_and_add("fs", NULL);
	if (!fs_kobj)
		printk(KERN_WARNING "%s: kobj create error\n", __func__);
	init_rootfs();
	init_mount_tree();
}
  • 调用 kmem_cache_create 分配 mnt_cache
  • 调用 alloc_large_system_hash 分别分配 mount_hashtablemountpoint_hashtable
  • 初始化 sysfs 文件系统。
  • 初始化 rootfs 文件系统。
  • 初始化 mount tree,最终要的就是初始化根目录,然后挂载一个空的 rootfs 上去。

4 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;
}
  • 注册 rootfs_fs_type 。

5 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);
}
  • 调用 get_fs_type(“rootfs”) 获取文件系统类型 (file_system_type)。
  • 调用 vfs_kern_mount() 挂载 rootfs 到根目录( “/” ),同时返回 vfsmount 结构体。
  • 调用 create_mnt_ns() 创建 mnt namespace。
  • 将mnt namespace 赋给 0 号进程的 task_struct init_task.nsproxy->mnt_ns。
  • 将 0 号进程的根目录与当前目录都切换到根目录( “/” )。

6 vfs_kern_mount()

struct vfsmount *
vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
{
	struct mount *mnt;
	struct dentry *root;

	if (!type)
		return ERR_PTR(-ENODEV);

	mnt = alloc_vfsmnt(name);
	if (!mnt)
		return ERR_PTR(-ENOMEM);

	if (flags & SB_KERNMOUNT)
		mnt->mnt.mnt_flags = MNT_INTERNAL;

	root = mount_fs(type, flags, name, data);
	if (IS_ERR(root)) {
		mnt_free_id(mnt);
		free_vfsmnt(mnt);
		return ERR_CAST(root);
	}

	mnt->mnt.mnt_root = root;
	mnt->mnt.mnt_sb = root->d_sb;
	mnt->mnt_mountpoint = mnt->mnt.mnt_root;
	mnt->mnt_parent = mnt;
	lock_mount_hash();
	list_add_tail(&mnt->mnt_instance, &root->d_sb->s_mounts);
	unlock_mount_hash();
	return &mnt->mnt;
}
  • 调用 alloc_vfsmnt() 分配一个 mount 结构体。
  • 调用 mount_fs() ,挂载 rootfs,返回挂载的 dentry,也就是根目录,根目录会在里面创建的。
  • 将根目录 dentry root 赋值给 mount->vfsmount.dentry mnt_root 。
  • 将 super_block root->d_sb 赋值给 mount->vfsmount.super_block mnt_sb 。
  • 将 mount->vfsmount.dentry mnt_root 赋值给 mount->dentry mnt_mountpoint 。
  • 将 mount mnt 赋值给 mount->mount mnt_parent 。
  • 将 mount mnt 通过 mnt->mnt_instance 加入到 dentry->super_block->list_head root->d_sb->s_mounts 。

7 mount_fs()

struct dentry *
mount_fs(struct file_system_type *type, int flags, const char *name, void *data)
{
	struct dentry *root;
	struct super_block *sb;
	char *secdata = NULL;
	int error = -ENOMEM;

	if (data && !(type->fs_flags & FS_BINARY_MOUNTDATA)) {
		secdata = alloc_secdata();
		if (!secdata)
			goto out;

		error = security_sb_copy_data(data, secdata);
		if (error)
			goto out_free_secdata;
	}

	root = type->mount(type, flags, name, data);
	if (IS_ERR(root)) {
		error = PTR_ERR(root);
		goto out_free_secdata;
	}
	sb = root->d_sb;
	BUG_ON(!sb);
	WARN_ON(!sb->s_bdi);

	/*
	 * Write barrier is for super_cache_count(). We place it before setting
	 * SB_BORN as the data dependency between the two functions is the
	 * superblock structure contents that we just set up, not the SB_BORN
	 * flag.
	 */
	smp_wmb();
	sb->s_flags |= SB_BORN;

	error = security_sb_kern_mount(sb, flags, secdata);
	if (error)
		goto out_sb;

	/*
	 * filesystems should never set s_maxbytes larger than MAX_LFS_FILESIZE
	 * but s_maxbytes was an unsigned long long for many releases. Throw
	 * this warning for a little while to try and catch filesystems that
	 * violate this rule.
	 */
	WARN((sb->s_maxbytes < 0), "%s set sb->s_maxbytes to "
		"negative value (%lld)\n", type->name, sb->s_maxbytes);

	up_write(&sb->s_umount);
	free_secdata(secdata);
	return root;
out_sb:
	dput(root);
	deactivate_locked_super(sb);
out_free_secdata:
	free_secdata(secdata);
out:
	return ERR_PTR(error);
}

此函数调用实际的文件系统类型中的 mount 操作,返回 mount 的目录,也就是 mountpoint 。对于 rootfs_fs_type 返回的 dentry root 也就是根目录(“/”)。下载挂载着虚 rootfs 文件系统(ramfs的一种)。后面会根据启动项中的参数,再挂载具体的根文件系统。

8 rootfs_mount()

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);
}
  • ramfs_fill_super 是根据 ramfs 文件系统填充虚拟文件系统的 super block 。
  • 调用 mount_nodev() 挂载虚 rootfs 。nodev 说明了此文件系统没有具体的 block 载体,是存在于 ram 中的。

9 mount_nodev()

struct dentry *mount_nodev(struct file_system_type *fs_type,
	int flags, void *data,
	int (*fill_super)(struct super_block *, void *, int))
{
	int error;
	struct super_block *s = sget(fs_type, NULL, set_anon_super, flags, NULL);

	if (IS_ERR(s))
		return ERR_CAST(s);

	error = fill_super(s, data, flags & SB_SILENT ? 1 : 0);
	if (error) {
		deactivate_locked_super(s);
		return ERR_PTR(error);
	}
	s->s_flags |= SB_ACTIVE;
	return dget(s->s_root);
}
  • sget 现在现存 fs_type->fs_supers 链表中查找已经存在的对应的超级块实例(因为一个设备可能已经被挂载过了),fs_supers 是 file_system_type 的成员,它指向一个特定文件系统下的所有超级块实例的链表表头。如果没能找到已经存在的超级块实例,那就只能创建一个新的了。并把这个新的 sb 加入到全局 super_blocks 链表,以及此 file_system_type 的 fs_supers 链表中。到此就得到了一个已知的或新的 super_block 实例,后面的工作都是为了填充这个 super_block 的内容,并把它加入到各种链表中。
  • fill_super 填充上面获取到的 super block 。fill_super 就是 mount_nodev(fs_type, flags, data, fill) 传进来的 fill,也就是 fill_ramfs_super 函数。

10 fill_ramfs_super()

int ramfs_fill_super(struct super_block *sb, void *data, int silent)
{
	struct ramfs_fs_info *fsi;
	struct inode *inode;
	int err;

	fsi = kzalloc(sizeof(struct ramfs_fs_info), GFP_KERNEL);
	sb->s_fs_info = fsi;
	if (!fsi)
		return -ENOMEM;

	err = ramfs_parse_options(data, &fsi->mount_opts);
	if (err)
		return err;

	sb->s_maxbytes		= MAX_LFS_FILESIZE;
	sb->s_blocksize		= PAGE_SIZE;
	sb->s_blocksize_bits	= PAGE_SHIFT;
	sb->s_magic		= RAMFS_MAGIC;
	sb->s_op		= &ramfs_ops;
	sb->s_time_gran		= 1;

	inode = ramfs_get_inode(sb, NULL, S_IFDIR | fsi->mount_opts.mode, 0);
	sb->s_root = d_make_root(inode);
	if (!sb->s_root)
		return -ENOMEM;

	return 0;
}
  • 此函数主要是 获取 ramfs 文件系统的根目录 inode,一般是根目录 inode 号为2。
  • 根据根 inode make root dentry 。将 dentry root 赋值到 super_block->dentry sb->s_root 。

11 d_make_root()

struct dentry *d_make_root(struct inode *root_inode)
{
	struct dentry *res = NULL;

	if (root_inode) {
		res = __d_alloc(root_inode->i_sb, NULL);
		if (res) {
			res->d_flags |= DCACHE_RCUACCESS;
			d_instantiate(res, root_inode);
		} else {
			iput(root_inode);
		}
	}
	return res;
}
  • 调用 __d_alloc() 分配一个目录结构。
  • 调用 d_instantiate,使用 root_inode 初始化分配的目录。

12 __d_alloc()

struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
{
	struct external_name *ext = NULL;
	struct dentry *dentry;
	char *dname;
	int err;

	dentry = kmem_cache_alloc(dentry_cache, GFP_KERNEL);
	if (!dentry)
		return NULL;

	/*
	 * We guarantee that the inline name is always NUL-terminated.
	 * This way the memcpy() done by the name switching in rename
	 * will still always have a NUL at the end, even if we might
	 * be overwriting an internal NUL character
	 */
	dentry->d_iname[DNAME_INLINE_LEN-1] = 0;
	if (unlikely(!name)) {
		name = &slash_name;
		dname = dentry->d_iname;
	} else if (name->len > DNAME_INLINE_LEN-1) {
		size_t size = offsetof(struct external_name, name[1]);
		ext = kmalloc(size + name->len, GFP_KERNEL_ACCOUNT);
		if (!ext) {
			kmem_cache_free(dentry_cache, dentry); 
			return NULL;
		}
		atomic_set(&ext->u.count, 1);
		dname = ext->name;
		if (IS_ENABLED(CONFIG_DCACHE_WORD_ACCESS))
			kasan_unpoison_shadow(dname,
				round_up(name->len + 1,	sizeof(unsigned long)));
	} else  {
		dname = dentry->d_iname;
	}	

	dentry->d_name.len = name->len;
	dentry->d_name.hash = name->hash;
	memcpy(dname, name->name, name->len);
	dname[name->len] = 0;

	/* Make sure we always see the terminating NUL character */
	smp_wmb();
	dentry->d_name.name = dname;

	dentry->d_lockref.count = 1;
	dentry->d_flags = 0;
	spin_lock_init(&dentry->d_lock);
	seqcount_init(&dentry->d_seq);
	dentry->d_inode = NULL;
	dentry->d_parent = dentry;
	dentry->d_sb = sb;
	dentry->d_op = NULL;
	dentry->d_fsdata = NULL;
	INIT_HLIST_BL_NODE(&dentry->d_hash);
	INIT_LIST_HEAD(&dentry->d_lru);
	INIT_LIST_HEAD(&dentry->d_subdirs);
	INIT_HLIST_NODE(&dentry->d_u.d_alias);
	INIT_LIST_HEAD(&dentry->d_child);
	d_set_d_op(dentry, dentry->d_sb->s_d_op);

	if (dentry->d_op && dentry->d_op->d_init) {
		err = dentry->d_op->d_init(dentry);
		if (err) {
			if (dname_external(dentry))
				kfree(external_name(dentry));
			kmem_cache_free(dentry_cache, dentry);
			return NULL;
		}
	}

	if (unlikely(ext)) {
		pg_data_t *pgdat = page_pgdat(virt_to_page(ext));
		mod_node_page_state(pgdat, NR_INDIRECTLY_RECLAIMABLE_BYTES,
				    ksize(ext));
	}

	this_cpu_inc(nr_dentry);

	return dentry;
}
  • 调用此函数的时候,第二个参数为 NULL,当第二个参数为 NULL 时,目录的名字就为 slash_name 。
  • const struct qstr slash_name = QSTR_INIT("/", 1); slash_name 就是根目录的名字(“/”)。
  • 其他的就不具体分析了。

至此虚 rootfs 就挂载在根目录下了,此文件系统的目的就是提供一个空的目录结构,等待后面再挂载具体的文件系统。这个我们下一篇文章在分析。

此时的文件系统如下图所示。此文件系统是存在于 ram 中的。

Linux vfs流程分析 阶段一:安装 rootfs 文件系统_第1张图片

你可能感兴趣的:(linux)