Linux 异步IO:io_uring的设计初衷,与原理。
https://kernel.dk/io_uring.pdf 开发io_uring的原因与设计目标
背景介绍
linux中基于文件的IO
同步接口
- read,write
- 支持偏移量的 pread,pwrite
- 矢量版本 preadv,pwritev
- 允许修饰符标志的系统调用 preadv2,pwritev2
异步IO
- POSIX 提供了 aio_read 和 aio_write
- linux AIO的限制
- 最大的限制是它仅支持
O_DIRECT
(无缓冲)访问,由于O_DIRECT
(绕过缓冲和大小/对齐限制)的限制,导致原生AIO
接口在大多数情况下都不可行,对于正常(缓冲) IO 来说,接口依然以同步的方式运行 - 仅支持
O_DIRECT(
un-buffered)访问,因为O_DIRECT(
cache bypassing 和 对齐限制),导致AIO在大多数用例下不可行,对于
normal (buffered) IO 接口依然以同步方式执行; - 很多场景会导致IO提交时被阻塞,比如需要元数据来执行IO。对于存储设备,有优先的请求草,如果插槽都在使用中,提交将阻塞等待可用插槽。这些不确定性使得异步提交程序依然被阻塞;
- API :带来更多内存拷贝;“完成事件”缓冲区导致IO完成变慢,并且较难使用;IO总是需要两次系统调用才能完成提交和等待完成;
- 最大的限制是它仅支持
其他一些解决方案:应用层实现的异步能力:比如异步日志,就是在主要的工作线程中将需要写的日志内容写入一个专门的buffer队列中,再由另一个专门的日志写入线程将队列内容写入文件。也有一些库能够实现这样的功能,例如log4j2。
2.0 改善现状
最初开发人员将精力主要集中于改善aio上,并且取得了一些进展,但是依然遇到了很多困难;
选择改进aio的原因:
- 时间成本:提供新的接口以及进行检查和review 需要更多时间;
- 人力成本:开发人员需要以最少的工作量完成最多的工作。扩展现有接口更优优势;
现有的 AIO 接口主要有三个系统调用
- io_setup :设置 aio 上下文
- io_submit :提交 IO
- io_getevents: 获取或者等待 IO 事件完成
遇到的问题:
因为需要多多个系统个调用进行修改,最终在代码复杂性和可维护性上并不友好。另外现有的api变得更加复杂、难以理解和使用。
虽然放弃现有接口从头开始很难,但是显然我们需要一个全新的东西来提供高性能和可扩展性,而且方便使用,并且具备我们需要的特性。
3.0 新接口设计目标
按重要性从高到低的顺序,主要的设计目标如下:
-
易用性:接口应该易于理解和直观使用。
-
可扩展性:作者不希望该接口仅用于面向块的IO。后续会增加网络和非块存储的接口。
-
特性丰富:Linux AIO支持的场景有限.在新的设计上希望可以支持所有的异步IO场景。
-
性能:在小请求的or 没有携带数据的请求上,请求的开销要做到高效。(减少每次调度传递的参数大小;二是减少io_uring系统调用的次数。)
-
伸缩性:新的接口开放一个性能上可伸缩的能力给应用层。
上述一些目标之间似乎互斥,“高效且可扩展”的接口通常不易用&很难正确使用。“功能丰富”且“高效”也很难实现,但是这就是我们的目标。
而io_uring的具体实现中涉及到几个重要的概念:ring buffer,轮询io,排序,超时机制,针对易用性的封装。在后面章节中会具体提到