文件系统的作用 linux 内核中进程管理、内存管理、网络协议栈、文件系统
是内核的四大核心模块。其中文件系统提供最基础的操作文件的能力。简单概要的说,内核中有vfs
和实际文件系统(比如ext4)
,vfs
是虚拟文件系统,是内核提供一种工厂设计模式的抽象层,对外提供标准的posix语义层;实际文件系统就是实现特定的文件功能的磁盘文件系统。具体如下图所描述 文件系统的IO协议栈 应用程序如果以dio
方式读写文件请求,首先经过内核的vfs,然后到实际的文件系统的对应的处理函数,接着请求进入那么设备映射,最后请求传递到了块设备的IO层,调度算法和IO优化也会在这里进行处理,块设备IO层处理完毕后就直接到了磁盘的驱动层,这一层就是调用IO命令来对磁盘进行读写。
文件和目录 linux在设计之处就流行一句话linux
中一切都是文件,在文件系统设计中也非常实用。内核看待目录也是当作文件来看待。文件的inode
中存储的是文件对应的数据块索引和数据,而目录的inode
则是存储该目录下的文件的inode和文件名称
,虽然都是inode
,但是存储的数据绝不相同。文件系统中的所有的目录名称和文件名称都是存在于vfs层(仅仅是内存结构展现),这个结构是以struct dentry
表示,文件或者目录是以struct inode
表示。
每个打开的文件在内核进程中是以文件描述符存在的,每个进程维护一个数组,这个数组的下标就是给应用返回的文件描述符,数组中的每个元素对应的是struct file
,struct 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_op
和const 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);
}