参考:https://blog.csdn.net/u012319493/article/details/85331567
访问基于磁盘的文件是一种复杂的活动,既涉及 VFS 抽象层、块设备的处理,也涉及磁盘高速缓存的使用。
将磁盘文件系统的普通文件和块设备文件都简单地统称为“文件”。
访问文件的模式有多种:
- 规范模式:规范模式下文件打开后,标志 O_SYNC 和 O_DIRECT 清 0,且它的内容由 read() 和 write() 存取。
read() 阻塞调用进程,直到数据被拷贝进用户态地址空间。
但 write() 在数据被拷贝到页高速缓存(延迟写)后马上结束。 - 同步模式:同步模式下文件打开后,标志 O_SYNC 置 1 或稍后由系统调用 fcntl() 对其置 1。
该标志只影响写操作(读操作总是会阻塞),它将阻塞系统调用,直到数据写入磁盘。 - 内存映射模式:内存映射模式下文件打开后,应用程序发出系统调用 mmap() 将文件映射到内存中。
因此,文件就成为 RAM 中的一个字节数组,应用程序就可直接访问数组元素,而不需要调用 read()、write() 或 lseek()。 - 直接 I/O 模式:直接 I/O 模式下文件打开后,标志 O_DIRECT 置 1。
任何读写操作都将数据在用户态地址空间与磁盘间直接传送而不通过页高速缓存。 - 异步模式:异步模式下,文件的访问可以有两种方法,即通过一组 POSIX API 或 Linux 特有的系统调用实现。
所谓异步模式就是数据传输请求并不阻塞调用进程,而是在后台执行,同时应用程序继续它的正常执行。
一、读写操作的系统调用
参考:https://blog.csdn.net/yiqiaoxihui/article/details/79958039?spm=1001.2014.3001.5501
参考:https://blog.csdn.net/tiantao2012/article/details/70770500
read() 和 write() 的服务例程最终会调用文件对象的 read 和 write 方法,这两个方法可能依赖文件系统。
对于磁盘文件系统,这些方法能确定被访问的数据所在物理块的位置,并激活块设备驱动程序开始数据传送。
读文件文件是基于页的,内核总是一次传送几个完整的数据页。
如果进程发出 read() 后,数据不在 RAM 中,内核就分配一个新页框,并使用文件的适当部分填充该页,把该页加入页高速缓存,最后把请求的字节拷贝到进程地址空间中。
对于大部分文件系统,从文件中读取一个数据页等同于在磁盘上查找所请求的数据存放在哪些块上。
该过程完成后,内核通过向通用块成提交适当的 I/O 操作来填充这些页。
大多数磁盘文件系统 read 方法由 generic_file_read() 通用函数实现。
对基于磁盘的文件,写操作比较复杂,因文件大小可改变,因此内核可能会分配磁盘上的一些物理块。
很多磁盘文件系统通过 generic_file_write() 实现 write 方法。
1.1 sys_write的流程
sys_write()是linux文件系统中核心函数之一,它完成的操作是将用户缓冲区的文件内容写入到文件在磁盘中的对应位置。
fs/read_write.c
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
struct file *file;
ssize_t ret = -EBADF;
int fput_needed;
file = fget_light(fd, &fput_needed);
if (file) {
loff_t pos = file_pos_read(file); //返回文件偏移量file->f_pos
ret = vfs_read(file, buf, count, &pos);
file_pos_write(file, pos); //重置文件偏移量file->f_pos = pos
fput_light(file, fput_needed);
}
return ret;
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); //异步I/O写操作
else
return -EINVAL;
}
EXPORT_SYMBOL(__vfs_write);
__vfs_write 这个函数中就调用具体文件系统的写函数,从可以可以看到优先调用file->f_op->write。
其中file->f_op是在调用文件系统注册的时候赋值的,这里以ext4 为例
const struct file_operations ext4_file_operations = {
.llseek = ext4_llseek,
.read_iter = generic_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,
.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,
};
调用流程,自己画了一个图:
可见最终的写操作还是在ext4 中实现,写操作由于不同的flag,走的flow有所不同,这里只是举例而已。
可见,虽然应用不需要关注哪个文件系统接口,只需要通过VFS这个通用接口,然后各个文件系统自己实现read/write接口,并把自己注册到文件系统的列表中。
参考:https://baijiahao.baidu.com/s?id=1621334078382950294&wfr=spider&for=pc
1.2 sys_read的流程
参考:https://blog.csdn.net/u013837209/article/details/54923508