I/O复用——各类不一样的IO模型

1、概述

         咱们看到上面的TCP客户同时处理两个输入:标准输入和TCP套接字。咱们遇到的问题就是在客户阻塞于(标准输入上的)fgets调用期间,服务器进程会被杀死。服务器TCP虽然正确地给客户TCP发送一个FIN,可是既然客户进程阻塞于从标准输入读入的过程,它将看不到这个ROF,知道从套接字读时为止(可能已通过了很长时间)。这样的进程须要一种预先告知内核的能力,使得内核一旦发现进程指定的一个或多个I/O条件就绪(也就是说输入已准备好被读取,或者描述符已能承接更多的输出),它就通知进程。这个能力成为I/O复用,是由select和poll这两个函数支持的。web

        I/O复用典型使用在下列网络应用场合:编程

         1)当客户处理多个描述符(一般是交互式输入和网络套接字)时,必须使用I/O复用服务器

         2)一个客户同时处理多个套接字是可能的,不过比较少见。在16.5节结合一个web客户的上下文给出这种场合使用select的例子网络

          3)若是一个TCP服务器既要处理监听套接字,又要处理已链接套接字,通常就要使用I/O复用异步

          4)若是一个服务器既要处理TCP,又要处理UDP,通常就要使用I/O复用。8.15节有这么一个例子函数

         5)若是一个服务器要处理多个服务或者镀铬协议(在13.5节讲述的inetd守护进程),就要用I/O复用spa

   I/O复用并不是只限于网络编程,许多重要的应用程序也须要使用这项技术指针

2、I/O模型

          在UNIX下可用的5种I/O模型:进程

           阻塞式I/O;事件

          非阻塞式I/O;

           I/O复用(select和poll);

          信号驱动式I/O;

           异步I/O

          在上述所说的那样,一个输入操做一般包括两个不一样的阶段:

          1)等待数据准备好;

          2)从内核向进程复制数据

         对于一个套接字上的输入操做,第一步一般涉及等待数据从网络中到达。当所等待分组到达时,它被复制到内核总的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用进程缓冲区。

       (1)阻塞时I/O模型:

        最流行的I/O模型,本书到目前为止的全部例子都使用该模型。默认情形下,全部套接字都是阻塞的。

         使用UDP而不是TCP为例子的缘由在于就UDP而言,数据准备好读取的概念比较简单:要么整个数据报已经收到,要么尚未。对于TCP而言,诸如套接字低水位标记等额外变量开始起做用,道指这个概念复杂。

         咱们把recvfrom函数视为系统调用,由于咱们正在区分应用进程和内核。无论如何实现,通常都会从在应用进程空间中国运行切换到在内核空间中运行,一端时间以后再切换回来。 在上图中,进程调用recvfrom,其系统调用直到数据报到达且被复制到应用进程的缓冲区中或者发送错误才返回。最多见的错误是系统调用被信号中断,咱们说进程在从调用recvfrom开始到它返回的整段时间内是被阻塞的。recvfrom成功返回后,应用进程开始处理数据报。

         (2)非阻塞式I/O模型:

        进程把一个套接字设置成非阻塞是在通知内核:当全部请求的I/O操做非得把本进程投入睡眠才能完成时,不要把本进程投入睡眠,而是返回一个错误。将在16章中详细介绍非阻塞是I/O

         前三次调用recvfrom时没有数据可返回,所以内核转而当即返回一个EWOULDBLOCK错误。第四次调用recvfrom时已有一个数据报准备好,它被复制到应用进程缓冲区,因而recvfrom成功返回。接着处理数据。

       当一个应用进程像这样对一个非阻塞描述符循环调用recvfrom时,咱们成为轮询,应用进程持续轮询内核,以查看某个操做是否就绪。这么作每每耗费大量CPU时间,不过这种模型偶尔也会遇到。

          (3)I/O复用模型:

          有了I/O复用,咱们就能够调用select或者poll,阻塞在这两个系统调用中的某一个,而不是阻塞在真正的I/O系统调用上。下图展现了I/O复用模型

          咱们阻塞与select调用,等待数据报套接字变为可读。当select返回套接字可读这一条件时,咱们调用recvfrom把所可读数据报复制到应用进程缓冲区。比较上面两图,I/O复用并不显得有什么优点,事实上因为使用select须要两个而不是单个系统调用,其优点在于能够等待多个描述符就绪

         (4)信号驱动式U/O模型:

         能够用信号,让内核在描述符就绪时发送SIGIO信号通知咱们。称为信号驱动式I/O

        咱们首先开启套接字的信号驱动式I/O功能,并经过sigaction系统调用安装一个信号处理函数。该系统调用将当即返回,咱们的进程继续工做,也就是说它没有被阻塞。当数据报准备好读取时,内核就为该进程产生一个SIGIO信号。咱们随后既能够在信号处理函数中调用recvfrom读取数据报,并通知主循环数据已准备好待处理。也能够当即通知循环,让它读取数据报。

         不管如何处理SIGIO信号,这种模型的优点在于等待数据报到达期间进程不被阻塞。主循环能够继续执行,只要等待来自信号处理函数的通知:既能够是数据已准备好被处理,也能够是数据报已准备好被读取。

        (5) 异步I/O模型:

        告知内核启动某个操做,并让内核在整个操做(包括将数据从内核复制到咱们本身的缓冲区)完成后通知咱们。这种模型与前一节介绍的信号驱动模型的主要区别在于:信号驱动I/O是由内核通知咱们如何启动一个I/O操做,而异步I/O模型是由内核通知咱们I/O操做什么时候完成。

       咱们调用aio_read函数,给内核传递描述符、缓冲区指针。缓冲区大小和文件偏移,并告诉内核当整个操做完成时如何通知咱们。该系统调用当即返回,并且在等到I/O完成期间,咱们的进程不被阻塞。

         前四种模型的主要区别在于第一阶段,由于它们的第二阶段是同样的:在数据从内核复制到调用者的缓冲区期间,进程阻塞与recvfrom调用。相反,异步I/O模型在这两个阶段都要处理。

4 同步IO和异步IO

  • 同步IO操做致使请求进程阻塞,直到IO操做完成
  • 异步IO操做不致使请求进行阻塞

从理论上讲,非阻塞IO、阻塞IO、IO复用和信号驱动IO都是同步IO模型。由于这四种IO模型中,IO的读写操做,都是在IO事件发生以后,由应用进程来完成的。而POSIX规范所定义的异步IO模型则不一样。对异步IO而言,用户能够直接对IO执行读写操做,这些操做告诉内核用户读写缓冲区的位置,以及IO操做完成以后内核通知应用程序的方式。异步IO的读写操做老是当即返回,而不论IO是不是阻塞的,由于真正的读写操做已经由内核接管。也就是说,同步IO模型要求用户代码自行执行IO操做(将数据从内核缓冲区读入用户缓冲区,或将数据从用户缓冲区写入内核缓冲区),而异步IO机制则由内核来执行IO操做(数据在内核缓冲区和用户缓冲区之间的移动是由内核在“后台”完成的)。你能够这样认为,同步IO向应用程序通知的是IO就绪事件,而异步IO向应用程序通知的是IO完成事件。

相关文章
相关标签/搜索