前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >聊聊文件系统的原理

聊聊文件系统的原理

作者头像
用户4700054
发布2022-08-17 12:55:03
7900
发布2022-08-17 12:55:03
举报
文章被收录于专栏:存储内核技术交流

文件系统的作用

  • linux 内核中进程管理、内存管理、网络协议栈、文件系统是内核的四大核心模块。其中文件系统提供最基础的操作文件的能力。简单概要的说,内核中有vfs实际文件系统(比如ext4),vfs是虚拟文件系统,是内核提供一种工厂设计模式的抽象层,对外提供标准的posix语义层;实际文件系统就是实现特定的文件功能的磁盘文件系统。具体如下图所描述
文件系统的IO协议栈
  • 应用程序如果以dio方式读写文件请求,首先经过内核的vfs,然后到实际的文件系统的对应的处理函数,接着请求进入那么设备映射,最后请求传递到了块设备的IO层,调度算法和IO优化也会在这里进行处理,块设备IO层处理完毕后就直接到了磁盘的驱动层,这一层就是调用IO命令来对磁盘进行读写。
文件和目录
  • linux在设计之处就流行一句话linux中一切都是文件,在文件系统设计中也非常实用。内核看待目录也是当作文件来看待。文件的inode中存储的是文件对应的数据块索引和数据,而目录的inode则是存储该目录下的文件的inode和文件名称,虽然都是inode,但是存储的数据绝不相同。文件系统中的所有的目录名称和文件名称都是存在于vfs层(仅仅是内存结构展现),这个结构是以struct dentry表示,文件或者目录是以struct inode表示。
  • 每个打开的文件在内核进程中是以文件描述符存在的,每个进程维护一个数组,这个数组的下标就是给应用返回的文件描述符,数组中的每个元素对应的是struct filestruct file中保存了struct dentry,而struct dentry中就实际包括文件的inode的信息。具体的关系如下:
Posix 函数
  • vfs层提供标准文件操作的函数接口,具体的文件操作函数是由实际问价系统提供。针对应用程序访问文件系统,比如你执行一个echo "aaa" > 1.txt命令,cat命令先去根据服务目录找文件,然后再去读取1.txt的文件数据,这里涉及到部分posix的函数,整个echo命令在文件系统层面(vfs和实际文件系统)会经历lookup->open->write->close的过程.接下来会着重分析这write语义的函数。如下针对内核 4.18进行这2个函数的分析。
  • 实际文件系统会定义针对文件或者目录定义相关的操作函数,每个inode会有const struct inode_operations *i_opconst struct file_operations *i_fop,实际文件系统的相关操作函数会在__ext4_iget中给对应的inode进行赋值
代码语言:javascript
复制
// 这里是以 ext4本地文件系统为例
const struct inode_operations ext4_dir_inode_operations = {
	// 文件创建函数
	.create		= ext4_create,
	// 查找函数
	.lookup		= ext4_lookup,
	/****** 省略其他的定义的函数*****/
};

const struct file_operations ext4_file_operations = {
	.llseek		= ext4_llseek,
	// 读函数
	.read_iter	= ext4_file_read_iter,
	// 写函数
	.write_iter	= ext4_file_write_iter,
	.unlocked_ioctl = ext4_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= ext4_compat_ioctl,
#endif
	.mmap		= ext4_file_mmap,
	.mmap_supported_flags = MAP_SYNC,
	// open函数对应实际文件系统的函数
	.open		= ext4_file_open,
	.release	= ext4_release_file,
	.fsync		= ext4_sync_file,
	.get_unmapped_area = thp_get_unmapped_area,
	.splice_read	= generic_file_splice_read,
	.splice_write	= iter_file_splice_write,
	.fallocate	= ext4_fallocate,
};
  • 用户程序发起write的posix语义,则进入内核的ksys_write->vfs_write->ext4_file_write_iter来完成这个write操作。整体流程如下:
代码语言:javascript
复制
// write函数进入内核态的ksys_write
// fd是已经打开的文件描述符,buf是需要写入的数据,count是写入的长度
ssize_t ksys_write(unsigned int fd, const char __user *buf, size_t count)
{
	// f 代表打开的文件和打开文件的flag
	struct fd f = fdget_pos(fd);
	ssize_t ret = -EBADF;
	// 如果打开的文件为空,则退出
	if (f.file) {
		// 获取文件读写的位置
		loff_t pos = file_pos_read(f.file);
		// 进入vfs_write的函数,接着处理文件写操作
		ret = vfs_write(f.file, buf, count, &pos);
		if (ret >= 0)
			file_pos_write(f.file, pos);
		fdput_pos(f);
	}

	return ret;
}

// vfs_write包装了ext4_file_write_iter 函数
ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
	ssize_t ret;
	// 判断文件是否写入,如果不能则返回错误码
	if (!(file->f_mode & FMODE_WRITE))
		return -EBADF;
	if (!(file->f_mode & FMODE_CAN_WRITE))
		return -EINVAL;
	if (unlikely(!access_ok(VERIFY_READ, buf, count)))
		return -EFAULT;
	// 检查文件系统可写区域以及锁检查
	ret = rw_verify_area(WRITE, file, pos, count);
	if (!ret) {
		if (count > MAX_RW_COUNT)
			count =  MAX_RW_COUNT;
		
		file_start_write(file);
		// 调用ext4_file_write_iter 开始写数据
		ret = __vfs_write(file, buf, count, pos);
		if (ret > 0) {
			fsnotify_modify(file);
			add_wchar(current, ret);
		}
		inc_syscw(current);
		file_end_write(file);
	}

	return ret;
}

// __vfs_write是包装函数
ssize_t __vfs_write(struct file *file, const char __user *p, size_t count,
		    loff_t *pos)
{
	if (file->f_op->write)
		return file->f_op->write(file, p, count, pos);
	else if (file->f_op->write_iter)
		return new_sync_write(file, p, count, pos);
	else
		return -EINVAL;
}

// 把用户需要写入的数据封装为struct iovec,然后把这个iovec和fd对应的struct file,传递给时间文件系统的函数,进行文件写入。
static ssize_t new_sync_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
{
	struct iovec iov = { .iov_base = (void __user *)buf, .iov_len = len };
	struct kiocb kiocb;
	struct iov_iter iter;
	ssize_t ret;

	init_sync_kiocb(&kiocb, filp);
	kiocb.ki_pos = *ppos;
	iov_iter_init(&iter, WRITE, &iov, 1, len);

	ret = call_write_iter(filp, &kiocb, &iter);
	BUG_ON(ret == -EIOCBQUEUED);
	if (ret > 0)
		*ppos = kiocb.ki_pos;
	return ret;
}

// call_write_iter是直接调用f_op->write_iter函数,这里对应的是 ext4_file_write_iter,到了这里虚拟文件系统层基本已经结束,进入实际文件系统的调用过程。
static inline ssize_t call_write_iter(struct file *file, struct kiocb *kio,
				      struct iov_iter *iter)
{
	return file->f_op->write_iter(kio, iter);
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-03-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 存储内核技术交流 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文件系统的作用
    • 文件系统的IO协议栈
      • 文件和目录
        • Posix 函数
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档