C中的多管道

时间:2011-08-07 05:46:12

标签: c exec fork fcntl dup2

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

cmd1 | cmd2 | cmd3

和for:

        |--- 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并且从结果文件描述符中读取每个文件描述符 - 看起来它对复制的文件描述符有影响吗?

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

2 个答案:

答案 0 :(得分:2)

是的,dup和dup2为同一个管道创建完全等效的句柄。如果多个进程(或线程)同时尝试使用重复/分叉描述符从管道读取,它们的“随机”将首先获取数据,但写入管道的每个字节仅传递一次。

如果要将数据复制到多个不同的读取器,则必须对其进行显式编程 - 分叉子进程(或生成线程)以从一个传入管道读取一些数据,然后将其写入所有传出管道,并继续循环,直到你达到EOF。

答案 1 :(得分: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。

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