read系统调用流程
read()->ksys_read()
ssize_t ksys_read(unsigned int fd, char __user *buf, size_t count)
{
struct fd f = fdget_pos(fd);//根据fd号从task的文件描述符中获取file指针
ssize_t ret = -EBADF;
if (f.file) {
loff_t pos, *ppos = file_ppos(f.file);//找到文件当前读写位置
if (ppos) {
pos = *ppos;
ppos = &pos;
}
ret = vfs_read(f.file, buf, count, ppos);//执行file->f_op->read/->read_iter
if (ret >= 0 && ppos)
f.file->f_pos = pos;//更新读取位置
fdput_pos(f);//释放file
}
return ret;
}
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
return ksys_read(fd, buf, count);
}
文件打开/读写时,将引用计数加1,关闭时,将引用计数减1。
1、根据fd号从task的文件描述符表中获得file指针
2、找到当前的读写位置;
3、调用vfs_read
4,fdget_pos释放file
vfs_read()->__vfs_read()
ssize_t __vfs_read(struct file *file, char __user *buf, size_t count,
loff_t *pos)
{
if (file->f_op->read)
return file->f_op->read(file, buf, count, pos);
else if (file->f_op->read_iter)
return new_sync_read(file, buf, count, pos);
else
return -EINVAL;
}
有两个分支,如果file->f_op->read为空则调用file->f_op->read_iter
这两个回调函数是在何时注册的?
重要!!!结构体之间的关联
- vfs_read会调用file->f_op->read_iter函数。在ext2文件系统中指向ext2_file_read_iter
- file->f_op是在文件打开时,在do_dentry_open中赋值为inode->i__fop
- inode->i_fop是在初始化inode时,在ext2_iget中赋值为&ext2_file_operations。
const struct file_operations ext2_file_operations = {
.llseek = generic_file_llseek,
.read_iter = ext2_file_read_iter,
.write_iter = ext2_file_write_iter,
.unlocked_ioctl = ext2_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = ext2_compat_ioctl,
#endif
.mmap = ext2_file_mmap,
.open = dquot_file_open,
.release = ext2_release_file,
.fsync = ext2_fsync,
.get_unmapped_area = thp_get_unmapped_area,
.splice_read = generic_file_splice_read,
.splice_write = iter_file_splice_write,
};
大部分文件系统的读取过程,都是将read_iter设置为generic_file_read_iter来实现。
即ext2_file_read_iter->generic_file_read_iter->generic_file_buffered_read
- 从页缓存中获取数据,如果也缓存中没有则从块这边中获取。
- 块设备中取数据是异步的,但在没有获取到数据前,task会进入睡眠。
- 数据读取完毕,唤醒task,将数据拷贝到用户态buffer
static ssize_t ext2_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
return generic_file_read_iter(iocb, to);
}
ssize_t
generic_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
{
...
retval = generic_file_buffered_read(iocb, iter, retval);
out:
return retval;
}
void ext2_set_file_ops(struct inode *inode)
{
inode->i_op = &ext2_file_inode_operations;
inode->i_fop = &ext2_file_operations;
if (IS_DAX(inode))
inode->i_mapping->a_ops = &ext2_dax_aops;
else if (test_opt(inode->i_sb, NOBH))
inode->i_mapping->a_ops = &ext2_nobh_aops;
else
inode->i_mapping->a_ops = &ext2_aops;
}
generic_file_buffered_read函数中有文件预读
文件预读
程序打开文件读入第一页,大概率会继续读取后面的页。而且文件系统也为相邻的数据尽量分配相邻的块。通过预读,可以降低延迟。但对于随机读,预读可能没有什么帮助,反而可能引起性能下降。
如何调整预读的数据量
/sys/block/<devname>/queue/read_ahead_kb
__do_page_cache_readahead-> read_pages 将page读转为block读
BIO -> bi_idx->biovec ->page(过程相当复杂)
IO完成,通知CPU,处理中断BLOCK_SOFTIRQ。最终解锁前面锁定的页面,阻塞的task继续执行。
参考链接
VFS(一) 虚拟文件系统概述 - 知乎 (zhihu.com)
VFS(二) 读文件的过程中发生了什么 - 知乎 (zhihu.com)