免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 4962 | 回复: 13
打印 上一主题 下一主题

对于使用管道的一些理解 [复制链接]

论坛徽章:
1
荣誉版主
日期:2011-11-23 16:44:17
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2003-06-20 11:46 |只看该作者 |倒序浏览
看到如下这个连接的帖子,我觉得这是个很好的讨论主题,感觉想写点东西。因此就按照网友andyY和gadfly版主的思路继续写了下去。
http://www.chinaunix.net/forum/viewtopic.php?t=93300

1、首先看看正常的情况。

测试代码如下

  1. #include <stdio.h>;
  2. #include <unistd.h>;
  3. #include <sys/types.h>;
  4. #include <errno.h>;
  5. #define  WR 100000

  6. int main(void)
  7. {
  8.     char wrline[WR];
  9.     int fd[2];
  10.     int res;
  11.     pid_t pid;

  12.     memset(wrline,'K',WR);

  13.     if(pipe(fd) < 0 ) {
  14.         (void)fprintf(stderr,"pipe

  15. error:errno[%d],errmsg[%s]\n",errno,strerror(errno));
  16.         exit(-1);
  17.     }

  18.     pid=fork();
  19.     if(pid==0)
  20.     {
  21.         close(fd[1]);
  22.         sleep(20);
  23.         while(1) {
  24.              if( ( res=read(fd[0],wrline,WR-1) ) < 0 ) {
  25.                  (void)fprintf(stderr,"read

  26. error:errno[%d],errmsg[%s]!\n",errno
  27. ,strerror(errno));
  28.                  break;
  29.              }
  30.              (void)fprintf(stderr,"child[%d] process read: %d\n",getpid(),res);
  31.         }
  32.         exit(0);
  33.     }
  34.     else if( pid >; 0 ) {
  35.          close(fd[0]);
  36.          if( ( res=write(fd[1],wrline,WR-1) ) < 0 ) {
  37.              (void)fprintf(stderr,"write

  38. error:errno[%d],errmsg[%s]!\n",errno,str
  39. error(errno));
  40.          }
  41.          (void)fprintf(stderr,"parent[%d] process write: %d\n",getpid(),res);
  42.          pause();
  43.     } else {
  44.          (void)fprintf(stderr,"fork

  45. error:errno[%d],errmsg[%s]!\n",errno,strerro
  46. r(errno));
  47.          exit(-1);
  48.     }
  49.     exit(0);
  50. }
复制代码

如上代码安排的属于正常情况,在pipe后,父进程关闭pipe读的一端,子进程关闭写的一端。

然后父进程写10万字节的数据到pipe。只要内核存储器条件满足,可以写足够大的数据量,例

如100万。父进称写成功后调用pause()是有目的的。这一点将在下面的测试中说明。

我们让子进程sleep(20)的目的是为了保证让父进程写管道顺利完成(所谓的顺利是考虑到写管

道时的数据可能超过了内核参数PIPE_BUF规定的管道缓存区的大小,这时候写的动作往往由内核

分为多次完成)。

代码很简单,不作过多介绍,下面我们看看在不同的系统上面的测试结果,遗憾的是,我这里

的Sun OS环境损坏了,只能测试hp,aix,sco上面的情况。

(1)、在hp-11上面测试结果如下:

  1. child[1568] process read: 8192
  2. child[1568] process read: 8192
  3. child[1568] process read: 8192
  4. child[1568] process read: 8192
  5. child[1568] process read: 8192
  6. child[1568] process read: 8192
  7. child[1568] process read: 8192
  8. child[1568] process read: 8192
  9. child[1568] process read: 8192
  10. child[1568] process read: 8192
  11. child[1568] process read: 8192
  12. child[1568] process read: 8192
  13. child[1568] process read: 1696
  14. parent[1567] process write: 100000
复制代码

我们看到在hp-11上,pipe缓存设置为8192.父进程按照pipe缓存的大小分为多次将10万字节

的数据成功写入pipe。


(2)、在aix5上面的测试结果如下:

  1. child[48770] process read: 32768
  2. child[48770] process read: 32768
  3. child[48770] process read: 32768
  4. child[48770] process read: 1696
  5. parent[48278] process write: 100000
复制代码

我们看到aix5的管道缓存大小为32768。这是int的上限值。


(3)、SCO unix 5.05上面的测试结果如下:

  1. child[1110] process read: 8192
  2. child[1110] process read: 8192
  3. child[1110] process read: 8192
  4. child[1110] process read: 8192
  5. child[1110] process read: 8192
  6. child[1110] process read: 8192
  7. child[1110] process read: 8192
  8. child[1110] process read: 8192
  9. child[1110] process read: 8192
  10. child[1110] process read: 8192
  11. child[1110] process read: 8192
  12. child[1110] process read: 8192
  13. parent[1109] process write: 99999
  14. child[1110] process read: 1695
复制代码

我们看到SCO5.05系统提供的管道缓存大小为8192.

总结:

综合以上三个系统来看,我们发现write数据到pipe,并不阻塞,不论这些数据有没有被其它的

进程读走。为了保证write彻底的将所有数据写完,可以让子进程sleep更长的时间。往一个管

道中写数据受到系统PIPE_BUF的限制。

2、写一个读端已经被关闭的管道。

测是代码如下:

  1. #include <stdio.h>;
  2. #include <unistd.h>;
  3. #include <sys/types.h>;
  4. #include <errno.h>;
  5. #include <signal.h>;
  6. #define  WR 100000

  7. void sig_pipe( int signo )
  8. {
  9.     (void)fprintf(stderr,"signal[%d] is capture.\n", signo );
  10. }
  11. int main(void)
  12. {
  13.     char wrline[WR];
  14.     int fd[2];
  15.     int res;
  16.     pid_t pid;

  17.     memset(wrline,'K',sizeof(wrline));

  18.     if(pipe(fd) < 0 ) {
  19.         (void)fprintf(stderr,"pipe

  20. error:errno[%d],errmsg[%s]\n",errno,strerror(
  21. errno));
  22.         exit(-1);
  23.     }
  24.     signal(SIGPIPE,sig_pipe);
  25.     pid=fork();
  26.     if(pid==0)
  27.     {
  28.         close(fd[0]);/* 我们特意关闭管道的读端,也可以用exit(0)让子进程直接退出 */
  29.         close(fd[1]);
  30.         while(1) {
  31.              if( ( res=read(fd[0],wrline,WR-1) ) < 0 ) {
  32.                  (void)fprintf(stderr,"read

  33. error:errno[%d],errmsg[%s]!\n",errno
  34. ,strerror(errno));
  35.                  break;
  36.              }
  37.              (void)fprintf(stderr,"child[%d] process read: %d\n",getpid(),res);
  38.         }
  39.         exit(0);
  40.     }
  41.     else if( pid >; 0 ) {
  42.          close(fd[0]);
  43.          sleep(5);
  44.          if((res=write(fd[1],wrline,WR)) < 0 ) {
  45.              (void)fprintf(stderr,"write

  46. error:errno[%d],errmsg[%s]!\n",errno,st
  47. rerror(errno));
  48.          }
  49.          (void)fprintf(stderr,"parent[%d] process write: %d\n",getpid(),res);
  50.          pause();
  51.     } else {
  52.          (void)fprintf(stderr,"fork

  53. error:errno[%d],errmsg[%s]!\n",errno,strerro
  54. r(errno));
  55.          exit(-1);
  56.     }
  57.     exit(0);
  58. }
复制代码


如上的代码,我们想办法让管道的读端关闭。一般的可以直接调用close便可,如果子进程调用

exit()也可以,因为我们知道进程退出后其打开的描述字被关闭,管道便是其中的一种,因此

可以达到相同的目的。这段解释也说明了为什么第一个示例代码用pause()二不用exit()的原

因。
我们让父进程sleep(5)的目的时保证在write的时候管道的读端已经被关闭。
signal(SIGPIPE,sig_pipe);语句用来在fork之前设置对SIGPIPE信号处理程序。

信号处理程序sig_pipe很简单,仅仅想标准输出打印一条消息,以便我们能够知道进程中发生

了SIGPIPE信号。

下面看看不同系统的表现形式:

在hp-11、aix5、sco 5.05上面的我们得到相同的输出,如下所示:

  1. read error:errno[9],errmsg[Bad file number]!
  2. signal[13] is capture.
  3. write error:errno[32],errmsg[Broken pipe]!
  4. 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、读一个写端已经被关闭的管道。
测试代码如下:

  1. #include <stdio.h>;
  2. #include <unistd.h>;
  3. #include <sys/types.h>;
  4. #include <errno.h>;
  5. #define  WR 100000

  6. int main(void)
  7. {
  8.     char wrline[WR];
  9.     int fd[2];
  10.     int res;
  11.     pid_t pid;

  12.     memset(wrline,'K',sizeof(wrline));

  13.     if(pipe(fd) < 0 ) {
  14.         (void)fprintf(stderr,"pipe

  15. error:errno[%d],errmsg[%s]\n",errno,strerror(
  16. errno));
  17.         exit(-1);
  18.     }

  19.     pid=fork();
  20.     if(pid==0)
  21.     {
  22.         close(fd[1]);
  23.         sleep(5);
  24.         while(1) {
  25.              if( ( res=read(fd[0],wrline,WR) ) <= 0 ) {
  26.                  (void)fprintf(stderr,"read

  27. error:errno[%d],errmsg[%s]!\n",errno
  28. ,strerror(errno));
  29.                  (void)fprintf(stderr,"read: %d\n",res);
  30.                  break;
  31.              }
  32.              (void)fprintf(stderr,"child[%d] process read: %d\n",getpid(),res);
  33.         }
  34.         exit(0);
  35.     }
  36.     else if( pid >; 0 ) {
  37.          close(fd[0]);
  38.          if( ( res=write(fd[1],wrline,WR)  ) < 0 ) {
  39.              (void)fprintf(stderr,"write

  40. error:errno[%d],errmsg[%s]!\n",errno,st
  41. rerror(errno));
  42.          }
  43.          (void)fprintf(stderr,"parent[%d] process write: %d\n",getpid(),res);
  44.          close(fd[1]);
  45.     } else {
  46.          (void)fprintf(stderr,"fork

  47. error:errno[%d],errmsg[%s]!\n",errno,strerro
  48. r(errno));
  49.          exit(-1);
  50.     }
  51.     exit(0);
  52. }
复制代码


如上的代码,我们让子进程sleep(5)的目的是保证让父进程写完数据后关闭管道后子进程在去

读取pipe。
父进程中close(fd[1])可以用exit(0)代替。作用是相同的。

我们在hp-11/aix5/SCO5.05上面看到了相似的输出过程:

  1. child[2004] process read: 8192
  2. child[2004] process read: 8192
  3. child[2004] process read: 8192
  4. child[2004] process read: 8192
  5. child[2004] process read: 8192
  6. child[2004] process read: 8192
  7. child[2004] process read: 8192
  8. child[2004] process read: 8192
  9. child[2004] process read: 8192
  10. child[2004] process read: 8192
  11. child[2004] process read: 8192
  12. child[2004] process read: 8192
  13. child[2004] process read: 1696
  14. parent[2003] process write: 100000
  15. read error:errno[0],errmsg[Error 0]!
  16. 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

论坛徽章:
1
荣誉版主
日期:2011-11-23 16:44:17
2 [报告]
发表于 2003-06-20 13:28 |只看该作者

对于使用管道的一些理解

关于1.
原帖由 "蓝色键盘" 发表:

综合以上三个系统来看,我们发现write数据到pipe,并不阻塞,不论这些数据有没有被其它的

进程读走。为了保证write彻底的将所有数据写完,可以让子进程sleep更长的时间。往一个管

道中写数据受到系统PIPE_BUF的限制。

不阻塞是什么意思?是我理解错了么?如果不阻塞,恐怕应该是parent先打印这行吧
parent[2003] process write: 100000     

从结果来看,应该说是阻塞的,每次写入的数据,是受限于PIPE_BUF

论坛徽章:
1
荣誉版主
日期:2011-11-23 16:44:17
3 [报告]
发表于 2003-06-20 13:44 |只看该作者

对于使用管道的一些理解

至于2,补充一点:

先关闭读端再写的情况下,是这种结论。

但是以下这个帖子,
http://www.chinaunix.net/forum/viewtopic.php?t=93300
情况还不一样。引用我的理解:
原帖由 "gadfly" 发表:

如果管道是在写的过程中关闭的,会返回成功的个数;如果在关闭后写,就会报pipe broken错,如果不捕捉或不阻塞或忽略这个SIGPIPE信号,缺省情况下,进程会直接退出,而不是write返回-1。

论坛徽章:
1
荣誉版主
日期:2011-11-23 16:44:17
4 [报告]
发表于 2003-06-20 13:54 |只看该作者

对于使用管道的一些理解

至于3,其实是1的延深。

还是基于我对测试1的理解,在最后一个PIPE_BUF大小的数据没读之前,父进程是阻塞,写,阻塞,写....的过程,也就是说实际上读剩最后的PIPE_BUF之后数据的时候,parent才写完,关闭写端。

但是这并不影响键盘兄关于这个问题的结论,
原帖由 "蓝色键盘" 发表:

尽管管道的写端已经被关闭,然而在管道读端可用的情况下,内核保证管道中的数

据仍然能够被读取。


请各位指正。

论坛徽章:
0
5 [报告]
发表于 2003-06-20 14:06 |只看该作者

对于使用管道的一些理解

原帖由 "gadfly" 发表:
ocess write: 100000     

从结果来看,应该说是阻塞的,每次写入的数据,是受限于PIPE_BUF

同意!!!

论坛徽章:
0
6 [报告]
发表于 2003-06-20 14:06 |只看该作者

对于使用管道的一些理解

应该是阻塞的吧!!!

"如果不阻塞,恐怕应该是parent先打印这行吧
parent[2003] process write: 100000
"     

论坛徽章:
0
7 [报告]
发表于 2003-06-20 14:10 |只看该作者

对于使用管道的一些理解

总结的好呀!!!
我昨天晚上也试了一下几种情况
发现自从到了chinaunix,我开始思考了!!!

论坛徽章:
1
荣誉版主
日期:2011-11-23 16:44:17
8 [报告]
发表于 2003-06-20 14:11 |只看该作者

对于使用管道的一些理解

原帖由 "shaoyijun" 发表:
总结的好呀!!!
我昨天晚上也试了一下几种情况
发现自从到了chinaunix,我开始思考了!!!
   

那共享你的结论亚。

论坛徽章:
0
9 [报告]
发表于 2003-06-20 15:12 |只看该作者

对于使用管道的一些理解

应该是阻塞
因为文件描述符初始时都是阻塞了
除非使用fcntl或是其它函数改变端口状态

论坛徽章:
1
荣誉版主
日期:2011-11-23 16:44:17
10 [报告]
发表于 2003-06-20 16:26 |只看该作者

对于使用管道的一些理解

讨论的相当好,非常感谢各位,尤其是gadfly!当然,gadfly的回复是正确的。


看来,我们的理解是一致的,只是我对write的阻塞理解和大家存在差别,遗憾的是,我在总结中没有写上这种情况。

我总结的如下:

  1. 综合以上三个系统来看,我们发现write数据到pipe,并不阻塞,不论这些数据有没有被其它的进程读走。为了保证write彻底的将所有数据写完,可以让子进程sleep更长的时间。往一个管道中写数据受到系统PIPE_BUF的限制。
复制代码


如果定义宏WR<=PIPE_BUF的话,我们会看到,write会马上成功返回,并不阻塞。

实施上,正真的原因在于:

1、如果write的数据量>IPE_BUF的话,write成功写的数据为PIPE_BUF。这时候只有写入的数据被读走。write才能继续写剩下的数据,我想大家认为的阻塞便是这种情况吧。但我总觉得理解为阻塞似乎不是很精确。不过也只能这么称呼。

2、如果write的数据量<=PIPE_BUF的话,write成功写入数据后返回。不会阻塞。而不管写入的数据有没有被读走。

欢迎指正!
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP