1、管道概述及相关API应用1.1管道相关的关键概念管道是Linux支持的最初UnixIPC形式之一,具有以下特点:管道是半双工的,数 据只能向一个方向流动;需要双方通信时,需要建立起两个管道;只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);单独构成一种独立 的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统 ,并且只存在与内存中。数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并 且每次都是从缓冲区的头部读出数据。1.2管道的创建:#includeintpipe(intfd[2])该 函数创建的管道的两端处于一个进程中间,在实际应用中没有太大意义,因此,一个进程在由pipe()创建管道后,一般再fork一个子进程 ,然后通过管道实现父子进程间的通信(因此也不难推出,只要两个进程中存在亲缘关系,这里的亲缘关系指的是具有共同的祖先,都可以采用管道 方式来进行通信)。1.3管道的读写规则:管道两端可分别用描述字fd[0]以及fd[1]来描述,需要注意的是,管道的两端是固定了任务 的。即一端只能用于读,由描述字fd[0]表示,称其为管道读端;另一端则只能用于写,由描述字fd[1]来表示,称其为管道写端。如果试 图从管道写端读取数据,或者向管道读端写入数据都将导致错误发生。一般文件的I/O函数都可以用于管道,如close、read、writ e等等。从管道中读取数据:如果管道的写端不存在,则认为已经读到了数据的末尾,读函数返回的读出字节数为0;当管道的写端存在时,如果请 求的字节数目大于PIPE_BUF,则返回管道中现有的数据字节数,如果请求的字节数目不大于PIPE_BUF,则返回管道中现有数据字节 数(此时,管道中数据量小于请求的数据量);或者返回请求的字节数(此时,管道中数据量不小于请求的数据量)。注:(PIPE_BUF在i nclude/linux/limits.h中定义,不同的内核版本可能会有所不同。Posix.1要求PIPE_BUF至少为512字节 ,redhat7.2中为4096)。关于管道的读规则验证:/readtest.c /#include#include#includerrno.h>main(){intpipe_fd[2];pid_tpid;charr_buf[100];charw_buf [4];charp_wbuf;intr_num;intcmd;memset(r_buf,0,sizeof(r_buf)); memset(w_buf,0,sizeof(r_buf));p_wbuf=w_buf;if(pipe(pipe_fd)<0){pr intf("pipecreateerror\n");return-1;}if((pid=fork())==0){printf ("\n");close(pipe_fd[1]);sleep(3);//确保父进程关闭写端r_num=read(pipe_fd[ 0],r_buf,100);printf("readnumis%dthedatareadfromthepip eis%d\n",r_num,atoi(r_buf));close(pipe_fd[0]);exit();}elseif(p id>0){close(pipe_fd[0]);//readstrcpy(w_buf,"111");if(write(pipe_f d[1],w_buf,4)!=-1)printf("parentwriteover\n");close(pipe_fd[1]) ;//writeprintf("parentclosefd[1]over\n");sleep(10);}}/ 程序输出结果:parentwr iteoverparentclosefd[1]overreadnumis4thedatarea dfromthepipeis111附加结论:管道写端关闭后,写入的数据将一直存在,直到读出为止. /向管道中写入数据:向管道中写入 数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据, 那么写操作将一直阻塞。?注:只有在管道的读端存在时,向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的SIFP IPE信号,应用程序可以处理该信号,也可以忽略(默认动作则是应用程序终止)。对管道的写规则的验证1:写端对读端存在的依赖性#inc lude#includemain(){intpipe_fd[2];pid_t pid;charr_buf[4];charw_buf;intwritenum;intcmd;memset(r_buf,0 ,sizeof(r_buf));if(pipe(pipe_fd)<0){printf("pipecreateerror\n") ;return-1;}if((pid=fork())==0){close(pipe_fd[0]);close(pipe_fd[1 ]);sleep(10);exit();}elseif(pid>0){sleep(1);//等待子进程完成关闭读端的操作cl ose(pipe_fd[0]);//writew_buf="111";if((writenum=write(pipe_fd[1], w_buf,4))==-1)printf("writetopipeerror\n");elseprintf("thebyt eswritetopipeis%d\n",writenum);close(pipe_fd[1]);}}则输出结果为: Brokenpipe,原因就是该管道以及它的所有fork()产物的读端都已经被关闭。如果在父进程中保留读端,即在写完pipe后 ,再关闭父进程的读端,也会正常写入pipe,读者可自己验证一下该结论。因此,在向管道写入数据时,至少应该存在某一个进程,其中管道读 端没有被关闭,否则就会出现上述错误(管道断裂,进程收到了SIGPIPE信号,默认动作是进程终止)对管道的写规则的验证2:linux 不保证写管道的原子性验证#include#include#includerno.h>main(intargc,charargv){intpipe_fd[2];pid_tpid;charr_b uf[4096];charw_buf[40962];intwritenum;intrnum;memset(r_buf,0, sizeof(r_buf));if(pipe(pipe_fd)<0){printf("pipecreateerror\n"); return-1;}if((pid=fork())==0){close(pipe_fd[1]);while(1){sleep(1 );rnum=read(pipe_fd[0],r_buf,1000);printf("child:readnumis%d\n ",rnum);}close(pipe_fd[0]);exit();}elseif(pid>0){close(pipe_fd[0 ]);//writememset(r_buf,0,sizeof(r_buf));if((writenum=write(pipe_f d[1],w_buf,1024))==-1)printf("writetopipeerror\n");elseprintf( "thebyteswritetopipeis%d\n",writenum);writenum=write(pipe _fd[1],w_buf,4096);close(pipe_fd[1]);}}输出结果:thebyteswritetopi pe1000thebyteswritetopipe1000//注意,此行输出说明了写入的非原子性thebytes writetopipe1000thebyteswritetopipe1000thebyteswriteto pipe1000thebyteswritetopipe120//注意,此行输出说明了写入的非原子性thebyt eswritetopipe0thebyteswritetopipe0......结论:写入数目小于4096时写入 是非原子的!?如果把父进程中的两次写入字节数都改为5000,则很容易得出下面结论:?写入管道的数据量大于4096字节时,缓冲区的空 闲空间将被写入数据(补齐),直到写完所有数据为止,如果没有进程读数据,则一直阻塞。1.4管道应用实例:实例一:用于shell管道可 用于输入输出重定向,它将一个命令的输出直接定向到另一个命令的输入。比如,当在某个shell程序(Bourneshell或Csh ell等)键入who│wc-l后,相应shell程序将创建who以及wc两个进程和这两个进程间的管道。考虑下面的命令行:$kil l-l运行结果见?http://www.ibm.com/developerworks/cn/linux/l-ipc/part1 /附一。$kill-l|grepSIGRTMIN运行结果如下:30)SIGPWR31)SIGSYS32)SIGRT MIN33)SIGRTMIN+134)SIGRTMIN+235)SIGRTMIN+336)SIGRTMIN+437)SI GRTMIN+538)SIGRTMIN+639)SIGRTMIN+740)SIGRTMIN+841)SIGRTMIN+94 2)SIGRTMIN+1043)SIGRTMIN+1144)SIGRTMIN+1245)SIGRTMIN+1346)SI GRTMIN+1447)SIGRTMIN+1548)SIGRTMAX-1549)SIGRTMAX-14实例二:用于具有亲缘关 系的进程间通信下面例子给出了管道的具体应用,父进程通过管道发送一些命令给子进程,子进程解析命令,并根据命令作相应处理。#inclu de#includemain(){intpipe_fd[2];pid_tpi d;charr_buf[4];charw_buf[256];intchildexit=0;inti;intcmd;m emset(r_buf,0,sizeof(r_buf));if(pipe(pipe_fd)<0){printf("pipecre ateerror\n");return-1;}if((pid=fork())==0)//子进程:解析从管道中获取的命令,并作相 应的处理{printf("\n");close(pipe_fd[1]);sleep(2);while(!childexit){re ad(pipe_fd[0],r_buf,4);cmd=atoi(r_buf);if(cmd==0){printf("child: receivecommandfromparentover\nnowchildprocessexit\n");chi ldexit=1;}elseif(handle_cmd(cmd)!=0)return;sleep(1);}close(pipe _fd[0]);exit();}elseif(pid>0)//parent:sendcommandstochild{cl ose(pipe_fd[0]);w_buf[0]="003";w_buf[1]="005";w_buf[2]="777";w_bu f[3]="000";for(i=0;i<4;i++)write(pipe_fd[1],w_buf[i],4);close(pip e_fd[1]);}}//下面是子进程的命令处理函数(特定于应用):inthandle_cmd(intcmd){if((cmd <0)||(cmd>256))//supposechildonlysupport256commands{printf(" child:invalidcommand\n");return-1;}printf("child:thecmdfro mparentis%d\n",cmd);return0;}1.5管道的局限性管道的主要局限性正体现在它的特点上:只支持单 向数据流;只能用于具有亲缘关系的进程之间;没有名字;管道的缓冲区是有限的(管道制存在于内存中,在管道创建时,为缓冲区分配一个页面大 小);管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令、或记录) 等等;http://www.ibm.com/developerworks/cn/linux/l-ipc/part1/回页首2、有 名管道概述及相关API应用2.1有名管道相关的关键概念管道应用的一个重大限制是它没有名字,因此,只能用于具有亲缘关系的进程间通信 ,在有名管道(namedpipe或FIFO)提出后,该限制得到了克服。FIFO不同于管道之处在于它提供一个路径名与之关联,以FI FO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互 通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。值得注意的是,FIFO严格遵循 先进先出(firstinfirstout),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支 持诸如lseek()等文件定位操作。2.2有名管道的创建#include#includetat.h>intmkfifo(constcharpathname,mode_tmode)该函数的第一个参数是一个普 通的路径名,也就是创建后FIFO的名字。第二个参数与打开普通文件的open()函数中的mode参数相同。如果mkfifo的第一 个参数是一个已经存在的路径名时,会返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只 要调用打开FIFO的函数就可以了。一般文件的I/O函数都可以用于FIFO,如close、read、write等等。2.3有名管道的 打开规则有名管道比管道多了一个打开操作:open。FIFO的打开规则:如果当前打开操作是为读而打开FIFO时,若已经有相应进程为写 而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为写而打开该FIFO(当前打开操作设置了阻塞标志);或者, 成功返回(当前打开操作没有设置阻塞标志)。如果当前打开操作是为写而打开FIFO时,如果已经有相应进程为读而打开该FIFO,则当前打 开操作将成功返回;否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作设置了阻塞标志);或者,返回ENXIO错误(当前打 开操作没有设置阻塞标志)。对打开规则的验证参见?http://www.ibm.com/developerworks/cn/linu x/l-ipc/part1/附2。2.4有名管道的读写规则从FIFO中读取数据:约定:如果一个进程为了从FIFO中读取数据而阻塞打 开FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作。如果有进程写打开FIFO,且当前FIFO内没有数据,则对于设置了阻塞标 志的读操作来说,将一直阻塞。对于没有设置阻塞标志读操作来说则返回-1,当前errno值为EAGAIN,提醒以后再试。对于设置了阻塞 标志的读操作说,造成阻塞的原因有两种:当前FIFO内有数据,但有其它进程在读这些数据;另外就是FIFO内没有数据。解阻塞的原因则是 FIFO中有新的数据写入,不论信写入数据量的大小,也不论读操作请求多少数据量。读打开的阻塞标志只对本进程第一个读操作施加作用,如果 本进程内有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其它将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有 数据也一样(此时,读操作返回0)。如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞。注:如果FIFO中有数据,则设置了阻 塞标志的读操作不会因为FIFO中的字节数小于请求读的字节数而阻塞,此时,读操作会返回FIFO中现有的数据量。向FIFO中写入数据: 约定:如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作为设置了阻塞标志的写操作。对于设置了阻塞标志的 写操作:当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则 进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。当要写入的数据量大于PIPE_BUF时,linux将不再 保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。对于没有设置阻塞标 志的写操作:当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后 成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写;对FIFO读写规则的验证:下 面提供了两个对FIFO的读写程序,适当调节程序中的很少地方或者程序的命令行参数就可以对各种FIFO读写规则进行验证。程序1:写FI FO的程序#include#include#include>#include#defineFIFO_SERVER"/tmp/fifoserver"main(int argc,charargv)//参数为即将写入的字节数{intfd;charw_buf[40962];intreal _wnum;memset(w_buf,0,40962);if((mkfifo(FIFO_SERVER,O_CREAT|O_EXC L)<0)&&(errno!=EEXIST))printf("cannotcreatefifoserver\n");if(fd ==-1)if(errno==ENXIO)printf("openerror;noreadingprocess\n"); fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);//设置非阻塞标志//fd=open(FIF O_SERVER,O_WRONLY,0);//设置阻塞标志real_wnum=write(fd,w_buf,2048);if(re al_wnum==-1){if(errno==EAGAIN)printf("writetofifoerror;tryla ter\n");}elseprintf("realwritenumis%d\n",real_wnum);real_wnu m=write(fd,w_buf,5000);//5000用于测试写入字节大于4096时的非原子性//real_wnum=writ e(fd,w_buf,4096);//4096用于测试写入字节不大于4096时的原子性if(real_wnum==-1)if(er rno==EAGAIN)printf("trylater\n");}程序2:与程序1一起测试写FIFO的规则,第一个命令行参数是 请求从FIFO读出的字节数#include#include#include #include#defineFIFO_SERVER"/tmp/fifoserver"m ain(intargc,charargv){charr_buf[40962];intfd;intr_size; intret_size;r_size=atoi(argv[1]);printf("requredrealreadbyte s%d\n",r_size);memset(r_buf,0,sizeof(r_buf));fd=open(FIFO_SERVER ,O_RDONLY|O_NONBLOCK,0);//fd=open(FIFO_SERVER,O_RDONLY,0);//在此处可以 把读程序编译成两个不同版本:阻塞版本及非阻塞版本if(fd==-1){printf("open%sforreaderror \n");exit();}while(1){memset(r_buf,0,sizeof(r_buf));ret_size=read (fd,r_buf,r_size);if(ret_size==-1)if(errno==EAGAIN)printf("nodat aavlaible\n");printf("realreadbytes%d\n",ret_size);sleep(1);} pause();unlink(FIFO_SERVER);}程序应用说明:把读程序编译成两个不同版本:阻塞读版本:br以及非阻塞读版 本nbr把写程序编译成两个四个版本:非阻塞且请求写的字节数大于PIPE_BUF版本:nbwg非阻塞且请求写的字节数不大于PIPE_ BUF版本:版本nbw阻塞且请求写的字节数大于PIPE_BUF版本:bwg阻塞且请求写的字节数不大于PIPE_BUF版本:版本bw 下面将使用br、nbr、w代替相应程序中的阻塞读、非阻塞读验证阻塞写操作:当请求写入的数据量大于PIPE_BUF时的非原子性:nb r1000bwg当请求写入的数据量不大于PIPE_BUF时的原子性:nbr1000bw验证非阻塞写操作:当请求写入的数据量大于 PIPE_BUF时的非原子性:nbr1000nbwg请求写入的数据量不大于PIPE_BUF时的原子性:nbr1000nbw不管 写打开的阻塞标志是否设置,在请求写入的字节数大于4096时,都不保证写入的原子性。但二者有本质区别:对于阻塞写来说,写操作在写满F IFO的空闲区域后,会一直等待,直到写完所有数据为止,请求写入的数据最终都会写入FIFO;而非阻塞写则在写满FIFO的空闲区域后, 就返回(实际写入的字节数),所以有些数据最终不能够写入。对于读操作的验证则比较简单,不再讨论。2.5有名管道应用实例在验证了相应的 读写规则后,应用实例似乎就没有必要了。http://www.ibm.com/developerworks/cn/linux/l-i pc/part1/回页首小结:管道常用于两个方面:(1)在shell中时常会用到管道(作为输入输入的重定向),在这种应用方式下,管 道的创建对于用户来说是透明的;(2)用于具有亲缘关系的进程间通信,用户自己创建管道,并完成读写操作。FIFO可以说是管道的推广,克 服了管道无名字的限制,使得无亲缘关系的进程同样可以采用先进先出的通信机制进行通信。管道和FIFO的数据是字节流,应用程序之间必须事 先确定特定的传输"协议",采用传播具有特定意义的消息。要灵活应用管道及FIFO,理解它们的读写规则是关键。附1:kill-l的 运行结果,显示了当前系统支持的所有信号:1)SIGHUP2)SIGINT3)SIGQUIT4)SIGILL5)SI GTRAP6)SIGABRT7)SIGBUS8)SIGFPE9)SIGKILL10)SIGUSR111)SIGS EGV12)SIGUSR213)SIGPIPE14)SIGALRM15)SIGTERM17)SIGCHLD18)SIG CONT19)SIGSTOP20)SIGTSTP21)SIGTTIN22)SIGTTOU23)SIGURG24)SIG XCPU25)SIGXFSZ26)SIGVTALRM27)SIGPROF28)SIGWINCH29)SIGIO30)S IGPWR31)SIGSYS32)SIGRTMIN33)SIGRTMIN+134)SIGRTMIN+235)SIGRTM IN+336)SIGRTMIN+437)SIGRTMIN+538)SIGRTMIN+639)SIGRTMIN+740)S IGRTMIN+841)SIGRTMIN+942)SIGRTMIN+1043)SIGRTMIN+1144)SIGRTMIN +1245)SIGRTMIN+1346)SIGRTMIN+1447)SIGRTMIN+1548)SIGRTMAX-1549 )SIGRTMAX-1450)SIGRTMAX-1351)SIGRTMAX-1252)SIGRTMAX-1153)SIG RTMAX-1054)SIGRTMAX-955)SIGRTMAX-856)SIGRTMAX-757)SIGRTMAX-65 8)SIGRTMAX-559)SIGRTMAX-460)SIGRTMAX-361)SIGRTMAX-262)SIGRTM AX-163)SIGRTMAX除了在此处用来说明管道应用外,接下来的专题还要对这些信号分类讨论。附2:对FIFO打开规则的验证( 主要验证写打开对读打开的依赖性)#include#include#inclu de#include#defineFIFO_SERVER"/tmp/fifoserver"inthandle_client(char);main(intargc,charargv){intr_rd;intw_fd;pid_tpid;if((mkfifo(FIFO_SERVER,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))printf("cannotcreatefifoserver\n");handle_client(FIFO_SERVER);}inthandle_client(chararg){intret;ret=w_open(arg);switch(ret){case0:{printf("open%serror\n",arg);printf("noprocesshasthefifoopenforreading\n");return-1;}case-1:{printf("somethingwrongwithopenthefifoexceptforENXIO");return-1;}case1:{printf("openserverok\n");return1;}default:{printf("w_no_rreturn----\n");return0;}}unlink(FIFO_SERVER);}intw_open(chararg)//0openerrorfornoreading//-1openerrorforotherreasons//1openok{if(open(arg,O_WRONLY|O_NONBLOCK,0)==-1){if(errno==ENXIO){return0;}elsereturn-1;}return1;} |
|