内核文件系统挂载和Open文件

2022-08-17 12:26:33 浏览数 (1)

ext4挂载

  • 在linux 5.x的内核中,实际文件系统的挂载采用新的挂载API,引入了struct fs_context用于内部文件系统挂载的信息。
  • 应用端发起mount命令,进入mount系统调用,执行do_mount的函数
代码语言:javascript复制
// vfs层保留该小节需要的核心字段
struct super_block {
	// 文件系统类型
	struct file_system_type	*s_type;
	// 文件系统super_block的操作函数
	const struct super_operations	*s_op;
	// 根目录
	struct dentry		*s_root;
	// 每个实际文件系统的超级块,比如ext4,这里就会存储ext4的super block
	void			*s_fs_info;	/* Filesystem private info */

} __randomize_layout;

// 实际的ext4文件系统,super_block中的s_type中存储了ext4_fs_type的信息
static struct file_system_type ext4_fs_type = {
	.owner		= THIS_MODULE,
	.name		= "ext4",
	// vfs层对应的mout的时间函数
	.mount		= ext4_mount,
	.kill_sb	= kill_block_super,
	.fs_flags	= FS_REQUIRES_DEV,
};
// ext4作为kernel module形式注入kernel,在ext4_init_fs注册文件系统
static int __init ext4_init_fs(void)
{
	register_filesystem(&ext4_fs_type);
}


// 应用端发出mount命令挂载的系统调用
COMPAT_SYSCALL_DEFINE5(mount, const char __user *, dev_name,
		       const char __user *, dir_name,
		       const char __user *, type, compat_ulong_t, flags,
		       const void __user *, data)
{
	// 实际是调用内核的do_mount函数来完成mount操作
	retval = do_mount(kernel_dev, dir_name, kernel_type, flags, options);
}

long do_mount(const char *dev_name, const char __user *dir_name,
		const char *type_page, unsigned long flags, void *data_page)
{
	do_new_mount(&path, type_page, sb_flags, mnt_flags,
				      dev_name, data_page);
}

static int do_new_mount(struct path *path, const char *fstype, int sb_flags,
			int mnt_flags, const char *name, void *data)
{
	// 根据文件系统类型名称获取文件系统的内核module.这里是ext4会根据ext4来获取ext4 文件系统的kernel module
	type = get_fs_type(fstype);
	// 为此次文件系统挂载创建文件系统挂载上下文
	fc = fs_context_for_mount(type, sb_flags);
	// 在这里实际调用文件的mount函数赋值,比如这是ext4_mount.
	err = vfs_get_tree(fc);
	// 内核开始进行挂载
	err = do_new_mount_fc(fc, path, mnt_flags);
}


// 初始化一个fs_context,同时给fs_context中的struct dentry *root,这个root包含了dentry和super_block.其中root是通过 legacy_get_tree中调用具体文件系统的mount函数,这里是调用ext4_mount函数,初始化root dentry
struct fs_context *fs_context_for_mount(struct file_system_type *fs_type,
					unsigned int sb_flags)
{

	return alloc_fs_context(fs_type, NULL, sb_flags, 0,
					FS_CONTEXT_FOR_MOUNT);
}
static struct fs_context *alloc_fs_context(struct file_system_type *fs_type,
				      struct dentry *reference,
				      unsigned int sb_flags,
				      unsigned int sb_flags_mask,
				      enum fs_context_purpose purpose)
{
	// ext4中ext4_fs_type并没有的函数指针为空
	init_fs_context = fc->fs_type->init_fs_context;
	if (!init_fs_context)
		// 这里默认init_fs_context
		init_fs_context = legacy_init_fs_context;
	// legacy_init_fs_context调用,fs_context->ops = &legacy_fs_context_ops;这里legacy_fs_context_ops定义了函数指针表。这里谈到的是vfs层的mount系统调用和实际的文件系统的mount有有关的就是legacy_fs_context_ops.get_tree函数
	ret = init_fs_context(fc);
}

//  新内核引入的fs_context_operations,vfs层的mount是是如何和真实的文件系统mount函数挂钩,这里就是调用fs_context_operations.get_tree实现
const struct fs_context_operations legacy_fs_context_ops = {
	.free			= legacy_fs_context_free,
	.dup			= legacy_fs_context_dup,
	.parse_param		= legacy_parse_param,
	.parse_monolithic	= legacy_parse_monolithic,
	 //这里是调用真是文件系统的ext4_mount函数
	.get_tree		= legacy_get_tree,
	.reconfigure		= legacy_reconfigure,
};


// 在这里调用真实文件系统的ext4的mount函数入口
int vfs_get_tree(struct fs_context *fc)
{
   // get_tree指向legacy_get_tree函数
	error = fc->ops->get_tree(fc);
}


// do_mount函数会调用do_new_mount,而do_new_mount函数会调用do_new_mount_fc来针对每个挂载的文件系统创建struct mount结构,然后再这个函数里面调用do_add_mount完成ext4文件系统的挂载
static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
			   unsigned int mnt_flags)
{
	struct vfsmount *mnt;
	struct super_block *sb = fc->root->d_sb;
	mnt = vfs_create_mount(fc);

	mnt_warn_timestamp_expiry(mountpoint, mnt);

	error = do_add_mount(real_mount(mnt), mountpoint, mnt_flags);
	if (error < 0)
		mntput(mnt);
	return error;
}

// 这里是挂载实际文件系统的mount的函数调用
static int legacy_get_tree(struct fs_context *fc)
{
	struct legacy_fs_context *ctx = fc->fs_private;
	struct super_block *sb;
	struct dentry *root;

	root = fc->fs_type->mount(fc->fs_type, fc->sb_flags,
				      fc->source, ctx->legacy_data);
	if (IS_ERR(root))
		return PTR_ERR(root);

	sb = root->d_sb;
	BUG_ON(!sb);

	fc->root = root;
	return 0;
}

ext4中super_block初始化

  • os提供vfs层来建立用户态文件操作和真实磁盘文件系统之间关系。vfs层的super_block是通过工厂模式设计模式来整合真实文件系统的super_block.
  • ext4的super_block,初始化是在用户态发起mount时候进行初始化。vfs中文件创建、删除、写入都会在vfs层的inode操作,最终会进入实际文件系统的inode操作
代码语言:javascript复制
// 实际文件系统的super_block的操作函数
static const struct super_operations ext4_sops = {
	// ext4文件系统inode申请
	.alloc_inode	= ext4_alloc_inode,
	// ext4文件系统inode释放
	.free_inode	= ext4_free_in_core_inode,
	.destroy_inode	= ext4_destroy_inode,
	// inode的写入操作函数
	.write_inode	= ext4_write_inode,
	.dirty_inode	= ext4_dirty_inode,
	.drop_inode	= ext4_drop_inode,
	.evict_inode	= ext4_evict_inode,
	.put_super	= ext4_put_super,
	.sync_fs	= ext4_sync_fs,
	.freeze_fs	= ext4_freeze,
	.unfreeze_fs	= ext4_unfreeze,
	.statfs		= ext4_statfs,
	.remount_fs	= ext4_remount,
	.show_options	= ext4_show_options,
#ifdef CONFIG_QUOTA
	.quota_read	= ext4_quota_read,
	.quota_write	= ext4_quota_write,
	.get_dquots	= ext4_get_dquots,
#endif
	.bdev_try_to_free_page = bdev_try_to_free_page,
};

// ext4 super_block的初始化函数
static int ext4_fill_super(struct super_block *sb, void *data, int silent)
{
	sb->s_op = &ext4_sops;
}

// 挂载时候进行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);
}

open文件流程间接

  • vfs层包含了是实际文件系统的内存影像
  • 用户进程调用open函数,传入文件名称、打开文件的flags、文件的权限等信息,进入内核态的do_sys_open函数
  • 进入do_sys_open函数,首先是执行get_unused_fd_flags从当前的进程中申请未被使用的文件描述符
  • 其次是构建一个struct file结构,该结构是每个进程打开的fd关联的文件。调用了do_filp_open函数,该函数根据文件名称和文件打开的flags。
  • 如果正常执行 fd_install 函数把fd和当前进程的打开的struct file数组关联起来,把索引为fd 的struct file数组和struct file进行关联
  • 最后释放了内核态的filename,这个也是从用户态参数初始化而来
代码语言:javascript复制
// 系统调用的入口,传入文件名称、文件打开模式、文件mode,进入内核态
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
	if (force_o_largefile())
		flags |= O_LARGEFILE;

	return do_sys_open(AT_FDCWD, filename, flags, mode);
}

long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
	struct open_flags op;
	int fd = build_open_flags(flags, mode, &op);
	struct filename *tmp;

	if (fd)
		return fd;

	tmp = getname(filename);
	if (IS_ERR(tmp))
		return PTR_ERR(tmp);
	// 从当前进程文件描述符中申请一个fd,这个fd的本质就是该进程的struct file数组的下标
	fd = get_unused_fd_flags(flags);
	if (fd >= 0) {
		// 根据文件路径和flags构建一个struct file实例
		struct file *f = do_filp_open(dfd, tmp, &op);
		// 如果返回是无效的指针
		if (IS_ERR(f)) {
			// 把申请的fd放回去
			put_unused_fd(fd);
			fd = PTR_ERR(f);
		} else {
			fsnotify_open(f);
			// 把对应的fd和struct file数组进行关联
			fd_install(fd, f);
		}
	}
	putname(tmp);
	return fd;
}

// do_filp_open解析文件路径,返回进程的打开的struct file

open文件执行函数说明

  • getname: 拷贝用户态传过来的文件路径
  • get_unused_fd_flags:从当前进程中获取未被使用的文件描述符
  • do_filp_open:解析路径并返回进程打开的文件struct file
  • path_openat:路径查找函数
  • path_init:设置路径的查找的开始位置
  • link_path_walk:目录逐层查找
  • do_last:查找dcache,通过所在目录的目录项对象的inode operation为相关文件创建新inode
  • lookup_fast:从dcache中找到dentry
  • lookup_open:通过所在目录的目录项对象的inode operation为相关文件创建新inode

0 人点赞