C中N个进程之间的管道输出

Piping output between N processes in C

我正在尝试编写一个 C 程序来模拟两个或多个进程的管道,例如 ls | sort | wc 所以 运行 我的代码 ./driver ls sort wc 应该显示相同的结果.我想我真的很接近,但我似乎无法在下面的代码中找到错误。任何帮助将不胜感激,我在这里真的很困惑。

我想我明白应该发生什么,但我不知何故越过了我的电线才能让它发生。 parent 应该分叉 child 个进程,这些进程又将其 STDOUT 重新路由到管道的写入端(a)。在第一个 child 之后创建的任何 child 都应该将此管道 (a) 的读取端视为其 STDIN,并将其自己的输出重定向到它自己的管道 (b)。

假设第三个进程通过管道传输。它应该将管道 (b) 的读取端视为 STDIN,并在执行请求的命令之前再次将其输出通过管道传输到新管道 (c) 的写入端。

最后一种情况是当最终进程被传递给管道时。在此示例中,第四个进程将考虑管道的读取端(c),但不需要重定向 STDOUT,只需将其正常发送到 STDOUT。

#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>

#define FORK_CHILD 0

static void error_and_exit(void) {
  fprintf(stderr, "Error: %s\n", strerror(errno));
  exit(EXIT_FAILURE);
}

static int fork_or_die(void) {
  int pid = fork();
  if (pid == -1) {
    error_and_exit();
  }
  return pid;
}

int main(const int argc, const char * argv[])
{
  int processes = argc - 1;
  int apipe[argc - 1][2];
  int pid;

  int result = -1;
  for (int i = 0; i < processes; i++) {
    result = pipe(apipe[i]);
    if (result == -1) {
      error_and_exit();
    }

  }

  for (int i = 0; i < processes; i++) {

    pid = fork_or_die();

    // Child process executes process
    if (pid == FORK_CHILD) {

      // If we are not the first program in the pipe
      if (i > 1) {
        // Use the output from the previous program in the pipe as our input

        // Check the read end of the pipe and STDIN are different descriptors
        if (apipe[i - 1][0] != STDIN_FILENO) {
          // Send the read end of the pipe to STDIN
          if (dup2(apipe[i - 1][0], STDIN_FILENO) == -1) {
            error_and_exit();
          }
        }
      }

      // Before we execute a process, bind the write end of the pipe to STDOUT
      // Don't do this to the last process in the pipe, just send output to STDOUT as normal
      if (i < processes - 1) {
        // Check the write end of the pipe and STDOUT are different descriptors
        if (apipe[i][1] != STDOUT_FILENO) {

          // Send the write end of the pipe to STDOUT
          if (dup2(apipe[i][1], STDOUT_FILENO) == -1) {
            error_and_exit();
          }
        }
      }

      // Child executes requested process
      if (execlp(argv[i + 1], argv[i + 1], (char *)NULL) == -1) {
        error_and_exit();
      }

      wait(NULL);
    }
    // Parent does nothing until loop exits (waits for children)

  }

  return 0;
}

我发现您的代码存在三个问题:

  1. 由于您决定从 0 开始索引子进程,因此两个进程将跳过这部分代码。我现在想到的最简单的解决方法是将 1 更改为 0 或将 > 更改为 >=.

    // If we are not the first program in the pipe
    if (i > 1) {
    
  2. 您在父级未执行的代码中调用 wait。将呼叫移到 if (pid == FORK_CHILD) 分支之外将无济于事,因为父级将等待一个子级完成,然后另一个子级开始。子进程的写操作可能会等待下一个子进程使用一些数据并在缓冲区中放置位置。我现在想到的最简单的解决方案是将 wait 调用移动到另一个循环。

  3. 您在父进程和子进程中保持所有管道的描述符打开。您应该在父进程中的 wait 循环之前和分叉进程中的 execlp 之前关闭它。像 grepsort 这样的程序将不会完成,除非它们在传入流中收到 EOF。只要至少一个写描述符仍然打开,管道就不会发送 EOF。

应用了最少更改的代码:

#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>

#define FORK_CHILD 0

static void error_and_exit(void) {
  fprintf(stderr, "Error: %s\n", strerror(errno));
  exit(EXIT_FAILURE);
}

static int fork_or_die(void) {
  int pid = fork();
  if (pid == -1) {
    error_and_exit();
  }
  return pid;
}

int main(const int argc, const char * argv[])
{
  int processes = argc - 1;
  int apipe[argc - 1][2];
  int pid;

  int result = -1;
  for (int i = 0; i < processes; i++) {
    result = pipe(apipe[i]);
    if (result == -1) {
      error_and_exit();
    }

  }

  for (int i = 0; i < processes; i++) {

    pid = fork_or_die();

    // Child process executes process
    if (pid == FORK_CHILD) {

      // If we are not the first program in the pipe
      if (i > 0) {
        // Use the output from the previous program in the pipe as our input

        // Check the read end of the pipe and STDIN are different descriptors
        if (apipe[i - 1][0] != STDIN_FILENO) {
          // Send the read end of the pipe to STDIN
          if (dup2(apipe[i - 1][0], STDIN_FILENO) == -1) {
            error_and_exit();
          }
        }
      }

      // Before we execute a process, bind the write end of the pipe to STDOUT
      // Don't do this to the last process in the pipe, just send output to STDOUT as normal
      if (i < processes - 1) {
        // Check the write end of the pipe and STDOUT are different descriptors
        if (apipe[i][1] != STDOUT_FILENO) {

          // Send the write end of the pipe to STDOUT
          if (dup2(apipe[i][1], STDOUT_FILENO) == -1) {
            error_and_exit();
          }
        }
      }

      for (int j = 0; j < processes; j++) {
         if(close(apipe[j][0]) == -1)
         error_and_exit();
         if(close(apipe[j][1]) == -1)
         error_and_exit();
      }

      // Child executes requested process
      if (execlp(argv[i + 1], argv[i + 1], (char *)NULL) == -1) {
        error_and_exit();
      }
    }

  }
   
  // Parent does nothing until loop exits (waits for children)
  for (int i = 0; i < processes; i++) {
         if(close(apipe[i][0]) == -1)
         error_and_exit();
         if(close(apipe[i][1]) == -1)
         error_and_exit();
  }

  for (int i = 0; i < processes; i++) {
      wait(NULL);
  }

  return 0;
}