linux内核源码分析之虚拟文件系统VFS(二)

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)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

为了维护世界和平_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值