kernel 文件系统挂载流程分析【转】

转自:https://blog.csdn.net/wuu1010/article/details/94332319

1. mount系统调用

1.1. sys_mount

sys_mount主要将系统调用的参数dev_name、dir_name、type、flags、data从用户空间拷贝到内核空间,然后调用do_mount函数

sys_mount
	copy_mount_string
	copy_mount_options
	do_mount
		user_path(dirname, &path) -> path_lookupat ==> 复杂的过程,找到目录对应的挂载描述符合dentry实例
		do_remount
		do_loopback
		do_change_type
		do_move_mount
		do_new_mount	// 构建虚拟挂载点vsfmount,检查设置namespace
			get_fs_type // 根据文件系统类型的命长找到file_system_type实例
			vfs_kernel_mount    // 调用具体文件系统的mount,并将返回的root dentry与分配的vfsmount挂接
				alloc_vfsmnt    // 分配挂载描述符
				mount_fs
					type->mount // 调用具体文件系统的mount,返回root dentry
					设置vfsmount结构内容
				把挂载描述符添加到长几块的挂载实例链表中
		do_add_mount
			graft_tree
				attach_recursive_mnt
					commit_tree
						__attach_mnt
							把挂载描述符加入散列表
							把挂载描述符加入父亲的孩子列表

mount

SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,
		char __user *, type, unsigned long, flags, void __user *, data)
{
	int ret;
	char *kernel_type;
	char *kernel_dev;
	void *options;

	kernel_type = copy_mount_string(type);
	ret = PTR_ERR(kernel_type);
	if (IS_ERR(kernel_type))
		goto out_type;

	kernel_dev = copy_mount_string(dev_name);
	ret = PTR_ERR(kernel_dev);
	if (IS_ERR(kernel_dev))
		goto out_dev;

	options = copy_mount_options(data);
	ret = PTR_ERR(options);
	if (IS_ERR(options))
		goto out_data;

	ret = do_mount(kernel_dev, dir_name, kernel_type, flags, options);

	kfree(options);
out_data:
	kfree(kernel_dev);
out_dev:
	kfree(kernel_type);
out_type:
	return ret;
}

do_mount

1.2. ext4文件系统的mount

mount_fs中通过下边语句调用特定文件系统的mount函数。

root = type->mount(type, flags, name, data);
  • 1

ext4_fs_type

static struct file_system_type ext4_fs_type = {
	.owner		= THIS_MODULE,
	.name		= "ext4",
	.mount		= ext4_mount,
	.kill_sb	= kill_block_super,
	.fs_flags	= FS_REQUIRES_DEV,
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

ext4_mount

static struct dentry *ext4_mount(struct file_system_type *fs_type, int flags,
		       const char *dev_name, void *data)
{
	return mount_bdev(fs_type, flags, dev_name, data, ext4_fill_super);
}
  • 1
  • 2
  • 3
  • 4
  • 5

mount_bdev是针对块设备挂载时使用的函数,此外还有mount_nodev, mount_single等函数,分别用于不同的挂载情况。
mount_bdev函数的主要逻辑如下:

  1. blkdev_get_by_path根据设备名得到block_device结构
  2. sget得到已经存在或者新分配的super_block结构
  3. 如果是已经存在的sb,就释放第一步得到的bdev结构
  4. 如果是新的sb,就调用文件系统个别实现的fill_super函数继续处理新的sb,并创建根inode, dentry
  5. 返回得到的s_root
  6. fill_super函数将完成mount接下来重要的工作

1.4. ext4_fill_super

在磁盘挂载的时候文件系统需要从磁盘中读取超级块来填充内存中的结构,EXT4文件系统超级块的填充是由函数ext4_fill_super()来完成的。在EXT4文件系统中,磁盘上的超级块结构是与结构体structext4_super_block的定义是一致的,大小是1K,即1024个字节。顺便提下,EXT3文件系统超级块在磁盘上的大小也是1024个字节,EXT4扩展了EXT3的定义,EXT3只是占了1024个字节,有些字节没有定义,在EXT4中重新定义了,总的大小没有改变。

在挂载文件系统的时候,读取磁盘上ext4_super_block结构的值,填充内存中ext4_sb_info的结构。

2. 挂载根文件系统

start_kernel
    vfs_caches_init
        mnt_init
            init_rootfs     // 注册rootfs文件系统
            init_mount_tree // 挂载rootfs文件系统
  • 1
  • 2
  • 3
  • 4
  • 5

2.1. init_rootfs

init_rootfs注册rootfs文件系统

static struct file_system_type rootfs_fs_type = {
	.name		= "rootfs",
	.mount		= rootfs_mount,
	.kill_sb	= kill_litter_super,
};

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;
}
  • 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

2.2. init_mount_tree

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);  // 挂载rootfs文件系统
	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;                 // 设置0号线程的挂载命名工具
	get_mnt_ns(ns);

	root.mnt = mnt;
	root.dentry = mnt->mnt_root;
	mnt->mnt_flags |= MNT_LOCKED;

	set_fs_pwd(current->fs, &root);                 // 把0号线程的当前工作目录设置为rootfs文件系统的根目录
	set_fs_root(current->fs, &root);                // 把0号线程的根目录设置为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

3. 参考资料

  1. Linux内核-文件系统-挂载流程分析
  2. Linux内核深度解析-第6章 文件系统
  3. mount过程分析之五(mount_bdev->fill_super)
  4. EXT4文件系统之ext4_fill_super()
  5. EXT4文件系统笔记之ext4_fill_super
【作者】张昺华
【大饼教你学系列】https://edu.csdn.net/course/detail/10393
【新浪微博】 张昺华--sky
【twitter】 @sky2030_
【微信公众号】 张昺华
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
原文地址:https://www.cnblogs.com/sky-heaven/p/13737377.html