我的任务是用 c 创建自己的 shell。我将使用 fork()、pipe()、exec() 和 wait() 来实现此目的。我有一个良好的开端,但我对管道的研究越多,我就越困惑。每个通过管道传输到子进程的示例如下所示:
我完全理解这一点。我之前已经实现过。我的问题是这个例子有多简单。在创建 shell 时,我需要两个子进程通过管道相互通信,以便运行“cat file | grep hello”等命令。我可以想象几种方法来做到这一点。这是我的第一个想法:
这似乎不起作用。我可能只是我的代码有缺陷,但我怀疑我的 对管道和文件描述符的理解还不够。我认为由于在 main() 中调用了 pipeline 并且 fd[] 是一个文件变量,因此该策略应该有效。 Linux 手册指出“在 fork() 时,两个内存空间具有相同的内容”。当然,我的子进程可以通过相同的文件描述符访问管道。
我的理解有问题吗?我可以尝试让进程像这样同时运行:
但我不确定为什么会有不同的表现。
问题:如果一个进程写入管道,但没有立即的第二个进程读取该数据,数据会丢失吗?
大多数在线示例表明每个进程都需要关闭不使用的管道末端。然而,偶尔我会看到一个在两个进程中都关闭管道两端的例子:
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
close(fd[0]);
据我所知, dup2 复制了文件描述符,使 2 个打开的文件描述符指向同一个文件。如果我不关闭两者,则 execvp() 继续等待输入并且永远不会退出。这意味着当我完成阅读后,我应该关闭(stdin)。
问题:有 2 个子进程通过管道进行通信,主进程是否需要与管道相关的任何内容,例如 close(fd[0])?
如果一个进程写入管道,但没有立即的第二个进程来读取该数据,数据会丢失吗?没有。
默认情况下,写入管道是一个阻塞操作。也就是说,写入管道将阻止调用进程的执行,直到管道中有足够的空间来写入请求的数据。
读取方有责任排空管道以腾出空间,或关闭管道一侧以表明他们不再希望接收数据。
有 2 个子进程通过管道进行通信,主进程是否需要与管道相关的任何内容,例如 close(fd[0])?
因此,父进程应该关闭管道的两端(在两个分叉之后),因为它没有理由保留这些文件描述符。如果不这样做可能会导致父进程耗尽文件描述符 (涉及的每个进程都将拥有其“自己的文件描述符副本”。
ulimit -n
)。
您对
dup2
的理解似乎是正确的。
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
close(fd[0]);
管道的两端都被关闭,因为在
dup2
之后,通常与stdin
关联的文件描述符现在引用与管道读取端的文件描述符相同的文件描述
。当替换过程映像(
stdin
)退出时,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], (char *) 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], (char *) NULL);
return 1;
}
close(fds[0]);
close(fds[1]);
waitpid(left, NULL, 0);
waitpid(right, NULL, 0);
}