一、用户态和内核态
我们知道现在的操作系统是分层的,内核封装了与底层的接口,通过系统调用提供给上层应用使用。 当进程运行在内核空间时,它就处于内核态;当进程运行在用户空间时,它就处于用户态。
当我们需要进行IO操作时,如读写硬盘文件、读写网卡数据等,进程需要切换到内核态。一般情况下,应用不能直接操作内核空间的数据,需要把内核态的数据拷贝到用户空间才能操作。
应用程序向操作系统发出IO请求:应用程序发出IO请求给操作系统内核,操作系统内核需要等待数据就绪,这里的数据可能来自别的应用程序或者网络。 一般来说,一个IO分为两个阶段:
先读取到内核缓存,等待数据准备好。再读取到用户进程,将数据从内核读取到用户空间。
1、等待数据: 数据可能来自其他应用程序或者网络,如果没有数据,应用程序就阻塞等待。
2、拷贝数据: 将就绪的数据拷贝到应用程序工作区。
在Linux系统中,操作系统的IO操作是一个系统调用recvfrom(),即一个系统调用recvfrom包含两步,等待数据就绪和拷贝数据。
二、2个容易混淆的概念
1、阻塞与非阻塞
阻塞和非阻塞是针对进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式。
(1)阻塞指的是当试图对该文件描述符进行读写时,如果当时没有东西可读,或暂时不可写,程序就进入等待状态,直到有东西可读或可写为止。 比如我们去窗口办事的时候,发现工作人员碰巧不在,然后我们就在原地等待,一直等到工作人员回来为止。
(2)非阻塞指的是如果没有东西可读,或不可写,读写函数马上返回,而不会等待。 去银行办业务时,领取一张小票,之后我们可以去干其他的事情,当轮到我们时,银行会通知,这时候我们就可以去办业务了。
2、同步与异步
同步和异步是针对应用程序和内核的交互而言的。
(1)同步指的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪。 例如:自己亲自干这件事,别的事干不了。
(2)异步指的是用户进程触发IO操作以后便开始做其他的事情,而当IO操作已经完成的时候会得到IO完成的通知。 例如:告诉朋友自己合适衣服的尺寸、颜色、款式,委托朋友去买,然后自己可以去干别的事。(使用异步I/O时,将I/O读写委托给OS处理,需要将数据缓冲区地址和大小传给OS)。
三、IO模型
1、同步阻塞(同步IO)
在此种方式下,用户进程在发起一个IO操作以后,必须等待IO操作的完成,只有当IO操作完成之后,用户进程才能运行。
2、同步非阻塞(同步IO)
在此种方式下,用户进程发起一个IO操作以后就可返回做其它事情,但是用户进程需要时不时地询问IO操作是否就绪,这就要求用户进程不停的去询问,从而引起不必要的CPU资源浪费。
3、多路复用(同步IO)
多路复用中,通过select函数,可以同时监听多个IO请求的内核操作,只要有任意一个IO的内核操作就绪,就可以通知select函数返回,再进行系统调用recvfrom()完成IO操作。
我们通常所说的select,epoll就是这种方式。多路复用中,通过select函数,可以同时监听多个IO请求的内核操作,只要有任意一个IO的内核操作就绪,都可以通知select函数返回,再进行系统调用recvfrom()完成IO操作。
注意:
当一个服务器 在处理多个客户时,绝对不能阻塞于单个客户相关的系统函数调用。否则导致服务器端程序被挂起,不能为其它客户提供服务。解决方法如下:
(1)使用非阻塞式I/O。
(2)对每个客户有单独的控制进程提供服务。
(3)对每个I/O操作设置一个超时。
4、信号驱动IO(同步IO)
应用程序发起IO请求时,可以给IO请求注册一个信号函数,请求立即返回,操作系统底层则处于等待状态(等待数据就绪),直到数据就绪,然后通过信号通知主调程序,主调程序才去调用系统函数recvfrom()完成IO操作。
信号驱动IO的特点:
(1)相比前面的非阻塞式IO模型,信号驱动式IO模型不需要轮询检查底层IO数据是否就绪,而是被动接收信号,然后再调用recvfrom执行IO操作。
(2)相比多路复用IO模型来说,信号驱动IO模型针对的是一个IO的完成过程, 而多路复用IO模型针对的是多个IO同时进行时候的场景。
注意:
(1)对于TCP套接字:信号驱动式IO不适合处理TCP套接字,因为信号产生的过与频繁,在TCP中,连接请求完成、断开连接发起、断开连接完成、数据到达、数据送走。。。都会产生SIGIO。但是我们真正只需要它在数据到达或者数据送走的时候才产生信号。
(2)对于UDP套接字:SIGIO信号在数据报到达套接字以及套接字上发生异步错误才会发生。(UDP套接字推荐使用)。
5、异步IO
异步IO将整个IO操作(包括等待数据就绪,复制数据到应用程序工作空间)全都交给操作系统完成。 数据就绪后操作系统将数据拷贝进应用程序运行空间之后,操作系统再通知应用程序,这个过程中应用程序不需要阻塞。
四、IO模型对比
举一个现实生活中的例子:
如果你想吃饭。
同步阻塞:你到饭馆点餐,然后在那儿等着,还要一直喊:好了没啊!
同步非阻塞:在饭馆点完餐,就去遛狗了。不过遛一会儿,就回饭馆喊一声:好了没啊!
多路复用:在多个饭馆点餐,然后查看哪家饭馆先做好,谁先做好就吃谁的。
信号驱动:遛狗的时候,接到饭馆电话,说饭做好了,让您亲自去拿。
异步非阻塞:饭馆打电话说,我们知道您的位置,一会儿给你送过来,安心遛狗就可以了。
五:总结
(1)阻塞、非阻塞、多路IO复用,都是同步IO。
(2)异步必定是非阻塞的,所以不存在异步阻塞和异步非阻塞的说法。
(3)真正的异步IO需要CPU的深度参与。 换句话说,只有用户线程在操作IO的时候根本不去考虑IO的执行,全部都交给CPU去完成,而只需要等待一个完成信号的时候,才是真正的异步IO。 所以,fork子线程去轮询、死循环或者使用select、poll、epoll,都不是异步。
(4)阻塞方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
(5)非阻塞方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
(6)异步方式适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。