发布时间:2023-08-04 10:30
这一块看的冰河老师的文章,做个复习。
首先来说下什么是IO:涉及计算机核心与其他设备间数据迁移的过程,就是IO。 例如磁盘IO:
而操作系统发起一次IO操作一般会包含两个阶段:
其中,IO执行阶段又分为两个阶段:
IO模型主要有五种类型:
应用程序进程发起IO调用,但是内核数据还没准备好。因此应用进程一直阻塞等待,直到内核数据准备好。
缺点:如果内核数据一直没准备好,那用户进程将一直阻塞,浪费性能。
鉴于阻塞式IO的缺点,在其基础上,倘若内核数据还没准备好,非阻塞式IO会先将错误信息返回给用户进程,让其无需等待,再通过轮询的方式来请求。优点:相对于阻塞式IO,用户可以无需等待,不会因为内核数据没准备好而进入阻塞状态。
缺点:频繁的轮询,导致频繁的进行系统调用,消耗大量的CPU资源。
既然频繁的轮询导致CPU消耗很大。那就让内核数据准备好的时候,主动通知应用程序进行系统调用即可。也就是IO多路复用。
概念:文件描述符(File Descriptor
)
文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。
IO复用模型核心思路:系统给我们提供一类函数,它们可以同时监控多个fd
的操作,任何一个返回内核数据就绪,应用进程再发起系统调用。 注意,这里依旧是需要应用进程去发起系统调用的。
IO多路复用的方式有三种:select、poll、epoll
。
应用进程可以通过select
函数,来同时监控多个fd
,在select
函数监控fd
的过程中,只要任何一个数据状态准备就绪了。select
就会返回可读状态,这时候应用进程就会发起请求读取内核数据。 如图:
缺点如下:
select
函数返回后,是通过遍历fd
集合,找到就绪的描述符fd
。(即遍历所有的流)鉴于select
方式的缺点,就提出了poll
。与前者相比,poll
解决了连接数量限制的问题。但是poll
还是需要通过遍历文件描述符来获取已经就绪的socket
。
那么为了解决select
和poll
存在的问题,就有了IO多路复用(epoll
)模型,采用事件驱动来实现:
看起来和select
的流程图没啥区别,这里加点文字描述:
epoll
首先通过epoll_ctl()
函数来注册一个文件描述符。fd
就绪时,内核会采用回调机制,迅速激活这个fd
。epoll_wait()
时便得到通知。通过采用监听事件回调的机制来避免遍历所有的文字描述符。虽然IO多路复用这种方式对于非阻塞式IO,不需要进行频繁的调用,而是通过回调的方式来进行。但是当进程调用epoll_wait()
时,仍然可能被阻塞。
重要的事情说三遍,多路复用IO它依旧是:同步阻塞的!同步阻塞的!同步阻塞的!
因此从设计上希望有这么个功能(这里我觉得应该这么理解会更好):
随之而来的也就是信号驱动IO模型。
在多路复用的基础上。向内核发送一个信号。此时应用进程不用阻塞,可以去做其他事情。当内核数据准备好后,再通过SIGIO
信号通知应用进程。进程一旦获取到信号,就立即调用获取内核数据。如图:
当然,这里数据状态询问流程是异步的没错,但是数据复制部分,依旧是同步阻塞的,也因此这整个信号驱动IO的流程并不是异步的。
只需要向内核发送一次请求,就可以完成数据状态询问和数据拷贝的所有操作,并且不用阻塞等待结果。
这里解释下BIO、NIO、AIO
:
blocking-IO
)简称BIO
。non-blocking-IO
)简称NIO
。asynchronous-non-blocking-IO
)简称AIO
。比较项 | select | poll | epoll |
---|---|---|---|
底层数据结构 | 数组 | 链表 | 红黑树+双链表 |
获取就绪的fd方式 | 遍历所有 | 遍历所有 | 事件回调 |
事件复杂度 | O(n) |
O(n) |
O(1) |
最大连接数 | 1024(Linux ) |
无限制 | 无限制 |
fd 数据拷贝方式 |
每次调用select ,都需要将fd 从用户空间拷贝到内核空间 |
每次调用poll ,都需要将fd 从用户空间拷贝到内核空间 |
通过内存映射(mmap ),不需要进行频繁的拷贝fd,一次即可。 |
IO模型 | 阻塞状态 | 同步状态 |
---|---|---|
阻塞式IO | 阻塞 | 同步 |
非阻塞式IO | 非阻塞 | 同步 |
IO多路复用 | 阻塞 | 同步 |
信号驱动IO | 非阻塞 | 同步 |
异步IO | 非阻塞 | 异步 |
阻塞,非阻塞的概念区分:可以简单理解为需要做一件事能不能立即得到返回应答,如果不能立即获得返回,需要等待,那就阻塞。 更倾向于是否立即应答。
同步,异步的概念区分:你总是做完一件再去做另一件,不管是否需要时间等待,这就是同步。否则就是异步。 更倾向于是否可以并行做两件事。
那回过头再看上面的表格:
非阻塞式IO方面的解释:
IO多路复用方面的解释:
信号驱动IO方面的解释:
fd
找到了。让用户进程得到fd
,再由用户进程发起请求进行数据拷贝。异步IO方面的解释:
最终可以发现,针对这五种IO模型,关于异步和同步的区别无非就是: