原文:Efficient IO with io_uring

本文为该英文文档的翻译,此外还有一些个人理解。以下为正文:

本文为对最新的Linux IO接口 io_uring 的简介,并将其与现有IO接口进行比较。具体来说,我们将探讨 io_uring 被提出的原因、内部实现方式和用户态接口。本文并不会深入具体命令的细节,因为这些内容可以通过查阅man pages得到(但是不可避免地,会有一些重叠)。相反,本文的目的是介绍 io_uring 和它的工作方式,以使读者深入理解它的各个部分是如何组合到一起的。

1.0 介绍

Linux中有许多进行进行文件IO访问的接口。最古老、最基础的为 read()write() 系统调用。后续还有 preadpwrite()preadv()pwritev()preadv2()pwritev2()等改进后的接口。它们均为同步接口,即这些系统调用在读或者写完成后才返回。在高并发场景下,同步接口的IOPS受限。 类似aio_read()aio_write() 的异步接口被提出。然而,这些接口的实现不够优雅,性能也不太好。

具体来说,Linux原生的异步接口 aio 的缺点为:

  1. 仅支持 O_DIRECT 模式(即绕过page cache)。
  2. IO的提交依然可能被阻塞。例如,若IO提交需要等待元数据IO执行完成。又如,当存储设备的请求提交队列不可用时,IO的提交也会被阻塞。
  3. 需要额外的数据拷贝。每次IO请求提交需要拷贝64+8个字节,每次IO请求完成需要拷贝32个字节。

由于时间关系,中间部分暂时略过,之后直接进行到 io_uring 的细节

4.0 深入io_uring

io_uring 最开始的设计目标就是高效。我们不希望在IO提交和完成事务时有任何内存拷贝或是间接的内存访问。注意在 aio 的设计中,由于内核态与用户态的相互拷贝,性能和可扩展性都受到了影响。

为了达到这个目标,内核和用户必须优雅的共享由IO自己定义的数据结构。你可以想到,用户和内核共享数据结构必须需要某种同步机制。用户必须通过系统调用才能和内核共享锁,而系统调用会降低和内核通信的速率,影响性能。所以,我们选择使用生产商-消费者结构的环形缓冲区,以此消除用户和内核的共享锁,转而使用内存的barrier等机制。

在异步IO接口中,最基本的事情有二:提交请求和请求完成。当提交请求时,应用是生产者,内核是消费者;完成请求时则反之。所以,我们需要一对环形缓冲区,分别为提交队列(SQ)和完成队列(CQ)。

4.1 数据结构