Linux 文件系统解析(四)IO模式

文件系统是IO流程的起点,为了满足应用程序对IO操作的各种需求,在文件系统层设计了多种IO模式,上一篇介绍的bufferIO就是最常见的一种,本篇来梳理一下其他IO模式,它们的作用,流程以及是如何实现的。

iov_iter & kiocb

介绍各IO模式的实现前,先来看两个数据结构 iov_iter,kiocb

iov_iter

struct kvec {
	void *iov_base; /* and that should *never* hold a userland pointer */
	size_t iov_len;
};

struct iov_iter {
	/*
	 * Bit 0 is the read/write bit, set if we're writing.
	 * Bit 1 is the BVEC_FLAG_NO_REF bit, set if type is a bvec and
	 * the caller isn't expecting to drop a page reference when done.
	 */
	unsigned int type;    //标识读or写,以及其他属性 
	size_t iov_offset;    //第一个iovec中,数据起始偏移
	size_t count;    //数据大小
	union {
		const struct iovec *iov;    //结构与kvec一致,描述用户态的一段空间
		const struct kvec *kvec;    //描述内核态的一段空间
		const struct bio_vec *bvec;    //描述一个内存页中的一段空间
		struct pipe_inode_info *pipe;
	};
	union {
		unsigned long nr_segs;    //iovec数量
		struct {
			int idx;
			int start_idx;
		};
	};
}

“迭代器” 是内核中常见的设计,通常用来描述一个对象的处理进度。 iov_iter最初主要用于描述一次IO流程中用户空间的处理进度,以*iov保存用户空间的内存地址,iov_offset和count记录当前处理进度,这两个参数会随IO的进行会不断变化。随后该机制拓展到内核其他功能中,以union形式定义了更多属性。

参考:https://lwn.net/Articles/625077/

kiocb

struct kiocb {
	struct file		*ki_filp;    //open文件创建的file结构

	/* The 'ki_filp' pointer is shared in a union for aio */
	randomized_struct_fields_start

	loff_t			ki_pos;    //数据偏移
	void (*ki_complete)(struct kiocb *iocb, long ret, long ret2);    //IO完成回调
	void			*private;
	int			ki_flags;    //IO属性
	u16			ki_hint;
	u16			ki_ioprio; /* See linux/ioprio.h */
	unsigned int		ki_cookie; /* for ->iopoll */

	randomized_struct_fields_end
}

kiocb 中主要保存了一个file结构,以及记录读写偏移,相当于描述了一次IO中文件侧的处理进度。

iov_iter 和 kiocb 实际上分别描述了一次IO的两端,iov_iter描述内存侧,kiocb描述文件侧,文件系统提供两个接口基于这两个数据结构封装读写操作。

static inline ssize_t call_read_iter(struct file *file, struct kiocb *kio,
				     struct iov_iter *iter)
{
	return file->f_op->read_iter(kio, iter);
}

将kiocb描述的文件数据,读到iov_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);
}

将iov_iter描述的内存数据,写到kiocb描述的文件中。 

文件系统中所有IO模式的读写逻辑,最终都是基于这两个接口实现的。

DirectIO

上一篇提到的bufferIO,使用pagecache来缓存数据,但有时由于种种原因,应用不希望使用cache。directIO是用来解决这一需求的,它的实现是通过在open()调用时传入O_DIRECT参数,使得file->f_flags标记为O_DIRECT,在进行读写时根据该标记判断是否进行要绕过pagecache,当然底层文件系统驱动也需要实现directIO相关接口。还是以ext4为例看下directIO的流程。

directIO 也使用read(),write() 等系统调用,与bufferIO调用栈前面部分一致:

vfs_read() ->__vfs_read() ->call_read_iter() -> ext4_file_read_iter() -> generic_file_read_iter()

vfs_write() ->__vfs_write() -> call_write_iter() -> ext4_file_write_iter() -> __generic_file_write_iter()

主要区别在generic_file_read_iter() 和 __generic_file_write_iter()之后,来看下这两个函数的流程:

generic_file_read_iter 流程图

generic_file_write_iter 流程图

 这两个函数在directIO模式下的流程很类似,可总结如下:

  • 将pagecache中的“脏”页回写。
  • 调用a_ops->directIO,执行磁盘到iov_iter中的用户内存地址的数据读写。
  • 若上一步执行后,仍未完成所有数据的读写,以buffer IO模式读写剩下数据。

ext4 的a_ops->directIO 指向ext4_direct_IO() , 以read为例,调用栈为:

ext4_direct_IO()->ext4_direct_IO_read()->__blockdev_direct_IO()->do_blockdev_direct_IO()

do_blockdev_direct_IO() 中实现了directIO的核心逻辑,大致流程如下:

  • 首先做一些基础信息校验,如数据是否超过inode大小,offset是否按blocksize对齐等
  • 根据kiocb->ki_complete回调是否为NULL,设置本次directIO是同步or异步
  • 调用do_direct_IO()
  • 10
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值