I/O有内存I/O、网络I/O和磁盘I/O三种,通常我们说的I/O指的是后两者。如下图所示是I/O通信过程的调度示意。
五种I/O通信模型
在网络环境下,通俗地讲,将I/O分为两步:第一步是等待;第二步是数据搬迁。
如果想要提高I/O效率,需要将等待时间降低。因此发展出来五种I/O模型,分别是:
- 阻塞I/O模型、
- 非阻塞I/O模型、
- 多路复用I/O模型、
- 信号驱动I/O模型、
- 异步I/O模型。
其中,前四种被称为同步I/O,下面对每一种I/O模型进行详细分析。
IO模型总结
想要清楚了解IO,你必须要了解操作系统,了解内核进程与用户进程,了解计算机网络,不然多多少少会有些迷糊。
我也迷糊
五种阻塞模型,分别对应五种情况
- 用户进程需要数据,执行读取操作,数据没有到达,用户进程需要阻塞等待,这种叫做 阻塞I/O模型
- 用户进程需要数据,执行读取操作,数据没有到达,但是读取操作可以返回一个err结果终止这次read,用户之后再进行read,这叫做非阻塞I/O模型
- 用户进程需要数据,执行读取操作,数据没有到达,用户进程将自己注册到另一个进程,称为select进程,select进程专门负责查看内核中数据有没有到达,为所有需要I/O的用户进程服务,如果发现内核中有某个用户进程需要的数据到达,通知该用户进程,而在此之前,用户进程可以做其他事。这叫做多路复用I/O模型。
- 用户进程需要数据,首先发个信号给内核,当内核数据到达,发送一个信号给进程,进程再执行读取操作,这叫做信号驱动I/O模型
这里的用户进程和内核有点像客户端和服务器,有些过程还有点像我们熟知的短轮询和长连接实现。双方都具有通信的主动权,平常会通过用户进程主动请求,但是也可以由内核主动通知用户。
- 用户进程需要数据,直接把数据要存放的内存位置告诉内核,内核直接实现IO全过程,然后直接通知用户进程操作完成,这叫做异步I/O模型
阻塞IO模型
主要表现,用户进程进入等待,不做其他事
当用户进程调用了recvfrom这个系统调用,内核就开始了I/O的第一个阶段:准备数据。**对于网络I/O来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候内核就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞,当数据准备好时,它就会将数据从内核拷贝到用户内存,然后返回结果,用户进程才解除阻塞的状态,重新运行起来。**几乎所有的开发者第一次接触到的网络编程都是从listen()、send()、recv()等接口开始的,这些接口都是阻塞型的。阻塞I/O模型的特性总结如下表所示
非阻塞I/O模型
当用户进程发出read操作时,如果内核中的数据还没有准备好,那么它并不会阻塞用户进程,而是立刻返回一个error。从用户进程角度讲,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果,用户进程判断结果是一个error时,它就知道数据还没有准备好。于是它可以再次发送read操作,一旦内核中的数据准备好了,并且再次收到了用户进程的系统调用,那么它会马上将数据拷贝到用户内存,然后返回,非阻塞型接口相比于阻塞型接口的显著差异在于,在被调用之后立即返回。非阻塞I/O模型的特性总结如下表所示。
多路复用I/O模型
多个进程的I/O可以注册到一个复用器(Selector)上,当用户进程调用该Selector,Selector会监听注册进来的所有I/O,如果Selector监听的所有I/O在内核缓冲区都没有可读数据,select调用进程会被阻塞,而当任一I/O在内核缓冲区中有可读数据时,select调用就会返回,而后select调用进程可以自己或通知另外的进程(注册进程)再次发起读取I/O,读取内核中准备好的数据,多个进程注册I/O后,只有一个select调用进程被阻塞。
多路复用I/O相对阻塞和非阻塞更难简单说明,所以额外解释一段,其实多路复用I/O模型和阻塞I/O模型并没有太大的不同,事实上,还更差一些,因为这里需要使用两个系统调用(select和recvfrom),而阻塞I/O模型只有一次系统调用(recvfrom)。但是,用Selector的优势在于它可以同时处理多个连接,所以如果处理的连接数不是很多,使用select/epoll的Web Server不一定比使用多线程加阻塞I/O的Web Server性能更好,可能延迟还更大,select/epoll的优势并不是对于单个连接能处理得更快,而是能处理更多的连接。多路复用I/O模型的特性总结如下表所示。
信号驱动I/O模型
信号驱动I/O是指进程预先告知内核,向内核注册一个信号处理函数,然后用户进程返回不阻塞,当内核数据就绪时会发送一个信号给进程,用户进程便在信号处理函数中调用I/O读取数据。从上图可以看出,实际上I/O内核拷贝到用户进程的过程还是阻塞的,信号驱动I/O并没有实现真正的异步,因为通知到进程之后,依然由进程来完成I/O操作。这和后面的异步I/O模型很容易混淆,需要理解I/O交互并结合五种I/O模型进行比较阅读。信号驱动I/O模型的特性总结如下表所示
异步I/O模型
用户进程发起aio_read操作后,给内核传递与read相同的描述符、缓冲区指针、缓冲区大小三个参数及文件偏移,告诉内核当整个操作完成时,如何通知我们立刻就可以开始去做其他的事;而另一方面,从内核的角度,当它收到一个aio_read之后,首先它会立刻返回,所以不会对用户进程产生任何阻塞,内核会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,内核会给用户进程发送一个信号,告诉它aio_read操作完成。
异步I/O的工作机制是:告知内核启动某个操作,并让内核在整个操作完成后通知我们,这种模型与信号驱动I/O模型的区别在于,信号驱动I/O模型是由内核通知我们何时可以启动一个I/O操作,这个I/O操作由用户自定义的信号函数来实现,而异步I/O模型由内核告知我们I/O操作何时完成。