【问题标题】:forked processes with pipes having different output具有不同输出的管道的分叉进程
【发布时间】:2021-09-21 12:35:33
【问题描述】:

我有一些代码练习分叉进程(在命令行中模拟 (|))。但是,每次的输出都不相同。比如./pipe ls cat wc的输入,应该和ls | cat | wc一样。但是,有时我的代码会输出

Child with PID 12126 exited with status 0x0.

Child with PID 12127 exited with status 0x0.

      7       7      52

Child with PID 12128 exited with status 0x0.

但有时它也会输出:

Makefile

pipe

#pipe.c#

pipe.c

pipe.o

README.md

test

Child with PID 12138 exited with status 0x0.

Child with PID 12139 exited with status 0x0.

      0       0       0

Child with PID 12140 exited with status 0x0.

第一个输出是正确的(与ls | cat | wc 相比)。我发现通过第二个输出,lscat 程序的管道输出没有被wc 处理。我想知道我的程序出了什么问题,因为似乎我正确设置了管道 - 第一个程序将从标准输入获取输入并输出到管道的写入端,最后一个程序将从读取端获取输入管道并输出到标准输出。任何意见表示赞赏。

代码(./pipe):

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

int main(int argc, char *argv[])
{
  int fd[2];
  if (pipe(fd) == -1) {
    int err = errno;
    perror("pipe");
    return err;
  }
  pid_t pid[argc-1];
  int n = argc;
  if(argc <= 1){
    return EINVAL;
  }
  for (int i = 1; i < argc; i++){
    if ((pid[i] = fork()) == -1){
      int err = errno;
      perror("fork");
      return err;
    }else if(pid[i] == 0){

      //open(fd[1]);
      if(i == 1){
        dup2(fd[1],STDOUT_FILENO);
        if (execlp(argv[i], argv[i], NULL) == -1) {
          printf("failed to search for the provided executed program. \n");
          return errno;
        }
        
        
      }else if(i == argc-1){
        dup2(fd[0],STDIN_FILENO);
        if (execlp(argv[i], argv[i], NULL) == -1) {
          printf("failed to search for the provided executed program. \n");
          return errno;
        }
      }else{
          dup2(fd[0],STDIN_FILENO);
          dup2(fd[1],STDOUT_FILENO);
          if (execlp(argv[i], argv[i], NULL) == -1) {
            printf("failed to search for the provided executed program. \n");
            return errno;
          }
      }
      
    }
     close(fd[1]);
  }
  
  int wstatus;
  pid_t pids;
  while (n > 1) {
    pids = wait(&wstatus);
    printf("Child with PID %ld exited with status 0x%x.\n", (long)pids, wstatus);
    --n;
  }

}

【问题讨论】:

    标签: c linux process pipe fork


    【解决方案1】:

    您正在越界访问可变长度数组pid,因此您的程序有未定义的行为

    你可以通过将数组增大一个元素来解决这个问题:

    pid_t pid[argc]; /* not argc - 1 */
    

    另一个问题是您需要在每对命令之间建立一个新管道。总共比命令数少一个管道。也就是说,一个命令的零管道。

    stdin - ls - pipe - cat - pipe - wc - stdout   // 3 commands, 2 pipes
    

    这是一个想法,我在(几乎)循环的每次迭代中创建一个管道,并将管道的输入端保存为下一次迭代。我根本不费心保存进程 id:s,也没有为dup2 添加错误检查。但是,您应该检查它是否确实成功。

    #include <errno.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    
    int exec(const char* prog) {
        if(execlp(prog, prog, NULL) == -1) {
            perror("execlp");
        }
        return 1;
    }
    
    int main(int argc, char* argv[]) {
        pid_t pid;
    
        int in; // for saving the input side of the pipe between iterations
    
        for(int i = 1; i < argc; ++i) {
            int fd[2];
    
            // must have at least 2 commands to create a pipe
            // and we need one pipe less than the number of commands
            if(argc > 1 && i < argc - 1) pipe(fd);
    
            if((pid = fork()) == -1) {
                perror("fork");
                return 1;
    
            } else if(pid == 0) {
                if(i == 1) {       // first command
                    if(argc > 1) { // must have more than one command to dup
                        dup2(fd[1], STDOUT_FILENO);
                        close(fd[1]);
                        close(fd[0]);
                    }
    
                } else if(i == argc - 1) { // last command
                    dup2(in, STDIN_FILENO);
                    close(in);
    
                } else { // middle commands
                    dup2(in, STDIN_FILENO);
                    close(in);
                    dup2(fd[1], STDOUT_FILENO);
                    close(fd[1]);
                    close(fd[0]);
                }
    
                return exec(argv[i]);
            }
    
            // parent
            if(i > 1) close(in);  // close old in
            in = fd[0]; // save the stdin side of the pipe for next loop iteration
            close(fd[1]); // no use for the stdout end
        }
    
        int wstatus;
        while((pid = wait(&wstatus)) != -1) {
            printf("Child with PID %ld exited with status 0x%x.\n", (long)pid,
                   wstatus); 
        }
    }
    

    【讨论】:

    • 您好,我试过了,但没有解决问题
    • @d3a 它解决了具有未定义行为的问题。我没有超越这一点。如果您将问题更新为没有 UB,我将删除此答案。 Fwiw:int fd[2]; 只有一个管道的描述符,你似乎需要更多。
    • @d3a 我查看了更多内容并添加了一些可能有帮助的内容。
    【解决方案2】:

    您需要更多管道,因为您当前的代码中只有一个。如果lscat 都写入该管道,并且catwc 都从该管道读取,则您无法同步cat 读取的数据与wc 读取的数据。也就是说,wc 可能直接从lscat 读取数据。而cat 可能会从自身读取数据。 (这似乎有时会发生)。进程从管道读取数据的顺序将取决于它们计划运行的顺序。

    您需要更多管道。 (比进程总数少一个。)

    【讨论】:

    • 我还猜想close(fd[1]); 可能会出现竞争条件,在孩子完成之前关闭管道输出可能会导致输出丢失。我猜 OP 遇到了 open 并忘记评论 close
    • 附带说明,dup2 可能失败,代码应检查 dup2 返回并在 -1 返回时使用 perror
    猜你喜欢
    • 2011-06-12
    • 1970-01-01
    • 2011-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-24
    • 1970-01-01
    相关资源
    最近更新 更多