【问题标题】:Why does this pipe work?为什么这个管道有效?
【发布时间】:2017-01-31 00:21:59
【问题描述】:

我正在尝试理解管道。该程序运行良好(父母向其孩子发送消息“你好”,孩子打印它。我不明白两件事:

  • 程序是否会在某个时候停止,因为假设子进程关闭了写描述符,同时父进程也关闭了写描述符?

  • 为什么描述符关闭后不需要再打开?


#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main(){

    int desc[2];
    pipe(desc);

    int pid = fork();

    if(pid == 0){
        while(1){
            char buffer[100];
            close(desc[1]);
            read(desc[0], buffer, 100);
            printf("Child: recieved message - '%s'\n", buffer);
        }
    }
    if(pid > 0){
        while(1){
            sleep(1);
            char buffer[100];
            strcpy(buffer, "Hello, child!");
            close(desc[0]);
            write(desc[1], buffer, 100);
        }
    }
    return 0;
}

【问题讨论】:

    标签: c unix process pipe


    【解决方案1】:

    也许一些关于 fork() 的信息已经到位。成功执行后,fork 会创建两个almost identical 进程。在您的情况下,唯一的区别是 fork 的返回值(父母收到孩子的 pid,孩子得到 0)。因此,管道在两个进程中都打开,并且文件描述符对两者都有效。这里的关键是这些描述符所代表的内容已复制到两个进程。

    因此,当子进程关闭 fd 时,它所做的只是销毁自己的文件描述符,而不是其父文件的描述符。

    请注意,fd 的关闭应该发生在循环之外。虽然不确定(在您的情况下,可能没有发生),但多次关闭同一个文件可能会导致您的程序崩溃。

    【讨论】:

      【解决方案2】:

      这么少的代码,这么多小问题:

      #include <stdlib.h>
      #include <stdio.h>
      #include <unistd.h>
      #include <string.h>
      
      int main(){
      
          int desc[2];
          pipe(desc);
      

      pipe() 调用或其他系统调用没有错误检查。我不会对大多数可能性做进一步评论。

          int pid = fork();
      

      代码实际上处理了一个失败的fork()

          if(pid == 0){
              while(1){
                  char buffer[100];
                  close(desc[1]);
      

      close() 不应位于while 循环内。无论如何,它在第一次迭代后就失败了,所以没有造成巨大的损害,但你不应该反复尝试关闭已经关闭的东西。它应该移到循环之外。

                  read(desc[0], buffer, 100);
      

      您不知道读取了多少数据。您应该注意读取数据的操作的结果。如果你不这样做,你很容易处理垃圾。此外,当父母去世时,孩子的返回值为零;没有其他东西可以导致孩子死亡。您的代码应该测试何时该退出循环。

                  printf("Child: received message - '%s'\n", buffer);
      

      这假设从管道读取的数据是空终止的。这并不总能得到保证,但请稍后查看此计划。

              }
          }
          if(pid > 0){
              while(1){
                  sleep(1);
                  char buffer[100];
                  strcpy(buffer, "Hello, child!");
                  close(desc[0]);
      

      又一个错位的close();它也应该在循环之外。

                  write(desc[1], buffer, 100);
      

      您初始化了您写入的 100 个字节中的 14 个字节,其中最后一个字节为空字节。那是幸运的;它使孩子免于有未定义的行为。严格来说,父级具有未定义的行为;在实践中,这不会是一个问题。可以说,您应该测试write() 是否有效。如果孩子死了,父母会得到一个SIGPIPE,一切都很好——父母也停止了。

              }
          }
          return 0;
      }
      

      没有什么可以阻止这些过程无限重复。你必须打断他们才能阻止他们。从长远来看,这不是一个好的策略。

      你问:

      • 程序是否会在某个时候停止,因为假设子进程关闭了写描述符,同时父进程也关闭了写描述符?

      父级只能关闭它的写描述符副本,尽管它不会这样做,除非它退出。子进程可以并且确实在第一次通过循环时关闭其写入描述符的副本(并且当它尝试在后续循环中这样做时失败 - 它会在 errno 中返回 EBADF 指示错误的文件描述符。类似地使用读取描述符;父级在第一个循环中关闭其读取描述符,并最终在每次后续迭代中失败。子级在退出之前不会关闭其读取描述符。进程之间没有干扰。紧接在fork() 之后, 在任何一个进程执行更多操作之前, 每个方向都有一个带有两个打开文件描述符 (很棒的术语, 不是吗!) 的单个公共打开文件描述。close() 调用影响文件描述符;当文件描述被消除时没有任何文件描述符可以引用它。

      • 为什么描述符关闭后不需要再打开?

      无法重新打开已关闭的管道文件描述符。并且每个进程只关闭它不使用的管道的末端(这是一个很好的做法 - 甚至很重要,因为如果一个进程试图从它具有写入端的管道中读取,它将不会获得 EOF管道仍然打开)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-04-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-02-13
        相关资源
        最近更新 更多