参考博客:
1. 内核级别的IO
1.1 同步IO vs 异步IO
看过很多版本对于这些概念的描述,我觉得来自POSIX标准的这个判断准则最简单也最容易理解:
从发出IO操作请求开始到IO操作结束的过程中没有任何阻塞,就称为异步,否则为同步
1.2 IO 的两个阶段
那么,问题来了,从请求开始到操作完成,IO具体做了什么呢?其实,IO可以分为两个阶段,以socket的recv系统调用为例:
- 第一阶段、等待数据就绪
- 第二阶段、将数据从内核缓冲区复制到应用缓存区
2. IO 模型
IO模型在不断的演化和改进中,要做一个梳理并不容易,查阅了很多资料中对于IO模型的归纳,相比觉得《UNIX网络编程 卷1:套接字联网API》这本书中所总结的五种IO模型是最为清晰和合适的。
1) 阻塞式IO
最为常用的一种IO模型,整个IO操作从发起开始一直阻塞到完成,显然,这不够高效,但模型简单容易理解。目前,Linux的系统调用默认情况下大多是阻塞式的,socket的recv系统调用也是如此。
2) 非阻塞式IO
对于网络IO来说,IO的第一阶段往往需要比第二阶段花更多的时间,所以非阻塞式IO旨在将第一阶段非阻塞化,还是以recv系统调用为例,调用非阻塞式recv时,如果数据未ready,将直接返回,反之,就阻塞式地进行第二阶段的IO。在数据未就绪的情况下通常需要结合轮询机制,保证就绪状态的IO能够被及时的处理,这对CPU的浪费也是比较大的。非阻塞式的IO可以通过设置文件描述符O_NONBLOCK标识位实现,但它仅针对网络IO有效。
3) IO Multiplexing
非阻塞式IO在遇到大量并发IO的时候(想想Web服务器的场景),CPU将忙于对所有文件描述符进行轮询检查,因为一次检查只能获知一个文件描述符的就绪状态,非常低效。随后,IO Multiplexing就实现了一次检查可以同时获取到多个文件描述符的就绪状态,通过这种方式可以大大提高就绪检查的效率。但需要指出的是,就绪检查本身是阻塞式的操作,在Linux平台,IO Multiplexing有多种实现,这里按照出现顺序来进行一一介绍。
- select:通过select系统调用,需要检查的文件描述符数据被作为参数传入