I/O复用和阻塞式I/O很相似,不同的是,I/O复用等待多类事件,阻塞式I/O只等待一类事件,另外,在I/O复用中,会产生两个系统调用(如上图,select和recvfrom),而阻塞式I/O只产生一个系统调用。那么这就涉及到具体的性能问题,当只存在一类事件的时候,使用阻塞式I/O模型的性能会更好,当存在多种不同类型的事件时,I/O复用的性能要好的多,因为阻塞式I/O模型只能监听一类事件,所以这个时候需要使用多线程进行处理。
信号驱动式I/O模型在信号驱动式I/O模型中,与阻塞式和非阻塞式有了一个本质的区别,那就是用户态进程不再等待内核态的数据准备好,直接可以去做别的事情。
如上图所示,当需要等待数据的时候,首先用户态会向内核发送一个信号,告诉内核我要什么数据,然后用户态就不管了,做别的事情去了,而当内核态中的数据准备好之后,内核立马发给用户态一个信号,说”数据准备好了,快来查收“,用户态进程收到之后,立马调用recvfrom,等待数据从内核空间复制到用户空间,待完成之后recvfrom返回成功指示,用户态进程才处理别的事情。
通过上面的图,可以看出信号驱动式I/O模型有种异步操作的赶脚,但是在将数据从内核复制到用户空间这段时间内用户态进程是阻塞的
异步I/O模型异步I/O模型相对于信号驱动式I/O模型就更彻底了。
如上图,首先用户态进程告诉内核态需要什么数据(上图中通过aio_read),然后用户态进程就不管了,做别的事情,内核等待用户态需要的数据准备好,然后将数据复制到用户空间,此时才告诉用户态进程,”数据都已经准备好,请查收“,然后用户态进程直接处理用户空间的数据。
在复制数据到用户空间这个时间段内,用户态进程也是不阻塞的
同步I/O《UNIX网络变成卷1:套接字联网API》这本书中,并没有把同步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,书中说到:POSIX将同步IO操作定义为“导致请求进程阻塞,直到I/O操作完成”,而书中认为在信号驱动式I/O模型中等待数据的那段时间不算是真正的I/O操作(因为没有调用I/O相关的系统调用),而数据从内核复制到用户空间才是真正的I/O操作(这个时候调用了recvfrom系统调用)。
I/O模型比较书中的这张图表述的非常清楚,从等待数据和数据复制这两个时间段,指出了不同I/O模型的区别,这里不再赘述。
总结从网上看了很多资料,不同的博主对这五个模型总结的情况不同,无一例外,基本都采用一个生活场景来描述他们的不同,但是我个人觉得有些场景描述太过简单,没有将不同模型的区别描述完全,在这里我也举一个生活中的场景作为总结,当然这只是我自己的想法,不妥之处评论区可以指出。
我们去餐厅吃饭,会经过以下几个步骤:首先根据菜单点菜,然后等待厨房准备好,接着服务员上菜。在这个场景中,等待厨房准备菜肴等同于等待数据,服务员上菜等同于将数据从内核复制到用户空间,你就是用户态进程了,服务员和饭店看作是内核态的进程。
阻塞式I/O模型:只点一个菜,然后在餐桌上开始等待,在这个过程中什么事都不干,等服务员把菜上到桌子上之后才开始大快朵颐。
非阻塞式I/O模型:只点一个菜,然后开始等待,啥事都不做,等了一会儿然后就去问服务员,“我的菜好了吗?”,没好接着等待,过了一会儿然后又跑去问....重复这个过程,直到服务员说“亲,你的菜好了,我现在给您送桌上去”,然后你坐在桌子上,等待服务员把饭菜送到你的餐桌上,才开始吃饭。