对于使用管道的一些理解
发表于:2007-05-26来源:作者:点击数:
标签:
看到如下这个连接的帖子,我觉得这是个很好的讨论主题,感觉想写点东西。因此就按照网友andyY和gadfly版主的思路继续写了下去。 http://www.chinaunix.net/forum/viewtopic.php?t=93300 1、首先看看正常的情况。 测试代码如下 [code:1:a6839c86ed] #includes
看到如下这个连接的帖子,我觉得这是个很好的讨论主题,感觉想写点东西。因此就按照网友andyY和gadfly版主的思路继续写了下去。
http://www.chinaunix.net/forum/viewtopic.php?t=93300
1、首先看看正常的情况。
测试代码如下
[code:1:a6839c86ed]
#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);
}
[/code:1:a6839c86ed]
如上代码安排的属于正常情况,在pipe后,父进程关闭pipe读的一端,子进程关闭写的一端。
然后父进程写10万字节的数据到pipe。只要内核存储器条件满足,可以写足够大的数据量,例
如100万。父进称写成功后调用pause()是有目的的。这一点将在下面的测试中说明。
我们让子进程sleep(20)的目的是为了保证让父进程写管道顺利完成(所谓的顺利是考虑到写管
道时的数据可能超过了内核参数PIPE_BUF规定的管道缓存区的大小,这时候写的动作往往由内核
分为多次完成)。
代码很简单,不作过多介绍,下面我们看看在不同的系统上面的测试结果,遗憾的是,我这里
的Sun OS环境损坏了,只能测试hp,aix,sco上面的情况。
(1)、在hp-11上面测试结果如下:
[code:1:a6839c86ed]
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
[/code:1:a6839c86ed]
我们看到在hp-11上,pipe缓存设置为8192.父进程按照pipe缓存的大小分为多次将10万字节
的数据成功写入pipe。
(2)、在aix5上面的测试结果如下:
[code:1:a6839c86ed]
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
[/code:1:a6839c86ed]
我们看到aix5的管道缓存大小为32768。这是int的上限值。
(3)、SCO unix 5.05上面的测试结果如下:
[code:1:a6839c86ed]
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
[/code:1:a6839c86ed]
我们看到SCO5.05系统提供的管道缓存大小为8192.
总结:
综合以上三个系统来看,我们发现write数据到pipe,并不阻塞,不论这些数据有没有被其它的
进程读走。为了保证write彻底的将所有数据写完,可以让子进程sleep更长的时间。往一个管
道中写数据受到系统PIPE_BUF的限制。
2、写一个读端已经被关闭的管道。
测是代码如下:
[code:1:a6839c86ed]
#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);
}
[/code:1:a6839c86ed]
如上的代码,我们想办法让管道的读端关闭。一般的可以直接调用close便可,如果子进程调用
exit()也可以,因为我们知道进程退出后其打开的描述字被关闭,管道便是其中的一种,因此
可以达到相同的目的。这段解释也说明了为什么第一个示例代码用pause()二不用exit()的原
因。
我们让父进程sleep(5)的目的时保证在write的时候管道的读端已经被关闭。
signal(SIGPIPE,sig_pipe);语句用来在fork之前设置对SIGPIPE信号处理程序。
信号处理程序sig_pipe很简单,仅仅想标准输出打印一条消息,以便我们能够知道进程中发生
了SIGPIPE信号。
下面看看不同系统的表现形式:
在hp-11、aix5、sco 5.05上面的我们得到相同的输出,如下所示:
[code:1:a6839c86ed]
read error:errno[9],errmsg[Bad file number]!
signal[13] is capture.
write error:errno[32],errmsg[Broken pipe]!
parent[1848] process write: -1
[/code:1:a6839c86ed]
可以看出,当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、读一个写端已经被关闭的管道。
测试代码如下:
[code:1:a6839c86ed]
#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);
}
[/code:1:a6839c86ed]
如上的代码,我们让子进程sleep(5)的目的是保证让父进程写完数据后关闭管道后子进程在去
读取pipe。
父进程中close(fd[1])可以用exit(0)代替。作用是相同的。
我们在hp-11/aix5/SCO5.05上面看到了相似的输出过程:
[code:1:a6839c86ed]
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
[/code:1:a6839c86ed]
三个系统中不同的仅仅是缓存大小的差别。最后read都返回0。
我们看到,尽管管道的写端已经被关闭,然而在管道读端可用的情况下,内核保证管道中的数
据仍然能够被读取。
当管道中的数据被读取完后,read返回0。返回0并不标识一个错误,这往往有内核给调用者返
回一个文件结束符来标志。posix建议对于这种情况,分为进程存在或不存在分别处理,但是,
在上面的三个系统的实现中,我没有看到对于这种差别的处理。验证的的方法是让父进程close
(fd【1】)后pause()。
总结:
管道在实际的应用中使用的比较广泛,对于pipe而言,尤其是在父子进程之间使用的较多,实
际上,pipe对于没有亲缘关系的进程来说,实现起来比较麻烦。没什么意义。希望各位朋友连
接管道的特性的话,看看上面的浅薄分析。或许对大家有用。
最近比较忙,有时间的话,我会适当的找些主题聊些自己的感想,欢迎各位朋友指正!
蓝色键盘
2003.6.20
另外,我觉得如下的这个帖子提出的问题相当的有讨论的价值,哪位有时间首先整理一下,或者说说自己的看法,大家一起讨论一下。
http://www.chinaunix.net/forum/viewtopic.php?t=94621
gadfly 回复于:2003-06-20 13:28:37
|
关于1.
[quote:fdfdb21490="蓝色键盘"]
综合以上三个系统来看,我们发现write数据到pipe,并不阻塞,不论这些数据有没有被其它的
进程读走。为了保证write彻底的将所有数据写完,可以让子进程sleep更长的时间。往一个管
道中写数据受到系统PIPE_BUF的限制。
[/quote:fdfdb21490]
不阻塞是什么意思?是我理解错了么?如果不阻塞,恐怕应该是parent先打印这行吧
parent[2003] process write: 100000
从结果来看,应该说是阻塞的,每次写入的数据,是受限于PIPE_BUF
|
gadfly 回复于:2003-06-20 13:44:15
|
至于2,补充一点:
先关闭读端再写的情况下,是这种结论。
但是以下这个帖子,
http://www.chinaunix.net/forum/viewtopic.php?t=93300
情况还不一样。引用我的理解:
[quote:aa417fa76c="gadfly"]
如果管道是在写的过程中关闭的,会返回成功的个数;如果在关闭后写,就会报pipe broken错,如果不捕捉或不阻塞或忽略这个SIGPIPE信号,缺省情况下,进程会直接退出,而不是write返回-1。
[/quote:aa417fa76c]
|
gadfly 回复于:2003-06-20 13:54:25
|
至于3,其实是1的延深。
还是基于我对测试1的理解,在最后一个PIPE_BUF大小的数据没读之前,父进程是阻塞,写,阻塞,写....的过程,也就是说实际上读剩最后的PIPE_BUF之后数据的时候,parent才写完,关闭写端。
但是这并不影响键盘兄关于这个问题的结论,
[quote:a432de988b="蓝色键盘"]
尽管管道的写端已经被关闭,然而在管道读端可用的情况下,内核保证管道中的数
据仍然能够被读取。
[/quote:a432de988b]
请各位指正。
|
shaoyijun 回复于:2003-06-20 14:06:09
|
[quote:cd07a287ba="gadfly"]ocess write: 100000
从结果来看,应该说是阻塞的,每次写入的数据,是受限于PIPE_BUF[/quote:cd07a287ba]
同意!!! :oops:
|
HopeCao 回复于:2003-06-20 14:06:14
|
应该是阻塞的吧!!!
[color=blue:d8ea25a484]"如果不阻塞,恐怕应该是parent先打印这行吧
parent[2003] process write: 100000
" [/color:d8ea25a484]
|
shaoyijun 回复于:2003-06-20 14:10:17
|
总结的好呀!!!
我昨天晚上也试了一下几种情况
发现自从到了chinaunix,我开始思考了!!! :oops:
|
gadfly 回复于:2003-06-20 14:11:47
|
[quote:a2f1dbe078="shaoyijun"]总结的好呀!!!
我昨天晚上也试了一下几种情况
发现自从到了chinaunix,我开始思考了!!! :oops:[/quote:a2f1dbe078]
那共享你的结论亚。
|
无双 回复于:2003-06-20 15:12:07
|
应该是阻塞
因为文件描述符初始时都是阻塞了
除非使用fcntl或是其它函数改变端口状态
|
蓝色键盘 回复于:2003-06-20 16:26:52
|
讨论的相当好,非常感谢各位,尤其是gadfly!当然,gadfly的回复是正确的。
看来,我们的理解是一致的,只是我对write的阻塞理解和大家存在差别,遗憾的是,我在总结中没有写上这种情况。
我总结的如下:
[code:1:c71875e438]
综合以上三个系统来看,我们发现write数据到pipe,并不阻塞,不论这些数据有没有被其它的进程读走。为了保证write彻底的将所有数据写完,可以让子进程sleep更长的时间。往一个管道中写数据受到系统PIPE_BUF的限制。
[/code:1:c71875e438]
如果定义宏WR<=PIPE_BUF的话,我们会看到,write会马上成功返回,并不阻塞。
实施上,正真的原因在于:
1、如果write的数据量>PIPE_BUF的话,write成功写的数据为PIPE_BUF。这时候只有写入的数据被读走。write才能继续写剩下的数据,我想大家认为的阻塞便是这种情况吧。但我总觉得理解为阻塞似乎不是很精确。不过也只能这么称呼。:P
2、如果write的数据量<=PIPE_BUF的话,write成功写入数据后返回。不会阻塞。而不管写入的数据有没有被读走。
欢迎指正!
|
蓝色键盘 回复于:2003-06-20 16:30:19
|
我的排版很乱,有时间重新处理一下.
:P
|
shaoyijun 回复于:2003-06-20 17:57:24
|
[quote:ddbe1e7379="gadfly"]
那共享你的结论亚。[/quote:ddbe1e7379]
我只是验证了一下4种情况,不是很深刻!
|
无双 回复于:2003-06-21 01:15:46
|
我觉得应该是总数据量
就是BUF中总数据量>-PIPE_LEN时会产生阻塞
而不是由当前WRITE的数据量决定
这个机制与TCP SOCKET是一样的
|
liujing6484 回复于:2004-06-01 12:28:31
|
我看了这些东西但是看不懂呀!真是还要好好学习!
|
原文转自:http://www.ltesting.net
- 评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)
-