转载请联系EmbeddedHacker公众号。1、Linux的5种IO模型2、如何使用信号驱动I/O?3、内核什么时候发送“IOready”信号?四、最简单的例子五、扩展知识1、Linux的5种IO模型BlockingI/O:一个系统调用可能因为不能立即完成而被操作系统挂起,直到等待的事件发生。非阻塞I/O(O_NONBLOCK):无论事件是否发生,系统调用总是立即返回。I/O多路复用(select、poll、epoll):通过I/O多路复用函数向内核注册一组事件,内核通过I/O多路复用函数将准备好的事件通知应用程序。信号驱动I/O(SIGIO):为目标文件描述符指定主机进程。当文件描述符上发生事件时,会触发SIGIO的信号处理函数,然后可以对目标文件描述符进行I/O。操作哦。异步I/O(POSIX的aio_系列函数):异步I/O的读写操作总是立即返回,不管I/O是否阻塞,真正的读写操作由核心。想一想,什么时候应该选择哪种I/O模型?为什么要选择这种方式?下面重点介绍信号驱动的I/O模型,其他模型可以在文末的参考书中找到。2.如何使用信号驱动I/O?一般通过以下6个步骤来使用信号驱动的I/O模型。1>安装通知信号的处理函数。它由sigaction()完成:intsigaction(intsignum,conststructsigaction*act,structsigaction*oldact);默认情况下,此通知信号为SIGIO。2>设置文件描述符的所有者。这是通过fcntl()的F_SETOWN操作完成的:fcntl(fd,F_SETOWN,pid)所有者是当I/O在文件描述符上可用时将接收通知信号的进程或进程组。当pid为正整数时,表示进程ID号。当pid为负整数时,其绝对值代表进程组ID号。3>启用非阻塞I/O。它是由fcntl()的F_SETFL操作完成的:flags=fcntl(fd,F_GETFL);fcntl(fd,F_SETFL,flags|O_NONBLOCK);4>使能信号驱动I/O。它是由fcntl()的F_SETFL操作完成的:flags=fcntl(fd,F_GETFL);fcntl(fd,F_SETFL,flags|O_ASYNC);5>进程等待“IO就绪”信号的到来。当I/O操作就绪时,内核向进程发送信号,然后调用步骤1中安装的信号处理程序。6>进程执行尽可能多的I/O操作。循环I/O系统调用直到失败,错误代码为EAGAIN或EWOULDBLOCK。原因:信号驱动的I/O提供了边沿触发的通知,即我们只有在I/O事件发生的时候才会收到通知,而文件描述符收到I/O事件通知的时候不知道要处理多少我/O数据。3、内核什么时候发送“IOready”信号?不同类型的文件描述符情况不同。1>Terminal对于一个终端,当有新的输入时产生一个信号。2>PipelineandFIFO对于读端,以下情况会产生一个信号:数据写入管道;管道的写端关闭;对于写端,以下情况会产生一个信号:管道上的读操作增加了管道中空闲空间的大小。管道的读端关闭;3>socket对于UDP套接字,以下情况会产生信号:数据报到达套接字;套接字发生异步错误;对于TCP套接字,信号驱动的I/O几乎没用。信号产生的情况太多了,我们无法知道事件的类型,所以这里就不一一列举产生信号的情况。四、最简单的示例信号处理函数:staticvolatilesig_atomic_tgotSigio=0;staticvoidhandler(intsig){gotSigio=1;}主程序:intmain(intargc,char*argv[]){intflags,j,cnt;structtermiosorigTermios;charch;structsigaction;intdone;/*Establishhandler*/sigemptyset(&sa.sa_mask);sa.sa_flags=SA_RESTART;sa.sa_handler=handler;if(sigaction(SIGIO,&sa,NULL)==-1){perror("sigaction()\n");exit(1);}/*Setownerprocess*/if(fcntl(STDIN_FILENO,F_SETOWN,getpid())==-1){perror("fcntl()/F_SETOWN\n");exit(1);}/*启用“I/Opossible”signalingandmakeI/Ononblocking*/flags=fcntl(STDIN_FILENO,F_GETFL);if(fcntl(STDIN_FILENO,F_SETFL,flags|O_ASYNC|O_NONBLOCK)==-1){perror("fcntl()/F_SETFL\n");exit(1);}for(done=0,cnt=0;!done;cnt++){sleep(1);if(gotSigio){gotSigio=0;/*Readallavailableinputuntilerror(probablyEAGAIN)orEOF*/while(read(STDIN_FILENO,&ch,1)>0&&!done){printf("cnt=%d;read%c\n",cnt,ch);done=ch=='#';}}}退出(0);}运行效果:./build/sigioacnt=0;readacnt=0;readabccnt=4;readacnt=4;readbcnt=4;readccnt=4;read#cnt=7;read#程序会先使能信号来驱动IO,然后在有时循环执行计数操作一个IO就绪信号,会去终端读取数据并打印出来,然后继续执行计数操作。5、知识扩展I/O多路复用、信号驱动I/O、epoll机制可以用来监控多个文件描述符。它们实际上并不执行I/O操作。当一个文件描述符处于就绪状态时,仍然需要传统的I/O系统调用来完成I/O操作。与I/O多路复用相比,信号驱动I/O在监控大量文件描述符时具有显着的性能优势,因为内核可以帮助进程记录正在监控的文件描述符列表。信号驱动I/O的缺点:信号的处理流程比较复杂;无法指定需要监视的事件类型。Linux专用的epoll是更好的选择。6.相关参考UNIX网络编程第1卷6.2I/O模型25SignalDrivenI/OLLinux-UNIX系统编程手册63OtherAlternativeI/OModelsLinuxHighPerformanceServerProgramming8.3I/OModelLinuxMultithreadedServerProgramming_使用muduoC++的IO模型网络图书馆7.4.1muduo
