Почему мы должны вызывать close на каналах перед execvp? - PullRequest
0 голосов
/ 13 апреля 2020

Я пытался реализовать подобную оболочке функциональность с помощью каналов в приложении, и я следую этому примеру . Я воспроизведу код здесь для дальнейшего использования в случае удаления оригинала:

#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 Ответ

3 голосов
/ 13 апреля 2020

Мне сложно понять, почему каналы закрываются непосредственно перед вызовом execvp и чтением или записью каких-либо данных.

Каналы не закрываются. Скорее, некоторые файловые дескрипторы, связанные с концами канала, закрываются. Каждый дочерний процесс дублирует дескрипторы файлов на конце канала в один или оба своих стандартных потока, а затем закрывает все дескрипторы файлов на конце канала, которые он на самом деле не собирается использовать, и все они хранятся в массиве pipes. Каждый канал остается открытым и может использоваться до тех пор, пока каждый конец открыт хотя бы в одном процессе, а каждый дочерний процесс удерживает как минимум один конец одного канала открытым. Они закрываются, когда дочерние процессы завершаются (или, по крайней мере, под контролем дочерних процессов, post execvp()).

Одной из причин такого закрытия является аккуратность и управление ресурсами. Существует ограничение на количество файловых дескрипторов, которые процесс может открыть одновременно, поэтому разумно избегать оставлять ненужные файловые дескрипторы открытыми.

Но также функционально чтение процесса из одного из каналов не обнаружит конец файла, пока все дескрипторы открытого файла , связанные с концом записи канала, в любом процессе не будут закрыто. Это то, что EOF для канала означает , и это имеет смысл, поскольку, пока конец записи открыт где-либо, возможно, что в него будет записано больше данных.

...