IO模式
本文讨论的背景是Linux环境下的网络IO。
对于一次IO访问,数据会先被拷贝到操作系统内核的缓冲区中,然后再从操作系统内核的缓冲区拷贝到应用程序的地址空间。
所以,当发生一个IO操作时,它会经历两个阶段:
1. 等待数据准备
2. 将数据从内核拷贝到进程中
对于两个阶段,linux系统产生了下面五种网络模式的IO存储方案。
- 阻塞 I/O(blocking IO)
- 非阻塞 I/O(nonblocking IO)
- I/O 多路复用( IO multiplexing)
- 信号驱动 I/O( signal driven IO)
- 异步 I/O(asynchronous IO)
注:信号驱动IO在实际中并不常用,所以我们讨论剩下的四种IO模式,有兴趣可以自行了解信号驱动IO。
阻塞IO:
在linux中,默认情况下所有的socket都是阻塞io。
这个模式的特点在于,当用户进程调用了io操作的时候,操作系统就开始准备数据,当操作系统内核一直等到数据准备好了,它就会将数据从操作系统内核中拷贝到用户内存,然后返回结果,用户进程才解除锁住的状态,重新运行起来。整个过程需要等待(不能立刻完成),而用户进程这边,就相当于是卡着一直在等他完成返回结果。
所以,阻塞IO的特点就是在IO执行的两个阶段都被锁住了
非阻塞 I/O(nonblocking IO)
linux下,可以通过设置socket使其变为非阻塞IO。
非阻塞IO的特点是,当用户进程发出IO操作时,在第一个阶段如果内核数据还没准备好,并不会锁住用户进程,而是立刻返回一个error。然后用户进程收到error,就知道没准备好,就会再去问内核有没有准备好,直到内核准备好,并且收到了用户进程的询问,就会立刻拷贝数据,然后进行第二阶段(第二阶段依然会卡住,非阻塞IO只是在第一阶段非阻塞)
就像打电话一样,对方占线,你就一直打,直到对方接通为止,但效率不高。所以,nonblocking IO的特点是用户进程需要不断的主动询问kernel数据好了没有。
I/O 多路复用( IO multiplexing)
IO多路复用就是我们用的select,poll,epoll。当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。简单的说,就是和阻塞IO处理方式一样,但不同的是,select可以一下把很多很多文件描述符都传过来,然后操作系统内核同时监视他们,只要其中一个就绪,就及时处理那个,而不需要一个个等待处理。
他的性能可能比阻塞IO还要差,但他的优点在于能同时处理多个连接。所以,如果处理的连接数不是很高的话,使用IO多路复用不一定比使用多线程+阻塞IO的性能更好,可能延迟还更大。
异步 I/O(asynchronous IO)
Linux下的异步IO其实用得很少。
异步io的特点很简单:当处理一个io操作的时候,用户进程把数据传过来,操作系统会立刻返回“收到”,用户进程就不用管了,可以干别的事情了。等操作系统内核处理完2个阶段,就自动告诉用户进程我处理完了。
总结一下:
阻塞IO:用户一直等操作系统内核完成2个阶段io操作,再继续
非阻塞IO:第一阶段用户一直问操作系统有没有准备好,第二阶段等操作系统完成。
IO多路复用:用户进程可以同时发多个网络连接IO,操作系统实施监控,虽然用户进程2个阶段都卡住了,但可以处理多个连接。
异步IO:用户进程完全把东西交给操作系统完成,自己不管了。