- 论坛徽章:
- 1
|
看到如下这个连接的帖子,我觉得这是个很好的讨论主题,感觉想写点东西。因此就按照网友andyY和gadfly版主的思路继续写了下去。
http://www.chinaunix.net/forum/viewtopic.php?t=93300
1、首先看看正常的情况。
测试代码如下
- #include <stdio.h>;
- #include <unistd.h>;
- #include <sys/types.h>;
- #include <errno.h>;
- #define WR 100000
- int main(void)
- {
- char wrline[WR];
- int fd[2];
- int res;
- pid_t pid;
- memset(wrline,'K',WR);
- if(pipe(fd) < 0 ) {
- (void)fprintf(stderr,"pipe
- error:errno[%d],errmsg[%s]\n",errno,strerror(errno));
- exit(-1);
- }
- pid=fork();
- if(pid==0)
- {
- close(fd[1]);
- sleep(20);
- while(1) {
- if( ( res=read(fd[0],wrline,WR-1) ) < 0 ) {
- (void)fprintf(stderr,"read
- error:errno[%d],errmsg[%s]!\n",errno
- ,strerror(errno));
- break;
- }
- (void)fprintf(stderr,"child[%d] process read: %d\n",getpid(),res);
- }
- exit(0);
- }
- else if( pid >; 0 ) {
- close(fd[0]);
- if( ( res=write(fd[1],wrline,WR-1) ) < 0 ) {
- (void)fprintf(stderr,"write
- error:errno[%d],errmsg[%s]!\n",errno,str
- error(errno));
- }
- (void)fprintf(stderr,"parent[%d] process write: %d\n",getpid(),res);
- pause();
- } else {
- (void)fprintf(stderr,"fork
- error:errno[%d],errmsg[%s]!\n",errno,strerro
- r(errno));
- exit(-1);
- }
- exit(0);
- }
复制代码
如上代码安排的属于正常情况,在pipe后,父进程关闭pipe读的一端,子进程关闭写的一端。
然后父进程写10万字节的数据到pipe。只要内核存储器条件满足,可以写足够大的数据量,例
如100万。父进称写成功后调用pause()是有目的的。这一点将在下面的测试中说明。
我们让子进程sleep(20)的目的是为了保证让父进程写管道顺利完成(所谓的顺利是考虑到写管
道时的数据可能超过了内核参数PIPE_BUF规定的管道缓存区的大小,这时候写的动作往往由内核
分为多次完成)。
代码很简单,不作过多介绍,下面我们看看在不同的系统上面的测试结果,遗憾的是,我这里
的Sun OS环境损坏了,只能测试hp,aix,sco上面的情况。
(1)、在hp-11上面测试结果如下:
- child[1568] process read: 8192
- child[1568] process read: 8192
- child[1568] process read: 8192
- child[1568] process read: 8192
- child[1568] process read: 8192
- child[1568] process read: 8192
- child[1568] process read: 8192
- child[1568] process read: 8192
- child[1568] process read: 8192
- child[1568] process read: 8192
- child[1568] process read: 8192
- child[1568] process read: 8192
- child[1568] process read: 1696
- parent[1567] process write: 100000
复制代码
我们看到在hp-11上,pipe缓存设置为8192.父进程按照pipe缓存的大小分为多次将10万字节
的数据成功写入pipe。
(2)、在aix5上面的测试结果如下:
- child[48770] process read: 32768
- child[48770] process read: 32768
- child[48770] process read: 32768
- child[48770] process read: 1696
- parent[48278] process write: 100000
复制代码
我们看到aix5的管道缓存大小为32768。这是int的上限值。
(3)、SCO unix 5.05上面的测试结果如下:
- child[1110] process read: 8192
- child[1110] process read: 8192
- child[1110] process read: 8192
- child[1110] process read: 8192
- child[1110] process read: 8192
- child[1110] process read: 8192
- child[1110] process read: 8192
- child[1110] process read: 8192
- child[1110] process read: 8192
- child[1110] process read: 8192
- child[1110] process read: 8192
- child[1110] process read: 8192
- parent[1109] process write: 99999
- child[1110] process read: 1695
复制代码
我们看到SCO5.05系统提供的管道缓存大小为8192.
总结:
综合以上三个系统来看,我们发现write数据到pipe,并不阻塞,不论这些数据有没有被其它的
进程读走。为了保证write彻底的将所有数据写完,可以让子进程sleep更长的时间。往一个管
道中写数据受到系统PIPE_BUF的限制。
2、写一个读端已经被关闭的管道。
测是代码如下:
- #include <stdio.h>;
- #include <unistd.h>;
- #include <sys/types.h>;
- #include <errno.h>;
- #include <signal.h>;
- #define WR 100000
- void sig_pipe( int signo )
- {
- (void)fprintf(stderr,"signal[%d] is capture.\n", signo );
- }
- int main(void)
- {
- char wrline[WR];
- int fd[2];
- int res;
- pid_t pid;
- memset(wrline,'K',sizeof(wrline));
- if(pipe(fd) < 0 ) {
- (void)fprintf(stderr,"pipe
- error:errno[%d],errmsg[%s]\n",errno,strerror(
- errno));
- exit(-1);
- }
- signal(SIGPIPE,sig_pipe);
- pid=fork();
- if(pid==0)
- {
- close(fd[0]);/* 我们特意关闭管道的读端,也可以用exit(0)让子进程直接退出 */
- close(fd[1]);
- while(1) {
- if( ( res=read(fd[0],wrline,WR-1) ) < 0 ) {
- (void)fprintf(stderr,"read
- error:errno[%d],errmsg[%s]!\n",errno
- ,strerror(errno));
- break;
- }
- (void)fprintf(stderr,"child[%d] process read: %d\n",getpid(),res);
- }
- exit(0);
- }
- else if( pid >; 0 ) {
- close(fd[0]);
- sleep(5);
- if((res=write(fd[1],wrline,WR)) < 0 ) {
- (void)fprintf(stderr,"write
- error:errno[%d],errmsg[%s]!\n",errno,st
- rerror(errno));
- }
- (void)fprintf(stderr,"parent[%d] process write: %d\n",getpid(),res);
- pause();
- } else {
- (void)fprintf(stderr,"fork
- error:errno[%d],errmsg[%s]!\n",errno,strerro
- r(errno));
- exit(-1);
- }
- exit(0);
- }
复制代码
如上的代码,我们想办法让管道的读端关闭。一般的可以直接调用close便可,如果子进程调用
exit()也可以,因为我们知道进程退出后其打开的描述字被关闭,管道便是其中的一种,因此
可以达到相同的目的。这段解释也说明了为什么第一个示例代码用pause()二不用exit()的原
因。
我们让父进程sleep(5)的目的时保证在write的时候管道的读端已经被关闭。
signal(SIGPIPE,sig_pipe);语句用来在fork之前设置对SIGPIPE信号处理程序。
信号处理程序sig_pipe很简单,仅仅想标准输出打印一条消息,以便我们能够知道进程中发生
了SIGPIPE信号。
下面看看不同系统的表现形式:
在hp-11、aix5、sco 5.05上面的我们得到相同的输出,如下所示:
- read error:errno[9],errmsg[Bad file number]!
- signal[13] is capture.
- write error:errno[32],errmsg[Broken pipe]!
- parent[1848] process write: -1
复制代码
可以看出,当fd[0]被彻底关闭后(彻底是指父子进程都关闭的情况,因为fork后,子进程继承
了父进程的fd[0]和fd[1]。因此彻底的关闭需要父子进程同时close).
子进程从管道读时产生错误Bad file number。表明fd[0]已经不是有效的描述字。需要说明的
是:如果子进程用exit(0)测试这种情况,这里将不会看到read输出信息的。
父进程write时,产生错误Broken
pipe,也就是EPIPE。同时,我们的信号处理程序显示signal[13] is capture.表明确实在向
读端关闭的管道中写时(这里时父进程写)产生了信号_SIGPIPE(也就是SIGPIPE,这往往定义
为一个宏)。
3、读一个写端已经被关闭的管道。
测试代码如下:
- #include <stdio.h>;
- #include <unistd.h>;
- #include <sys/types.h>;
- #include <errno.h>;
- #define WR 100000
- int main(void)
- {
- char wrline[WR];
- int fd[2];
- int res;
- pid_t pid;
- memset(wrline,'K',sizeof(wrline));
- if(pipe(fd) < 0 ) {
- (void)fprintf(stderr,"pipe
- error:errno[%d],errmsg[%s]\n",errno,strerror(
- errno));
- exit(-1);
- }
- pid=fork();
- if(pid==0)
- {
- close(fd[1]);
- sleep(5);
- while(1) {
- if( ( res=read(fd[0],wrline,WR) ) <= 0 ) {
- (void)fprintf(stderr,"read
- error:errno[%d],errmsg[%s]!\n",errno
- ,strerror(errno));
- (void)fprintf(stderr,"read: %d\n",res);
- break;
- }
- (void)fprintf(stderr,"child[%d] process read: %d\n",getpid(),res);
- }
- exit(0);
- }
- else if( pid >; 0 ) {
- close(fd[0]);
- if( ( res=write(fd[1],wrline,WR) ) < 0 ) {
- (void)fprintf(stderr,"write
- error:errno[%d],errmsg[%s]!\n",errno,st
- rerror(errno));
- }
- (void)fprintf(stderr,"parent[%d] process write: %d\n",getpid(),res);
- close(fd[1]);
- } else {
- (void)fprintf(stderr,"fork
- error:errno[%d],errmsg[%s]!\n",errno,strerro
- r(errno));
- exit(-1);
- }
- exit(0);
- }
复制代码
如上的代码,我们让子进程sleep(5)的目的是保证让父进程写完数据后关闭管道后子进程在去
读取pipe。
父进程中close(fd[1])可以用exit(0)代替。作用是相同的。
我们在hp-11/aix5/SCO5.05上面看到了相似的输出过程:
- child[2004] process read: 8192
- child[2004] process read: 8192
- child[2004] process read: 8192
- child[2004] process read: 8192
- child[2004] process read: 8192
- child[2004] process read: 8192
- child[2004] process read: 8192
- child[2004] process read: 8192
- child[2004] process read: 8192
- child[2004] process read: 8192
- child[2004] process read: 8192
- child[2004] process read: 8192
- child[2004] process read: 1696
- parent[2003] process write: 100000
- read error:errno[0],errmsg[Error 0]!
- read: 0
复制代码
三个系统中不同的仅仅是缓存大小的差别。最后read都返回0。
我们看到,尽管管道的写端已经被关闭,然而在管道读端可用的情况下,内核保证管道中的数
据仍然能够被读取。
当管道中的数据被读取完后,read返回0。返回0并不标识一个错误,这往往有内核给调用者返
回一个文件结束符来标志。posix建议对于这种情况,分为进程存在或不存在分别处理,但是,
在上面的三个系统的实现中,我没有看到对于这种差别的处理。验证的的方法是让父进程close
(fd【1】)后pause()。
总结:
管道在实际的应用中使用的比较广泛,对于pipe而言,尤其是在父子进程之间使用的较多,实
际上,pipe对于没有亲缘关系的进程来说,实现起来比较麻烦。没什么意义。希望各位朋友连
接管道的特性的话,看看上面的浅薄分析。或许对大家有用。
最近比较忙,有时间的话,我会适当的找些主题聊些自己的感想,欢迎各位朋友指正!
蓝色键盘
2003.6.20
另外,我觉得如下的这个帖子提出的问题相当的有讨论的价值,哪位有时间首先整理一下,或者说说自己的看法,大家一起讨论一下。
http://www.chinaunix.net/forum/viewtopic.php?t=94621 |
|