0

我正在尝试在 C 中实现多个管道,解决方案应该是:

cmd1 | cmd2 | cmd3

并为:

        |--- cmd2

cmd1    |--- cmd3

        |--- cmd4
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

int main(int argc, char *argv[]) {

char* args1[] = { "ls", NULL, NULL };
char* args2[] = { "ls", "-l", NULL };
char* args3[] = { "sort", NULL, NULL };
char* args4[] = { "wc", "-l", NULL };

int rc1 = execute_cmd(args1, 0);
//printf("rc1 = %d\n", rc1);

int rc2 = execute_cmd(args2, rc1);
//printf("rc2 = %d\n", rc2);

int rc3 = execute_cmd(args3, rc1);
//printf("rc3 = %d\n", rc3);

int rc4 = execute_cmd(args4, rc1);
//printf("rc4 = %d\n", rc4);

int buffer[1024];
int len = 0;

if (rc2) {
    while ((len = read(rc2, buffer, sizeof(buffer))) > 0) {
        write(STDERR_FILENO, "rc2\n", 4);
        write(STDERR_FILENO, &buffer, len);
    }
} else {
    printf(stderr, "ERROR\n");
}

if (rc3) {
    while ((len = read(rc3, buffer, sizeof(buffer))) > 0) {
        write(STDERR_FILENO, "rc3\n", 4);
        write(STDERR_FILENO, &buffer, len);
    }
} else {
    printf(stderr, "ERROR\n");
}

if (rc4) {
    while ((len = read(rc4, buffer, sizeof(buffer))) > 0) {
        write(STDERR_FILENO, "rc4\n", 4);
        write(STDERR_FILENO, &buffer, len);
    }
} else {
    printf(stderr, "ERROR\n");
}

return 0;
}

int execute_cmd(char** args, int fd_in) {

int pipefd[2];
pipe(pipefd);

if (fork() == 0) {
    close(pipefd[0]);

    dup2(pipefd[1], STDOUT_FILENO);
    dup2(pipefd[1], STDERR_FILENO);

    close(pipefd[1]);

    if (fd_in) {
        dup2(fd_in, 0);
    }

    execvp(*args, args);
    printf("failed to execute %s %s", *args, *args[0]);
} else {
    close(pipefd[1]);

    return pipefd[0];

}
}

程序的输出不是确定性的,一旦我看到正确的结果,一旦我看到不同的结果。看起来 dup2 没有像我预期的那样运行,如果我 dup2 多次并且对于从结果文件描述符中读取的每个文件描述符 - 看起来它对复制的文件描述符有影响?

如果它像我在设计上提到的那样工作,我需要为两者使用哪个系统调用?

4

2 回答 2

2

Yes, dup and dup2 create completely equivalent handles to the same pipe. If several processes (or threads) simultaneously attempt to read from the pipe using duplicated/forked descriptors, a "random" of them will get to the data first, but each byte written to the pipe only gets delivered once.

If you want to copy data to multiple different readers, you have to program that explicitly -- fork a subprocess (or spawn a thread) to read some data from one incoming pipe, then write it to all of the outgoing ones, and continue in a loop until you reach EOF.

于 2011-08-07T05:52:54.017 回答
0

对同一个管道有多个句柄/引用会导致很多同步问题等。

例如,如果有 2 个子进程,其中一个发送“Hello\n”然后发送“World\n”,另一个发送“Foo\n”然后发送“Bar\n”;那么你可能会以“ Hello\n World\n Foo\n Bar\n”或“ Hello\n Foo\n World\n Bar”或“ Foo\n Hello\n Bar\n World”等结尾。输出最终是无序的(这将非常混乱)。

解决方案是使用不同的管道。

基本上,当主程序分叉时,它应该创建将成为子进程的 STDOUT 和 STDERR 的新管道。然后主程序将需要从所有新管道的末端读取并(可能)缓冲信息,以便主进程可以按特定顺序将来自子进程的数据发送到它自己的 STDOUT/STDERR - 例如所有第一个孩子的输出,然后是第二个孩子的所有输出,然后是下一个孩子的所有输出,等等。

主程序还可以添加额外的信息并进行一些格式化以使正在发生的事情更清楚。对于上面的示例,您最终可能会得到:

Process A (exit status = 0, OK):
    Hello
    World
Process B (exit status = 1, Failed):
    Foo
    Bar

而不仅仅是:

 Hello
 World
 Foo
 Bar

对于输入(STDIN),我不知道您希望它如何工作。如果没有子进程需要 STDIN(最简单和最可能的情况),那么您可以忽略它。如果每个子进程都需要获取自己的主进程 STDIN 副本,那么您需要为每个子进程创建新管道以用作其 STDIN。

另一种选择是有一个“当前选择的孩子”,这可能会变得更加复杂(特别是如果最终用户需要能够看到所选孩子的输出,因为这意味着实现某种方式在“当前正在显示”的孩子之间切换 - 例如,当用户选择不同的孩子时,清除屏幕并显示该孩子的积压)。

于 2011-08-07T06:33:52.290 回答