制作多个管道,等待每个进程的返回码

时间:2017-04-07 16:53:31

标签: c unix pipe fork dup

我想使用 execve dup2 fork waitpid & 管道功能。

是的,例如这个命令:/bin/ls -l | /usr/bin/head -2 | /usr/bin/wc由以下人员复制:

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

int add_child_process(char **argv, int in, int out, char **envp)
{
  int pid;

  pid = fork();
  if (pid == 0)
    {
      if (in != 0)
    {
      dup2(in, 0);
    }
      if (out != 1)
    {
      dup2(out, 1);
    }
      if (execve(argv[0], argv, envp) == -1)
        perror("Execve failed");
    }
  else
    return (pid);


}

int main(int av, char **ag, char **envp)
{
  int in;
  int pipes[2];
  char *argv[3];
  int pids[3];

  /** Launch first process that read on original stdin (0) and write on out of pipe **/
  pipe(pipes);
  argv[0] = "/bin/ls";
  argv[1] = "-l";
  argv[2] = NULL;
  pids[0] = add_child_process(argv, 0, pipes[1], envp);
  close(pipes[1]);
  in = pipes[0];

  /** Launch second process that read on in of old pipe and write on out of new pipe **/
  pipe(pipes);
  argv[0] = "/usr/bin/head";
  argv[1] = "-2";
  argv[2] = NULL;
  pids[1] = add_child_process(argv, in, pipes[1], envp);
  close(in);
  close(pipes[1]);
  in = pipes[0];

  /** Launch last process that read on in of old pipe and write on original stdout (1) **/
  argv[0] = "/usr/bin/wc";
  argv[1] = NULL;
  pids[2] = add_child_process(argv, in, 1, envp);
  close(in);

  /** Wait for all process end to catch all return codes **/
  int return_code;
  waitpid(pids[0], &return_code, 0);
  printf("Process 0 return : %d\n", return_code);
  waitpid(pids[1], &return_code, 0);
  printf("Process 1 return : %d\n", return_code);
  waitpid(pids[2], &return_code, 0);
  printf("Process 2 return : %d\n", return_code);
} 

工作,输出是:

2      11      60
Process 0 return : 0
Process 1 return : 0
Process 2 return : 0

echo "/bin/ls -l | /usr/bin/head -2 | /usr/bin/wc" | bash

但是对于像ping google.com | head -2 | wc这样的阻止命令,它会显示:

      2      16     145

并且仍然被阻止。

以下是使用此命令更新的主要内容:

int main(int av, char **ag, char **envp)
{
  int in;
  int pipes[2];
  char *argv[3];
  int pids[3];

  /** Launch first process that read on original in (0) and write on out of pipe **/
  pipe(pipes);
  argv[0] = "/bin/ping";
  argv[1] = "google.com";
  argv[2] = NULL;
  pids[0] = add_child_process(argv, 0, pipes[1], envp);
  close(pipes[1]);
  in = pipes[0];

  /** Launch second process that read on in of old pipe and write on out of new pipe **/
  pipe(pipes);
  argv[0] = "/usr/bin/head";
  argv[1] = "-2";
  argv[2] = NULL;
  pids[1] = add_child_process(argv, in, pipes[1], envp);
  close(in);
  close(pipes[1]);
  in = pipes[0];

  /** Launch last process that read on in of old pipe and write on original stdout (1) **/
  argv[0] = "/usr/bin/wc";
  argv[1] = NULL;
  pids[2] = add_child_process(argv, in, 1, envp);
  close(in);

  /** Wait for all process end to catch all return codes **/
  int return_code;
  waitpid(pids[0], &return_code, 0);
  printf("Process 0 return : %d\n", return_code);
  waitpid(pids[1], &return_code, 0);
  printf("Process 1 return : %d\n", return_code);
  waitpid(pids[2], &return_code, 0);
  printf("Process 2 return : %d\n", return_code);
} 

我真的不明白为什么会出现这种情况,例如在Bash中:echo "ping google.com | head -2 | wc" | bash显示2 16 145并且不会卡住。

我如何处理阻止命令?我需要根据上一个错误代码获取我的进程的所有返回代码以进行返回。

1 个答案:

答案 0 :(得分:1)

你的add_child_process()函数应该返回PID;它没有。如果程序无法执行,它还应该在execve()之后进行错误处理。

第一个解决方案 - 不够好

通过这些修复(和其他各种更改来超过我的最低编译警告级别 - 例如#include <stdio.h>以便在使用之前有printf()的声明),我得到了大致是预期的产出。

我使用此代码(源文件pc67.c)来测试:

#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

static
int add_child_process(char **argv, int in, int out, char **envp)
{
    int pid;

    pid = fork();
    if (pid == 0)
    {
        if (in != 0)
        {
            dup2(in, 0);
        }
        if (out != 1)
        {
            dup2(out, 1);
        }
        execve(argv[0], argv, envp);
        abort();
    }
    return pid;
}

int main(int av, char **ag, char **envp)
{
    int in;
    int pipes[2];
    char *argv[3];
    int pids[3];
    assert(ag[av] == 0);

    /** Launch first process that read on original stdin (0) and write on out of pipe **/
    pipe(pipes);
    argv[0] = "/bin/ls";
    argv[1] = "-l";
    argv[2] = NULL;
    pids[0] = add_child_process(argv, 0, pipes[1], envp);
    close(pipes[1]);
    in = pipes[0];

    /** Launch second process that read on in of old pipe and write on out of new pipe **/
    pipe(pipes);
    argv[0] = "/usr/bin/head";
    argv[1] = "-2";
    argv[2] = NULL;
    pids[1] = add_child_process(argv, in, pipes[1], envp);
    close(in);
    close(pipes[1]);
    in = pipes[0];

    /** Launch last process that read on in of old pipe and write on original stdout (1) **/
    argv[0] = "/usr/bin/wc";
    argv[1] = NULL;
    pids[2] = add_child_process(argv, in, 1, envp);
    close(in);

    /** Wait for all process end to catch all return codes **/
    int return_code;
    waitpid(pids[0], &return_code, 0);
    printf("Process 0 return : %d\n", return_code);
    waitpid(pids[1], &return_code, 0);
    printf("Process 1 return : %d\n", return_code);
    waitpid(pids[2], &return_code, 0);
    printf("Process 2 return : %d\n", return_code);
}

abort()没有开火;所有命令都已执行(我在尝试运行代码之前检查了它们是否在您列出的目录中)。

我使用命令行使用GCC 6.3.0在运行macOS Sierra 10.12.4的Mac上编译:

$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes \
>     -Wstrict-prototypes -Wold-style-definition pc67.c -o pc67
$

有了这个,我得到了输出:

$ ./pc67
       2      11      70
Process 0 return : 0
Process 1 return : 0
Process 2 return : 0
$

11个单词是&#39; total&#39;和块数,然后ls -l输出的一行9个字。我在命令之前和之后重新检查ps的输出;没有杂散进程在运行。

第二种解决方案 - 规避而不治愈

  

谢谢,但与Bash echo "ping google.com | head -2 | wc" | bash不同,它仍然在等待中被阻止,并且不会终止程序。

我不清楚您的替代命令行与您的问题有什么关系。但是,当您引入ping google.com而不是ls -l作为命令时,可能会发生许多事情。您无法合理地更改问题的参数。这完全是一个新问题。

在shell代理中,您没有指定程序的路径;你的代码不会处理它。 (如果您使用execvp()而不是execve() - 当您简单地中继继承的环境时使用execve()是无意义的;无论如何都要继承环境 - 然后命令的路径会无关紧要)。

然而,使用"/sbin/ping""google.com"(在程序pc23.c的副本中)似乎确实悬而未决。看一下另一个终端的设置,我看到了:

$ ps -ftttys000
  UID   PID  PPID   C STIME   TTY           TIME CMD
    0 51551 51550   0 10:13AM ttys000    0:00.50 login -pf jleffler
  502 51553 51551   0 10:13AM ttys000    0:00.14 -bash
  502 54866 51553   0 11:10AM ttys000    0:00.01 pc23
  502 54867 54866   0 11:10AM ttys000    0:00.00 /sbin/ping google.com
  502 54868 54866   0 11:10AM ttys000    0:00.00 (head)
  502 54869 54866   0 11:10AM ttys000    0:00.00 (wc)
$

headwc进程都已终止(这些是僵尸的条目),但ping进程并未死亡。它似乎没有做太多,但也不会濒临死亡。

一段时间后,我得到了:

$ ps -ftttys000
  UID   PID  PPID   C STIME   TTY           TIME CMD
    0 51551 51550   0 10:13AM ttys000    0:00.50 login -pf jleffler
  502 51553 51551   0 10:13AM ttys000    0:00.14 -bash
  502 54866 51553   0 11:10AM ttys000    0:00.01 pc23
  502 54867 54866   0 11:10AM ttys000    0:00.09 /sbin/ping google.com
  502 54868 54866   0 11:10AM ttys000    0:00.00 (head)
  502 54869 54866   0 11:10AM ttys000    0:00.00 (wc)
$

它设法使用了9秒CPU。因此,在这种情况下,由于某种原因,ping并未关注SIGPIPE信号。

如何解决?这需要实验,可能更复杂。最简单的解决方法是添加适用于我的-c3等选项。我没有立即有动力寻找替代修复方案。

ping的macOS手册页部分地说:

  

-c count

     

发送(和接收)计数 ECHO_RESPONSE数据包后停止。如果未指定此选项,ping将一直运行,直到被中断。如果此选项与ping扫描一起指定,则每次扫描将包含 count 数据包。

有趣的是,推测这个术语的中断程度是多么精确。是。如果发送实际( Control-C )中断或SIGTERM信号(kill 54867),程序将终止。奇怪的是它有时会在SIGPIPE停止,有时则不停止。

第三种解决方案 - 关闭文件描述符

进一步考虑,问题是代码没有关闭足够的文件描述符。这通常是管道无法终止的问题。以ls作为第一个进程,问题被隐藏,因为ls自动终止,关闭了杂散文件描述符。 更改为ping会暴露问题。这是代码的另一个修订版。它会关闭dup2()描述符(oops;在手腕上自拍)。它还确保通过tbc的新add_child_process()(待关闭)文件描述符参数关闭其他管道描述符。我还选择使用execv()代替execve(),从add_child_process()函数中删除一个参数,并允许int main(void),因为该程序不关注其参数(这使我失去了assert(),只要你没有&#39;就可以确保参数计数和矢量参数被使用了。用-DNDEBUG或等价物编译。

我还调整了命令构建代码,以便从-c添加/删除3ping参数,或者添加/删除其他命令中的参数。

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

static
int add_child_process(char **argv, int in, int out, int tbc)
{
    int pid;

    pid = fork();
    if (pid == 0)
    {
        if (tbc >= 0)
            close(tbc);
        if (in != 0)
        {
            dup2(in, 0);
            close(in);
        }
        if (out != 1)
        {
            dup2(out, 1);
            close(out);
        }
        execv(argv[0], argv);
        abort();
    }
    return pid;
}

int main(void)
{
    int in;
    int pipes[2];
    char *argv[10];
    int pids[3];

    /** Launch first process that read on original stdin (0) and write on out of pipe **/
    pipe(pipes);
    int argn = 0;
    argv[argn++] = "/sbin/ping";
    //argv[argn++] = "-c";
    //argv[argn++] = "3";
    argv[argn++] = "google.com";
    argv[argn++] = NULL;
    pids[0] = add_child_process(argv, 0, pipes[1], pipes[0]);
    close(pipes[1]);
    in = pipes[0];

    /** Launch second process that read on in of old pipe and write on out of new pipe **/
    pipe(pipes);
    argn = 0;
    argv[argn++] = "/usr/bin/head";
    argv[argn++] = "-2";
    argv[argn++] = NULL;
    pids[1] = add_child_process(argv, in, pipes[1], pipes[0]);
    close(in);
    close(pipes[1]);
    in = pipes[0];

    /** Launch last process that read on in of old pipe and write on original stdout (1) **/
    argn = 0;
    argv[argn++] = "/usr/bin/wc";
    argv[argn++] = NULL;
    pids[2] = add_child_process(argv, in, 1, -1);
    close(in);

    /** Wait for all process end to catch all return codes **/
    int return_code;
    waitpid(pids[0], &return_code, 0);
    printf("Process 0 return : %d\n", return_code);
    waitpid(pids[1], &return_code, 0);
    printf("Process 1 return : %d\n", return_code);
    waitpid(pids[2], &return_code, 0);
    printf("Process 2 return : %d\n", return_code);
}

使用此代码(从pc73构建的可执行文件pc73.c),我得到如下输出:

$ ./pc73
       2      14     109
Process 0 return : 13
Process 1 return : 0
Process 2 return : 0
$

出现wc的输出和另一个输出之间有一个短暂的亚秒级暂停;在尝试再次写入之前,ping命令等待一秒钟。 13的退出状态表示ping确实死于SIGPIPE信号。之前的问题是ping仍然打开了管道的读取端,因此它没有得到SIGPIPE信号。

要学习的课程 - 关闭大量文件描述符

使用管道时,请确保关闭所有需要关闭的文件描述符。