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

标签 c pipe fork stdout stdin

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

我想我明白应该发生什么,但我不知何故越过我的电线让它发生。父进程应该派生子进程,子进程又将其 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 循环之前和 fork 进程中的 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;
}

关于c - C中N个进程之间的管道输出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64827856/

相关文章:

c++ - 我如何正确使用 pipe、fork 和 execv 来启动辅助程序并与之交互?

C: fork 新进程失败

c - STM32CubeMX HAL_xxx_MspInit()函数中的MSP代表什么?

linux - 如何使用管道发送参数

c# - .net c# 匿名管道 - 被误解了吗?

linux - 奇怪的管道故障案例

c - 不同进程的共享结构

c - C 中的地址位置和地址位置的值

c - C 中的指针和局部数组

c - 如何打开anullsrc输入?