什么是异步IO

《UNIX网络编程卷1》中的IO多路复章节总结了几种典型IO模型,包括:

阻塞IO

非阻塞IO

IO复用

信号驱动式IO

异步IO

这些IO模型在本质上都是围绕着同步、异步、阻塞、非阻塞这几个特点在做一些不同的选择。IO的过程是应用程序从某个设备读取数据,或者往设备写入数据。操作系统把这些设备抽象为描述符fd,应用程序则在这些fd上面进行读写操作。由于fd的底层是设备,这里就会有个问题:设备还没有准备好数据的读写,比如网卡还没有收到数据,此时如果应用程序去读相应的fd,肯定是没有数据的。那么当遇到这种情况时,应用程序应该如何反应呢,有几个选择:

阻塞:应用程序一直等待数据ready,然后返回

非阻塞:应用程序立即返回,去跑跑其他逻辑,然后定期来看下数据是否ready

此外,即使设备中已经有数据,操作系统还需将数据从内核拷贝到用户的缓存,这也需要一些时间,具体长短和用户设定的读取数据量大小有关。换言之,一次IO操作可能是比较耗时的,那么是否有必要一直等待IO完成,或者,是否有必要定期去检查数据是否ready呢。显然不是必须的,因此这里又有了同步、异步的概念:

同步:同步和阻塞的意思是一样的, 每次发起IO请求后,等待完成才返回

异步:发起IO请求后立即返回,等到内核将IO完成后,才以某种形式通知应用

异步IO的核心在于:应用程序不需要花费时间在IO上,只需要提交一个IO操作,当内核执行这个IO操作时,应用可以去运行其他逻辑,也不需要定期去查看IO是否完成,当内核完成这个IO操作后会以某种方式通知应用。

内核通知应用的方式其实并不多,上面说的信号驱动IO,就是内核将数据准备好之后,用信号的方式通知应用。但是信号这种方式会打乱应用程序的执行流,让逻辑变得混乱,在实际中使用的很少。另外一种通知的方式是让应用主动来询问,例如现有的io_getevents系统调用,它可以让应用知道到现在是否已经完成了IO操作。

当使用特定的参数时,io_getevents会阻塞直到指定的IO操作全部完成。这看起来似乎又变成了阻塞IO的样子,但实际上有些区别,一个重要的不同在于:应用可以同时提交多个IO请求,然后在一个io_getevents中等待他们全部完成。这个和IO多路复用的机制很相似,从应用的角度看,就好像执行一个批量的操作,这显然是能够提升效率的。

总结一下,异步IO的基本逻辑是:应用提交一些IO操作到内核,然后不需要去关注这些IO,等到适当的时机,或者内核发信号给应用,或者应用主动询问内核,来获取到IO操作的执行是否完成。

为什么需要异步IO

在理想的情况下,运行中的程序会尽可能发挥硬件的能力,包括CPU的计算能力以及存储设备的IO能力,来获得最好的性能。在近几年,存储设备的IO性能提升很快,如果还是使用之前的阻塞IO模式,设备的能力会得不到发挥,这和我们程序运行的初衷是相悖的。也就是说,在硬件设备相同的条件下,我们需要尽力改善代码,来获取更好的性能。事实上,很多东西都在做这样的事情,比如协程、事件驱动这些机制,本质上都是在尽可能的提升程序的执行效率。

那么异步IO是怎么提高性能的呢?上面说到,异步IO的本质就是应用将一批IO提交给内核,然后就不用管了,可以去做其他事情。这个过程对性能的提升体现在两个地方:

应用不再阻塞在IO这里,由内核来操作IO,应用可以执行其他逻辑,此时应用的运行和IO执行变成了并行的关系

可以批量的进行IO操作,让设备的能力得到最大发挥

这里有也有值得商榷的地方:1,是不是真正的在并行执行,如果cpu资源有限,应用线程和内核线程不能同时在各自的cpu核心上运行,那么其实也不是并行(从设备拷贝数据到内核可能不需要cpu参与,只要硬件就够了,但是从内核往用户控件拷贝是肯定需要内核线程来操作的)。2,批量提交IO给内核,是不是这个量越大越好。这些都需要看具体的情况。

有哪些异步IO的实现

现有的异步IO实现主要包括两个:

以aio_为前缀的一系列函数,包括 aio_read,aio_write, aio_suspend等,这个异步IO的实现是在用户态使用线程池实现的,性能不怎么样,它只是暴露出异步IO风格的接口。

libaio包提供的系列函数,libaio是包装在io_setp,io_submit等系统调用上的lib,这个一套正儿八经在内核实现的异步IO机制。

第一个这里就不说了,第二个libaio也存在很多问题,导致没有没广泛的应用,主要的缺陷如下:

只能支持O_DIRECT模式,也就是没有缓冲的读写

只能支持ext2, ext3, jfs, xfs文件系统

不支持fsync

不支持socket

不支持管道pipes

api设计的不够好:一次IO需要至少两次系统调用;submit + completion一共需要拷贝104字节数据,本来应该是0拷贝的(这个量不大,可能也不是一个严重的问题);此外,api很难使用正确

由于这些原因,libaio只在一些底层软件如数据库中有被使用,大多数普通的应用都没有使用libaio。

io_uring

在linux5.1以后,内核引入了一种全新的异步IO机制,也就是io_uring。io_uring基本上克服了上述aio的各种缺陷,它的主要特性如下:

支持O_DIRECT以及非O_DIRECT模式的文件读写,并能够支持在各种类型的fd上操作,包括文件、网络

高性能,相比于旧的aio,省去了读书数据的拷贝,减少必须的系统调用次数

丰富的特性,包括fixed buffer,polled IO等

简单易用的api接口

ring buffer

io_uring的最大特色在于对性能的提升,它通过让用户态的应用和内核共享数据结构来实现这一点,这个数据结构就是ring buffer,这也是io_uring名字的由来。

上面讲过,异步IO的基本逻辑是应用提交IO请求到内核,内核执行这些IO请求,然后应用再以某种方式来获取到IO执行完成的情况。很明显这个过程需要应用和内核交换信息,应用需要告诉内核有一个新的IO请求到来,内核需要告诉应用某个IO已经完成。之前的异步IO做法是让应用通过系统调用来获取这些信息,但是系统调用是一个相对较重的操作,它需要中断当前的进程,保持上下文,陷入内核,执行相应逻辑后再返回。io_uring的做法是直接让应用和内核共享两个ring buffer,一个是submission ring,一个completion ring,这两个ring以queue的形式工作,应用和内核通过访问这两个ring来获取需要的信息。对于SQ(submission queue)来说,应用是生产者,内核是消费者;对于CQ(completion queue)则是相反的。

在多线程中共享数据结构时,必须要做好同步的工作,因为这里有竞争条件。类似的,当应用和内核共享数据结构时,也需要做同步。一般的做法是使用互斥锁,但是由于这里应用是和内核在共享数据,如果使用锁,则必须要使用某种形式的系统调用。一方面,互斥锁对性能是有损耗的,另外,系统调用也是要避免的。io_uring的做法是使用memory ordering来避免出现数据不一致(在多核心的cpu架构中,每个核都有自己的多级缓存,线程只会在一个核心上运行,当某个线程连续更改了内存中某个变量的值,运行在其他核心上的线程的缓存需要做相应更新,此时其他线程可能会看到这些变量的变更顺序和发起更改的顺序不一致,memory ordering主要是用于防止这种现象,也就是它可以保证所有线程看到相同的变更顺序)。在使用ring buffer当做queue的这种场景下,生产和消费都是通过修改相应的head、tail值来进行的,使用memory ordering能够保证两边看到的数据是一致的。 相比于使用互斥锁,memory ordering的效率更高。

高级特性

FIXED FILES:在每次提交一个IO请求后,内核会获取这个IO请求中fd的一个引用,在使用完成后释放这个引用。在高IOPS、同时操作的文件基本不变的情况下,这个过程会有显著的性能损耗。io_uring支持使用一个或一组固定的fd,这样避免了内核频繁的创建和销毁fd的应用

FIXED BUFFER:在使用O_DIRECT模式的IO时,内核会将用户给的缓冲区map到内核的内存地址,然后在上面做读写,完成后再unmap这些地址。这个是比较昂贵的操作,为了避免频繁额map和unmap,io_uring提供了FIXED BUFFER的能力,即可以让应用重复的使用同一块已经映射好的缓冲区。

POLLED IO:这种模式下,应用使用轮询的方式来查询IO完成情况,应用会不停的询问硬件驱动,相应的IO是否已经完成,从而避免了由硬件设备中断来告知IO已经完成。对于IOPS高的应用来说,频繁的硬件中断会带来很多效率上的损耗。polled io这种模式适合用在IOPS高,硬件性能高的场景,能够有效提升应用的性能。

KERNAL SIDE POLLING:内核侧的轮询模式,使用这种方式,在应用提交一个IO操作到submission queue后,不需要通过系统调用来告诉内核有一个新的IO请求到来。内核中会有一个专职的线程关注submission queue,一旦有新的entry,会立即处理它。

io_uring和io多路复用在用法上比较类似,都是先提交一些数据,然后等待相应的事件发生,由于io_uring能够支持各种设备的IO,包括文件、网络,现在似乎可以使用io_uring将网络、文件读写都统一起来。但实际上,io_uring和epoll还是有些差别的,io_uring最多能够提交4096个IO请求到submission queue中,epoll则可以同时管理数以百万的连接,仅这一点就使得io_uring不可能代替epoll。

linux异步io底层原理,异步IO简析相关推荐

  1. Linux I/O底层原理揭秘

    本文转载腾讯游戏工程师:Linux I/O底层原理全面揭秘 这里向作者致敬,写的非常棒,从技术背景到原理,通俗易懂. 摘要 从虚拟内存.I/O 缓冲区,用户态&内核态以及 I/O 模式等等知识 ...

  2. ntp如何确认与服务器偏差_NTP时钟同步原理及误差简析

    原标题:NTP时钟同步原理及误差简析 在我们某试验系统方案设计中,由于数据同步性的要求,需要将我们WP4000变频功率分析仪的时钟与客户的NI系统的时钟进行同步,对于WP4000变频功率测试系统而言, ...

  3. NTP时钟服务器原理及误差简析(京准)

    NTP时钟服务器原理及误差简析 1.引言 作为数字通信网的基础支撑技术,时钟同步技术的发展演进始终受到通信网技术发展的驱动.在网络方面,通信网从模拟发展到数字,从TDM网络为主发展到以分组网络为主:在 ...

  4. 后端开发程序员须彻底搞懂的 IO 底层原理

    一.混乱的 IO 概念 IO是Input和Output的缩写,即输入和输出.广义上的围绕计算机的输入输出有很多:鼠标.键盘.扫描仪等等.而我们今天要探讨的是在计算机里面,主要是作用在内存.网卡.硬盘等 ...

  5. NTP时钟同步原理及误差简析

    在我们某试验系统方案设计中,由于数据同步性的要求,需要将我们WP4000变频功率分析仪的时钟与客户的NI系统的时钟进行同步,对于WP4000变频功率测试系统而言,多台分析仪之间可通过同步光纤接口达到严 ...

  6. ipc原理linux,Docker 的底层原理,了解它只需要 5分钟!

    作者:小姐姐养的狗 来源:高效运维 一位同学曾给我打比方:宿主机就好比一间大房子,docker 把它变成了N个小隔断.在这些小隔断之间,有独立的卫生间.小床.电视- 麻雀虽小,五脏俱全,这个比喻非常的 ...

  7. 同步、异步、阻塞、非阻塞 简析

    阻塞/非阻塞 都是 同步 的状态,异步 不存在 阻塞/非阻塞 的情况. 同步:执行一个操作之后,等待结果,然后才继续执行后续的操作. 异步:执行一个操作后,可以去执行其他的操作,然后等待通知再回来执行 ...

  8. mmap内存 android,Android中mmap原理及应用简析

    mmap是Linux中常用的系统调用API,用途广泛,Android中也有不少地方用到,比如匿名共享内存,Binder机制等.本文简单记录下Android中mmap调用流程及原理.mmap函数原型如下 ...

  9. Linux进程描述符task_struct结构体简析

    进程是处于执行期的程序以及它所管理的资源(如打开的文件.挂起的信号.进程状态.地址空间等等)的总称 Linux内核通过一个被称为进程描述符的task_struct结构体来管理进程,这个结构体包含了一个 ...

  10. Linux内核RCU(Read Copy Update)锁简析

    在非常早曾经,大概是2009年的时候.写过一篇关于Linux RCU锁的文章<RCU锁在linux内核的演变>,如今我承认.那个时候我尽管懂了RCU锁,可是我没有能力用一种非常easy的描 ...

最新文章

  1. (JAVA学习笔记) Scanner类中next方法和nextline方法的区别
  2. JZOJ 5372. 【NOIP2017提高A组模拟9.17】猫
  3. 【数据库系统概论】考研重点章节分析【0】
  4. LTE TDD的特殊子帧
  5. 循环队列及C语言实现三
  6. Android Studio Gradle两种更新方式
  7. C#学习笔记(十三):I/O操作
  8. C#基础系列——Attribute特性使用
  9. 当前位置 计算机英语,计算机英语_文章
  10. Maven如何将别人的项目导入到自己的eclipse并使用
  11. python爬虫re_python网络爬虫之三re正则表达式模块
  12. Silverlight开发的15个最佳实践
  13. day_4 Selenium请求库
  14. 日期格式 java_Java日期格式转换
  15. 5G垂直领域:华为智慧园区
  16. RuntimeError: CUDA error: invalid device ordinal
  17. 2 创造你的物理世界(1)
  18. Arturia Sound Explorers Collection Belledonne现已上市
  19. aabResGuard使用
  20. 机试NOI:基本算法

热门文章

  1. 关于Lattic Diamond软件安装不成功问题(license问题)
  2. apache-hive-3.1.0-bin.tar.gz 下载
  3. 计算机毕设参考文献、Java参考文献、MySQL参考文献、jsp参考文献、Python参考文献、微信小程序参考文献、外文参考文献
  4. Java代码实现“爱心”表白
  5. 主力吸筹猛攻指标源码_通达信大于9000手大单指标公式,主力吸筹猛攻指标源码...
  6. CRMEB首届UI设计大赛报名啦!
  7. 网站建设的一般原则及网站推广技巧
  8. 【笔记】《Java核心技术卷1(第11版)》-第1章-Java程序设计概述
  9. uniapp解决h5打包空白的问题
  10. 图像增强总结-Retinex算法