发信人: kevintz() 
整理人: wenbobo(2002-12-27 15:48:51), 站内信件
 | 
 
 
                   关于pipe()的详细解析
                      ---- 兼答startrek问
 
                                            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] ※ 来源:.月光软件站 http://www.moon-soft.com.[FROM: 61.140.71.100]
  | 
 
 
 |