使用管道跨 child 个进程进行通信

Communicating across child processes with a pipe

我的任务是用 c 语言创建自己的 shell。我将使用 fork()、pipe()、exec() 和 wait() 来实现这一点。我有一个好的开始,但是我对管道的研究越多,我就越困惑。 child 进程的每个管道示例如下所示:

这个我完全理解。我以前实施过。我的问题是这个例子有多简单。在创建 shell 时,我需要两个 children 通过管道相互通信,以便 运行 像“cat file | grep hello”这样的命令。我可以想象有几种方法可以做到这一点。这是我的第一个想法:

这似乎不起作用。我可能只是因为我的代码有缺陷,但我怀疑我的 对管道和文件描述符的理解不够。我认为由于 pipe 在 main() 中被调用并且 fd[] 是一个文件变量,所以这个策略应该有效。 Linux 手册指出“在 fork() 时,两个内存空间具有相同的内容。”当然,我的 child 进程可以通过相同的文件描述符访问管道。

我的理解有问题吗?我可以尝试使进程 运行 像这样同时进行:

但我不确定为什么这会有所不同。

问题:如果一个进程写入管道,但没有第二个进程立即读取该数据,数据会丢失吗?

大多数在线示例表明每个进程都需要关闭它未使用的管道末端。但是,偶尔我会看到一个在两个进程中关闭管道两端的例子:

close(fd[1]);
dup2(fd[0], STDIN_FILENO);
close(fd[0]);

据我所知,dup2 复制了文件描述符,使 2 个打开的文件描述符指向同一个文件。如果我不关闭两者,那么 execvp() 将继续等待输入并且永远不会退出。这意味着当我完成阅读后,我应该关闭(stdin)。

问题:2 children 通过管道通信,主进程是否需要管道的任何东西,例如 close(fd[0])?

If a process writes to a pipe, but there is no immediate second process to read that data, does the data get lost?

没有

默认,写入管道是一个阻塞操作。也就是说,写入管道将阻止调用进程的执行,直到管道中有足够的空间来写入请求的数据。

读取端有责任排空管道以腾出空间,或者关闭管道的一侧以表示他们不再希望接收数据。

With 2 children communicating over a pipe, does the main process need anything with the pipe, such as close(fd[0])?

所涉及的每个进程都将有自己的文件描述符副本

因此,父进程应该关闭管道的两端(在两个分支之后),因为它没有理由保留这些文件描述符。如果不这样做,可能会导致父进程 运行 耗尽文件描述符 (ulimit -n)。


您对 dup2 的理解似乎是正确的。

close(fd[1]);
dup2(fd[0], STDIN_FILENO);
close(fd[0]);

管道的两端都关闭了,因为在 dup2 之后,通常与 stdin 关联的文件描述符现在指的是与文件相同的 文件描述 管道读取端的描述符。

stdin 当然是closed closed 当替换过程映像(exec*)退出时。


你的第二个分叉两个进程的例子,他们 运行 同时,是正确的理解。

在您典型的 shell 中,同时通过管道命令 运行。否则,如前所述,编写器可能会在完成其任务之前填充管道和块。

通常,父级等待两个进程完成。


这是一个玩具示例。 运行 作为 ./program FILE STRING 模拟 cat FILE | grep STRING.

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

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

    pipe(fds);

    int left = fork();

    if (0 == left) {
        close(fds[0]);
        dup2(fds[1], fileno(stdout));
        close(fds[1]);
        execlp("cat", "cat", argv[1], NULL);
        return 1;
    }

    int right = fork();

    if (0 == right) {
        close(fds[1]);
        dup2(fds[0], fileno(stdin));
        close(fds[0]);
        execlp("grep", "grep", argv[2], NULL);
        return 1;
    }

    close(fds[0]);
    close(fds[1]);

    waitpid(left, NULL, 0);
    waitpid(right, NULL, 0);
}