来自:jiffes > 馆藏分类
配色: 字号:
进程通信—管道
2016-05-31 | 阅:  转:  |  分享 
  
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;}
献花(0)
+1
(本文系jiffes首藏)