代码改变世界

(转)关于pipe()的详细解析

2008-06-10 09:17  htc开发  阅读(288)  评论(0编辑  收藏  举报

                                   kevintz 2000.8.23
    int pipe(int fd[2])函数在内核生成一个管道,如图。返回的
fd[0]描述符用于从管道读内容,fd[1]用于向管道写。
               ---------------------
       fd[0]<--| |<---fd[1]
---------------------
读的时候,如果管道没数据,读进程阻塞。如果写的时候管道满,
写进程阻塞。可以把fd[0], fd[1]设成是非阻塞,在上面的阻塞情形,
不再阻塞进程,立刻返回。
管道的某一端只能是只读或只写,如果向fd[0]写和向fd[1]读都会
出错,返回-1。
另外,fork()调用生成的子进程和原来的父进程是共用这条管道的,
并不是又生成另一条管道。如图所示:
+------------------------+
| |
------------- +- ----------- <-----+ | -----------
父 | fd[0] |<----- | | <---+ | +->| fd[0]   | 子
     |           |        -----------     |  |    |         |         
     |   fd[1]   |------------------------+  +--- |  fd[1]  |
     | ........  |                                | .....   |
    而且对于子进程来说,fd[0]也是用于只读,fd[1]也是用于写,因为
它们的指向和父进程里是一样的。
    下面来分析一下网友startrek写的程序,并分析为何出错。
int main(){
  int  fd[2];
  char buf[200],len;
  int  status;
  if(pipe(fd)==0){
    if(fork()==0){
      len=read(fd[0],buf,sizeof(buf));
      buf[len]=0;
      printf("[Child]:%s/n",buf);
      sprintf(buf,"answer from child");
      write(fd[1],buf,strlen(buf));
    }
    else{
      sprintf(buf,"string from parent");
      write(fd[1],buf,strlen(buf));
      sleep(2);
      len=read(fd[0],buf,sizeof(buf));    // ***
      buf[len]=0;
      printf("[Parent]:%s/n",buf);
    }
  }
  wait(&status);
}
[情况1]
  如果注释掉sleep(2);这一句,父进程就会接收自己传给子进程的
  字符串,显示
  [Parent]:string from parent
  而子进程就因为无数据读而阻塞。这说明父进程和子进程之间只有
  一条管道队列进行双向传输,父进程读取数据后管道为空,所以子
  进程阻塞。
kevintz分析:其实这个结果是正确的。原因如下:去掉sleep(2)后,当父进
程写fd[1]后,继续运行read(..),所以父进程把自己写进去的东西读
了出来。而到子进程运行时,就因为没数据读而阻塞了。注意:管道
只能是单向传输的!
[情况2]
  但如果将标有***的一句改为
  len=read(fd[1],buf,sizeof(buf)),而sleep(2);不注释,就会有
  下面奇怪的显示结果:
  [Child]:string from parent
  [Parent]:string from parent
  按道理说在子进程退出后,管道中应该只有"answer from child"。
kevintz分析:这个结果也是正确的。其实子进程是已经读到了父进程的内容,
而且已经写回了管道里。不过因为不论是父进程还是子进程对fd[1]读,
都是出错的,返回-1,而buf里的东西还是sprintf进去的东西,没变。
反而buf[len]就是buf[-1]=0这句有潜在的危险。然后你把buf打印,所
以还是原来的内容。管道的某一端都只能是只读或只写的!
[情况3]
  和情况2相同,在最后加3行,即父进程使用fd[0]进行read,其
  显示结果为
  [Child]:string from parent
  [Parent]:string from parent
  [Parent]:answer from child
kevintz分析:这个例子就正好做了情况2的解析。说明子进程的确收到了父进程
的信息,而且写回了管道,而父进程应该用read(fd[0]...)来读的,所
以读到了子进程写回的信息。
[情况4]
  如果一开始父进程多传一个串"string 2 from parent",其结果
  就更复杂。
kevintz分析:呵呵:),多传一个串,结果慢慢去分析吧................
总结:
    其实这种通信,应该用两条管道来形成一个双向的交流管道,而不应该用一
条管道。
    当然你可以用信号量来同步管道的使用,但太复杂,而且不实际。双向管道
可以这样实现。
int main()
{
    int fd1[2],fd2[2],len;
    char buf[128];
    int status;
    pipe(fd1);
    pipe(fd2);
    if( fork() == 0)
    {
       close(fd1[0]);
       close(fd2[1]);
       len=read(fd2[0], buf, 128);
       buf[len]=0;
       printf("[Child]:%s/n",buf);
       sprintf(buf,"answer from child");
       write(fd1[1], buf, strlen(buf));
       close(fd2[0]);
       close(fd1[1]);
    }
    else
    {
       close(fd1[1]);
       close(fd2[0]);
       sprintf(buf, "Hello from parent");
       write(fd2[1], buf, strlen(buf));
       len=read(fd1[0],buf,sizeof(buf));    
       buf[len]=0;
       printf("[Parent]:%s/n",buf);
       close(fd1[0]);
       close(fd2[1]);
    }
    wait(&status); 
    exit(0);
}
--
※ 修改:.kevintz 于 Aug 23 11:08:13 修改本文.[FROM: 61.140.71.100]