Linux io_uring

文章目录Linux io_uring1. Linux IO 系统调用简介2. io_uring 简介2.1 io_uring 数据结构参考:Linux io_uring1. Linux IO 系统调用简介在Linux系统中,可以进行IO操作的系统调用有read和write,并在此基础之上提供了功能更强的pread与pwrite,可以从指定的偏移位置开始读写。此外还有preadv与pwritev支持向量化的读写操作,以及进一步的preadv2与pwritev2允许设置修改标志。这些系统调用虽然在一般IO
摘要由CSDN通过智能技术生成

Linux io_uring

1. Linux IO 系统调用简介

在Linux系统中,可以进行IO操作的系统调用有readwrite,并在此基础之上提供了功能更强的preadpwrite,可以从指定的偏移位置开始读写。此外还有preadvpwritev支持向量化的读写操作,以及进一步的preadv2pwritev2允许设置修改标志。这些系统调用虽然在一般IO功能上进行了增强,但是它们都是同步的。即系统调用在数据就绪的时候返回。在某些情况下,这种方式使得程序无法达到最佳的性能。虽然POSIX中有aio_readaio_write异步IO系统调用接口,但是性能一般[1]

Linux 的异步IO接口主要有一下几点局限:

  • 异步IO接口仅支持O_DIRECT的方式(非buffer),而如果要使用带缓存的方式,则接口的工作方式与同步的相同。这使得部分场景下该异步IO接口无法发挥作用;
  • 对于一些存储设备,仅有固定个数的请求槽(request slot)。如果某个时刻这些request slot都正在使用,那么IO的提交过程需要阻塞等待,而该阻塞具有不确定性;
  • IO操作的过程包括提交请求与等待完成两个步骤。该接口的每个IO的提交需要复制 64 + 8 64+8 64+8个字节,并且在完成时需要复制 32 32 32字节的数据。这样,对于完整的单个IO操作总共需要复制 104 104 104个字节,这种额外的复制操作会使得IO操作变得缓慢

2. io_uring 简介

在前面提到Linux的aio接口会在IO过程中涉及到比较多的复制操作。为了提高IO性能,需要避免复制操作,而这需要内核与应用共享IO过程中的数据结构,以及两者的同步管理。如果采用应用与内核共享锁的方式,则应用部分需要系统调用,而这额外的系统调用会影响到IO的性能。因此,可以采用单生产者单消费者的环形缓冲区(ring buffer)的方式。采用这种方式,整个的异步IO的操作包含两个部分,分别是IO请求的提交以及对应的处理结束事件。对于IO操作请求提交步骤,应用程序时生产者而内核是消费者,而对于IO操作的结束事件则与之相反。因此,需要一对环形缓冲区,分别是提交队列(submission queue, SQ)与 完成队列(completion queue,CQ)。

2.1 io_uring 数据结构

io_uring 中,完成队列中的数据结构如下所示:

struct io_uring_cqe {
   
	__u64 user_data;
	__s32 res;
	__u32 flags;
};

该数据结构中包含user_data,包含应用对IO请求的标识信息,一种常用的做法是采用指针的方式指向原始的IO请求,内核不会对该字段进行修改。res 保存了IO请求的结果。flags保存与本次IO操作相关的元数据(metda data),目前,该字段没有使用。

相比于上面完成队列中的数据结构,IO请求的数据结构更为复杂。不仅包含了必要的字段,同时还考虑到对以后请求类型的可扩展性。

struct io_uring_sqe {
   
	__u8 opcode;
	__u8 flags;
	__u16 ioprio;
	__s32 fd;
	__u64 off;
	__u64 addr;
	__u32 len;
	union {
   
		__kernel_rwf_t rw_flags;
		__u32 fsync_flags;
		__u16 poll_events;
		__u32 sync_range_flags;
		__u32 msg_flags;
	};
	__u64 user_data;
	union {
   
		__u16 buf_index;
		__u64 __pad2[3];
	};
};

刻画IO完成事件的数据结构中,opcode字段保存IO请求的操作类型,比如IORING_OP_READV 表示向量化的读取操作。flags 字段保存修改标志。ioprio 保存该IO的优先级。fd 是IO操作目标的文件描述符。off字段保存本次IO操作开始的位置偏移量。addr 保存了opcode指定的操作的起始地址。比如,当IO操作是向量化的时候,addr是一个指向iovec array的指针;而对于非向量化的IO操作,addr必须包含地址。对于非向量化的操作,len字段包含IO的字节个数;对于向量化的操作,则保存vectors的个数。接下来是由标志位组成的union。结构体最后的union结构用于padding到64字节,在内存中对齐。

2.2 通信管道

尽管提交队列与完成队列是对称的,但是它们索引的方法并不相同。

完成队列(以下简称cqe) 在实现中是一个数组,内核与用户的应用程序都可以对其进行修改。由于该数组是内核创建的,实际上只有内核对cqe进行实际上的修改操作。当有一个新完成的IO事件,则会被内核提交到cqe,更新队列的尾部。当用户应用程序从队列中取出后,更新队列的头部。当队列的头部与尾部不同的时候,用户程序可知当前有一个或者多个事件可以取出处理。队列的计数器使用32位的整数,并且环形缓冲区的长度为2的指数。

为了找到事件的索引,需要使用到mask,操作过程如下所示:

unsigned head;
head = cqring→head;
read_barrier();
if (head != cqring→tail) {
   
	struct io_uring_cqe *cqe
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值