IO有三种:内存IO、网络IO和磁盘IO,通常说的是后两者
网络IO的本质是socket的读取,对于一次IO访问,过程描述为:网络传输过来的数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。
同步阻塞I/O
进程发起IO操作后会阻塞等待,直到有数据返回。默认情况下socket就是这种模型,服务端socket调用accept函数后便会阻塞进程,直到有客户端连接上来为止,然后调用recv函数接收并打印客户端数据。
同步阻塞IO模型中,用户空间的应用程序执行一个系统调用后,应用程序便阻塞等待结果,并不会消耗CPU资源,结果数据出来后,再将数据从内核复制到用户进程,最后该应用进程再处理数据,在等待数据到处理数据的两个阶段,整个进程都被阻塞,不能处理别的网络IO。
以socket的recvform系统调用为例,可用如下如图表示该过程:
当用户进程调用了recv()/recvfrom()系统调用后,kernel就开始了IO的第一个阶段:准备数据(对于网络IO来说,很多时候数据在一开始还没有到达。比如,还没有收到一个完整的UDP包,这个时候kernel就要等待所有的数据都接收到),对于IO来说,等待数据接收完毕,对于用户进程来说,整个进程会被主动阻塞。第二个阶段:当kernel等到数据准备好后,将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。
特点:阻塞等待结果
同步非阻塞I/O
可以设置recvfrom()的其中一个参数为MSG_DONTWAIT(非阻塞),当用户进程调用了recvfrom()系统调用后,可以去处理其他的任务,但是需要调用可以设置recvfrom轮询检查内核数据是否准备好,这个过程需要不停的切换两种任务(处理的其他任务和轮询数据是否准备好)。
可以用如下图表示该过程:
当用户进程发出read操作后,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果,并且还可以处理其他的任务。当用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。
特点:不用阻塞等待结果,但是轮询和进程切换会消耗CPU资源。
I/O复用
不用用户进程轮询数据是否准备好,而是借助内核去监听文件描述符,其中的任意一个进入读就绪状态,select, poll,epoll函数就可以返回结果。
在I/O复用模型中,每一个socket,一般都设置成为non-blocking,但是整个应用程序实质上是被阻塞的,只是不是socket IO阻塞,而是IO复用调用select或者poll或者epoll系统调用时被阻塞,一旦监听到数据准备好,就会返回结果,recvfrom被调用并且立即将数据从内核复制到用户空间(在上面提到阻塞I/O的模型中,调用recvfrom后会阻塞等待数据是否准备好)。因此I/O复用也是一种同步阻塞模式(同步是需要主动等待消息通知,而异步则是被动接收消息通知,通过回调、通知、状态等方式来被动获取消息。IO多路复用在阻塞到select阶段时,用户进程是主动等待并调用select函数获取数据就绪状态消息,并且其进程状态为阻塞。)
I/O复用的详解戳这里:https://blog.csdn.net/IT_10/article/details/90552984
同步、异步、阻塞、非阻塞的详解戳这里:https://blog.csdn.net/IT_10/article/details/93904775
信号驱动式I/O
上面提到的三种方式都需要用户程序不断询问是否有预期的结果返回,即使I/O复用模型中的recvfrom不用阻塞,但是select调用也会阻塞询问。
在信号驱动式I/O模型中,当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。过程如下图所示:
在等待数据是否准备好时,可以继续往下执行而不用阻塞和轮询,当数据准备好了,信号处理程序会自动通知应用程序。
特点:不用阻塞等待和轮询检查结果
异步非阻塞 I/O
对比信号驱动I/O,异步I/O的主要区别在于:信号驱动由内核告诉应用程序何时可以开始一个IO操作(数据在内核缓冲区中),而异步IO则由内核通知IO操作何时已经完成(数据已经在用户空间中)。
总结
同步阻塞I/O:应用程序发起一个系统调用后,阻塞等待结果并且不断轮询是否有结果,不处理其他任务,直到结果返回。
非阻塞I/O:应用程序发起一个系统调用后,不断轮询是否有结果,但是可以处理其他任务,直到结果返回。
I/O复用:由内核检查是否有结果返回,有结果返回后,调用系统调用等立即得到响应,但是内核在检查结果的过程中是阻塞轮询的方式。
信号驱动I/O:应用程序不用阻塞等待和轮询检查结果,而是通过注册信号机制,当数据准备好后通知应用程序。
异步非阻塞 I/O:应用程序发起一个系统调用后得到立即返回的结果,然后去处理其他的任务,当数据准备好并且已经复制到用户空间后通知应用程序。
参考:https://my.oschina.net/xianggao/blog/662803