【发布时间】:2021-08-23 14:41:45
【问题描述】:
我正在尝试实现一个必须能够通过管道传输命令的 minishell。
最初,我是按顺序执行进程,也就是说,我在等待nth 进程终止,然后再启动n + 1th 进程(这是执行ls | wc -l 的最小可重现示例):
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
pid_t family[2];
int pipefd[2];
extern char **environ;
void ft_exec(int i)
{
char **ls = malloc(sizeof(char *) * 2);
ls[0] = "/bin/ls";
ls[1] = NULL;
char **wc = malloc(sizeof(char *) * 3);
wc[0] = "/usr/bin/wc";
wc[1] = "-l";
wc[2] = NULL;
printf("ENTERED BY %d\n", getpid());
if (i == 0)
{
dup2(pipefd[1], 1);
execve(ls[0], ls, environ);
}
if (i == 1)
{
dup2(pipefd[0], 0);
execve(wc[0], wc, environ);
}
}
void ft_interpret(void)
{
int i;
i = 0;
while (i < 2)
{
family[i] = fork();
printf("CREATED %d INSIDE %d\n", family[i], getpid());
if (family[i] == 0)
ft_exec(i);
waitpid(family[i], NULL, 0);
printf("TERMINATED %d IN %d\n", family[i], getpid());
if (i == 0)
{
printf("CLOSED W%d\n", pipefd[1]);
close(pipefd[1]);
}
else if (i == 1)
{
printf("CLOSED R%d\n", pipefd[0]);
close(pipefd[0]);
}
i++;
}
}
int main(int argc, char **argv)
{
(void)argc;
(void)argv;
pipe(pipefd);
printf("PARENT ID IS %d\n", getpid());
printf("CREATED PIPE WITH FDS R%d AND W%d\n", pipefd[0], pipefd[1]);
ft_interpret();
}
但是,我意识到shell不是这样工作的,如果给出cat /dev/urandom | head -c 100这样的命令,程序就会陷入死锁。
所以我决定先 fork 进程,然后无论哪个进程退出,我都会关闭相关的管道写入端。
由于某种原因,读取命令(在这种情况下为wc;如果我们将参数更改为非读取命令,例如printenv,它将正确执行)在读取时卡住了,即使主进程也是如此已关闭管道的写入端,并且必须向 wc 发送 EOF。
这是修改后的代码:
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
pid_t family[2];
int pipefd[2];
extern char **environ;
void ft_exec(int i)
{
char **ls = malloc(sizeof(char *) * 2);
ls[0] = "/bin/ls";
ls[1] = NULL;
char **wc = malloc(sizeof(char *) * 3);
wc[0] = "/usr/bin/wc";
wc[1] = "-l";
wc[2] = NULL;
printf("ENTERED BY %d\n", getpid());
if (i == 0)
{
dup2(pipefd[1], 1);
execve(ls[0], ls, environ);
}
if (i == 1)
{
dup2(pipefd[0], 0);
execve(wc[0], wc, environ);
}
}
void ft_block_main_process(void)
{
int i;
pid_t terminated;
i = 0;
while (i < 2)
{
terminated = waitpid(-1, NULL, 0);
printf("TERMINATED %d IN %d\n", terminated, getpid());
if (terminated == family[0])
{
printf("CLOSED W%d\n", pipefd[1]);
close(pipefd[1]);
}
else if (terminated == family[1])
{
printf("CLOSED R%d\n", pipefd[0]);
close(pipefd[0]);
}
i++;
}
}
void ft_interpret(void)
{
int i;
i = 0;
while (i < 2)
{
family[i] = fork();
printf("CREATED %d INSIDE %d\n", family[i], getpid());
if (family[i] == 0)
ft_exec(i);
i++;
}
ft_block_main_process();
}
int main(int argc, char **argv)
{
(void)argc;
(void)argv;
pipe(pipefd);
printf("PARENT ID IS %d\n", getpid());
printf("CREATED PIPE WITH FDS R%d AND W%d\n", pipefd[0], pipefd[1]);
ft_interpret();
}
为什么会这样?
【问题讨论】:
-
您不能“发送 EOF”。 EOF 不是出现在管道上的东西。一旦管道的所有写入端都关闭,任何尝试从管道读取的读取器都将无法获取数据。
read调用将返回一个在某些(许多)语言中称为“EOF”的值。但是 EOF 不是出现(或“发送到”)管道的东西。 -
毫无疑问,您将打开管道的一些写入端。仔细检查您的代码并确保所有末端都在正确的位置关闭。
-
我建议您尝试简化您的代码,或者可能使用空的
main函数从头开始。然后你一个简单的一点点,测试它并确保它有效。一旦它在没有警告的情况下构建(启用了详细的额外警告)并通过了所有测试,那么您将继续下一个小而简单的部分。并且不要使用循环或过于通用的函数。这本身会大大简化,也可以更容易地单独测试每个位。 -
在
execwc 之前,您永远不会关闭管道的写入端,因此wc正在等待该管道关闭。它自己被阻止了。 -
dup2的标准样板包括一个关闭。它应该几乎总是dup2(a, b); close(a)(您可以使用close(b); dup2(a,b); close(a);,但不需要初始关闭)