利用io_uring实现高性能IO
本文为该英文文档的翻译,此外还有一些个人理解。以下为正文:
本文为对最新的Linux IO接口 io_uring
的简介,并将其与现有IO接口进行比较。具体来说,我们将探讨 io_uring
被提出的原因、内部实现方式和用户态接口。本文并不会深入具体命令的细节,因为这些内容可以通过查阅man pages得到(但是不可避免地,会有一些重叠)。相反,本文的目的是介绍 io_uring
和它的工作方式,以使读者深入理解它的各个部分是如何组合到一起的。
1.0 介绍
Linux中有许多进行进行文件IO访问的接口。最古老、最基础的为 read()
和 write()
系统调用。后续还有 pread
和 pwrite()
、 preadv()
和 pwritev()
、 preadv2()
和 pwritev2()
等改进后的接口。它们均为同步接口,即这些系统调用在读或者写完成后才返回。在高并发场景下,同步接口的IOPS受限。 类似aio_read()
和 aio_write()
的异步接口被提出。然而,这些接口的实现不够优雅,性能也不太好。
具体来说,Linux原生的异步接口 aio
的缺点为:
- 仅支持
O_DIRECT
模式(即绕过page cache)。 - IO的提交依然可能被阻塞。例如,若IO提交需要等待元数据IO执行完成。又如,当存储设备的请求提交队列不可用时,IO的提交也会被阻塞。
- 需要额外的数据拷贝。每次IO请求提交需要拷贝64+8个字节,每次IO请求完成需要拷贝32个字节。
由于时间关系,中间部分暂时略过,之后直接进行到 io_uring
的细节
4.0 深入io_uring
io_uring
最开始的设计目标就是高效。我们不希望在IO提交和完成事务时有任何内存拷贝或是间接的内存访问。注意在 aio
的设计中,由于内核态与用户态的相互拷贝,性能和可扩展性都受到了影响。
为了达到这个目标,内核和用户必须优雅的共享由IO自己定义的数据结构。你可以想到,用户和内核共享数据结构必须需要某种同步机制。用户必须通过系统调用才能和内核共享锁,而系统调用会降低和内核通信的速率,影响性能。所以,我们选择使用生产商-消费者结构的环形缓冲区,以此消除用户和内核的共享锁,转而使用内存的barrier等机制。
在异步IO接口中,最基本的事情有二:提交请求和请求完成。当提交请求时,应用是生产者,内核是消费者;完成请求时则反之。所以,我们需要一对环形缓冲区,分别为提交队列(SQ)和完成队列(CQ)。