为什么我们需要在 execvp 之前关闭管道?

Why do we need to call close on pipes before execvp?

我一直在尝试在应用程序中使用管道实现类似 shell 的功能,并且我正在关注 this example。如果原始代码被删除,我将在此处复制代码以供将来参考:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

/**
 * Executes the command "cat scores | grep Villanova | cut -b 1-10".
 * This quick-and-dirty version does no error checking.
 *
 * @author Jim Glenn
 * @version 0.1 10/4/2004
 */

int main(int argc, char **argv)
{
  int status;
  int i;

  // arguments for commands; your parser would be responsible for
  // setting up arrays like these

  char *cat_args[] = {"cat", "scores", NULL};
  char *grep_args[] = {"grep", "Villanova", NULL};
  char *cut_args[] = {"cut", "-b", "1-10", NULL};

  // make 2 pipes (cat to grep and grep to cut); each has 2 fds

  int pipes[4];
  pipe(pipes); // sets up 1st pipe
  pipe(pipes + 2); // sets up 2nd pipe

  // we now have 4 fds:
  // pipes[0] = read end of cat->grep pipe (read by grep)
  // pipes[1] = write end of cat->grep pipe (written by cat)
  // pipes[2] = read end of grep->cut pipe (read by cut)
  // pipes[3] = write end of grep->cut pipe (written by grep)

  // Note that the code in each if is basically identical, so you
  // could set up a loop to handle it.  The differences are in the
  // indicies into pipes used for the dup2 system call
  // and that the 1st and last only deal with the end of one pipe.

  // fork the first child (to execute cat)

  if (fork() == 0)
    {
      // replace cat's stdout with write part of 1st pipe

      dup2(pipes[1], 1);

      // close all pipes (very important!); end we're using was safely copied

      close(pipes[0]);
      close(pipes[1]);
      close(pipes[2]);
      close(pipes[3]);

      execvp(*cat_args, cat_args);
    }
  else
    {
      // fork second child (to execute grep)

      if (fork() == 0)
    {
      // replace grep's stdin with read end of 1st pipe

      dup2(pipes[0], 0);

      // replace grep's stdout with write end of 2nd pipe

      dup2(pipes[3], 1);

      // close all ends of pipes

      close(pipes[0]);
      close(pipes[1]);
      close(pipes[2]);
      close(pipes[3]);

      execvp(*grep_args, grep_args);
    }
      else
    {
      // fork third child (to execute cut)

      if (fork() == 0)
        {
          // replace cut's stdin with input read of 2nd pipe

          dup2(pipes[2], 0);

          // close all ends of pipes

          close(pipes[0]);
          close(pipes[1]);
          close(pipes[2]);
          close(pipes[3]);

          execvp(*cut_args, cut_args);
        }
    }
    }

  // only the parent gets here and waits for 3 children to finish

  close(pipes[0]);
  close(pipes[1]);
  close(pipes[2]);
  close(pipes[3]);

  for (i = 0; i < 3; i++)
    wait(&status);
}

我无法理解为什么在调用 execvp 和读取或写入任何数据之前关闭管道。我相信这与将 EOF 标志传递给进程有关,以便它们可以停止读写,但是在将任何实际数据推送到管道之前我看不出这有什么帮助。我会很感激一个明确的解释。谢谢

I have trouble understanding why the pipes are being closed just before calling execvp and reading or writing any data.

管道没有关闭。相反,一些与管道末端关联的文件描述符正在关闭。每个子进程都将管道端文件描述符复制到它的一个或两个标准流上,然后关闭它实际上不会使用的所有管道端文件描述符,这些文件描述符存储在 pipes大批。只要每一端在至少一个进程中打开,每个管道本身就保持打开和可用状态,并且每个子进程至少保持一个管道的一端打开。当子进程终止时(或至少在子进程的控制下,post execvp()),它们将关闭。

执行此类关闭的一个原因是为了整洁和资源管理。一个进程一次可以打开多少个文件描述符是有限制的,所以避免打开不需要的文件描述符是明智的。

而且,在功能上,从其中一个管道读取的进程将不会检测到文件结尾,直到 所有 打开与管道写入端关联的文件描述符,在任何情况下进程,关闭。这就是 EOF on a pipe 的意思,这是有道理的,因为只要写端在任何地方打开,就有可能向其写入更多数据。