为什么我们需要在execvp之前在管道上调用close?

时间:2020-04-13 06:02:53

标签: c pipe dup2

我一直在尝试在应用程序中使用管道来实现类似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标志传递给进程有关,以便它们可以停止读取写入,但是在将任何实际数据发送到管道之前,我看不出有什么帮助。我希望能得到一个清晰的解释。谢谢。

1 个答案:

答案 0 :(得分:3)

我很难理解为什么在调用execvp并读取或写入任何数据之前就关闭了管道。

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

执行此类关闭的原因之一是整洁和资源管理。一个进程一次可以打开多少个文件描述符是有限制的,因此,最好避免将不需要的文件描述符保持打开状态。

但是,从功能上讲,从一个管道读取的进程在任何进程中,直到与管道的写入端关联的所有所有打开文件描述符关闭,才会检测到文件结尾。这就是管道上的EOF表示的 ,这是有道理的,因为只要写端在任何地方都打开,就有可能向其中写入更多数据。