c-管道执行卡在第二个命令上


int main() {
char *cmd1[2] = { "ls", NULL };
char *cmd2[3] = { "grep", "a", NULL };
char *cmd3[3] = { "wc", "-l", NULL };
char *cmd4[5] = { "cat", NULL };
char *cmd5[5] = { "cat", NULL };
int pipe_count = 2;
int pid1, pid2, pid3, pid4, pid5;
int pfd1[2];
int pfd2[2];
pipe(pfd1);
pipe(pfd2);
if ((pid1 = fork()) == 0) {
close(pfd2[0]);
close(pfd2[1]);
close(pfd1[0]);
dup2(pfd1[1], 1);
if (execvp(cmd1[0], cmd1) == -1) {
exit(-1);
}
} else if (pid1 > 0) {
waitpid(pid1, NULL, 0);
}
if ((pid2 = fork()) == 0) {
if (pipe_count >= 2) {
close(pfd1[1]);
close(pfd2[0]);
dup2(pfd1[0], 0);
dup2(pfd2[1], 1);
} else {
close(pfd1[1]);
close(pfd2[0]);
close(pfd2[1]);
dup2(pfd1[0], 0);
}
if (execvp(cmd2[0], cmd2) == -1) {
exit(-1);
}
if (pipe_count == 1) {
printf("n");
return 0;
}
} else if (pid2 > 0) {
waitpid(pid2, NULL, 0);
}
if (pipe_count >= 2) {
if ((pid3 = fork()) == 0) {
if (pipe_count >= 3) {
close(pfd1[0]);
close(pfd2[1]);
dup2(pfd2[0], 0);
dup2(pfd1[1], 1);
} else {
close(pfd1[0]);
close(pfd1[1]);
close(pfd2[1]);
dup2(pfd2[0], 0);
}
if (execvp(cmd3[0], cmd3) == -1) {
exit(-1);
}
if (pipe_count == 2) {
printf("n");
}
} else if (pid3 > 0) {
waitpid(pid3, NULL, 0);
}
}
if (pipe_count >= 3) {
if ((pid4 = fork()) == 0) {
close(pfd1[1]);
close(pfd2[0]);
dup2(pfd1[0], 0);
if (pipe_count == 4)
dup2(pfd2[1], 1);
else
close(pfd2[1]);
if (execvp(cmd4[0], cmd4) == -1) {
exit(-1);
}
} else if (pid4 > 0) {
waitpid(pid4, NULL, 0);
}
}
if (pipe_count == 4) {
if ((pid5 = fork()) == 0) {
close(pfd1[0]);
close(pfd2[1]);
dup2(pfd2[0],0);
close(pfd1[1]);
if (execvp(cmd5[0], cmd5) == -1) {
exit(-1);
}
} else if (pid5 > 0) {
waitpid(pid5, NULL, 0);
}
}
return 0;
}

我正在尝试用管道命令构建一个shell。例如,当我输入ls | grep a | wc -l时,我意识到当我在终端上使用ps f时,程序卡在grep a上。外壳没有响应
当我终止grep a的子进程时,我再次被困在wc -l上,必须再次在终端上终止它
终止进程后,不会打印任何输出(我想要的输出是2(
如有任何帮助,我们将不胜感激。

正如在评论中已经诊断的那样,原始代码存在许多问题,包括:

  • 最可能的问题是,您的父进程在等待子进程死亡之前没有关闭管道,因此子进程不会得到EOF,也不会终止(这是问题之一,但远不是唯一的问题。(

  • 如果有N个进程要运行,则需要N-1个管道。这里只有两根管子;在只用两根管子就能工作之前,你还有很多工作要做。N=2的情况仍然有特殊情况:第一个和最后一个过程需要与处理过程2.N-1的方式有所不同。

  • 您还需要同时运行管道中的进程。控制过程不应等待任何子进程,直到整个管道启动。这是因为,例如,进程P1可能生成如此多的数据,以至于它填充了将其连接到进程P2的管道缓冲区,此时它将阻止等待P2读取一些数据。但如果P2还没有启动,P1就永远不会被阻塞,因此管道将不会取得任何进展。您需要重新考虑等待代码和管道代码。您最终会关闭许多文件描述符。

  • 您没有关闭足够的文件描述符经验法则:如果将管道的一端dup2()连接到标准输入或标准输出,请尽快关闭pipe()中的两个原始文件描述符。特别是,这意味着在使用任何exec*()函数族之前。该规则也适用于dup()fcntl()F_DUPFD

  • 请注意,不需要测试exec*()函数族的返回值。如果他们成功了,他们就不会回来;如果他们回来了,他们就失败了。我认为在大多数情况下,在exec*()调用失败后退出之前生成一条错误消息是一种很好的做法。

将这些观察结果放在一起会产生这样的代码:

/* SO 7412-0402 */
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stderr.h>
int main(void)
{
char *cmd1[2] = { "ls", NULL };
char *cmd2[3] = { "grep", "a", NULL };
char *cmd3[3] = { "wc", "-l", NULL };
char *cmd4[5] = { "cat", NULL };
char *cmd5[5] = { "cat", NULL };
int pid1, pid2, pid3, pid4, pid5;
int pfd1[2];
int pfd2[2];
int pfd3[2];
int pfd4[2];
err_setarg0("pipe61");
err_setlogopts(ERR_PID | ERR_MILLI);
err_remark("Parent processn");
if (pipe(pfd1) != 0 ||
pipe(pfd2) != 0 ||
pipe(pfd3) != 0 ||
pipe(pfd4) != 0)
err_syserr("failed to create a pipe: ");
if ((pid1 = fork()) < 0)
err_syserr("failed to fork(): ");
if (pid1 == 0)
{
err_remark("Child process 1n");
dup2(pfd1[1], 1);
close(pfd1[0]); close(pfd1[1]);
close(pfd2[0]); close(pfd2[1]);
close(pfd3[0]); close(pfd3[1]);
close(pfd4[0]); close(pfd4[1]);
execvp(cmd1[0], cmd1);
err_syserr("failed to execute '%s': ", cmd1[0]);
}
if ((pid2 = fork()) < 0)
err_syserr("failed to fork(): ");
else if (pid2 == 0)
{
err_remark("Child process 2n");
dup2(pfd1[0], 0);
dup2(pfd2[1], 1);
close(pfd1[0]); close(pfd1[1]);
close(pfd2[0]); close(pfd2[1]);
close(pfd3[0]); close(pfd3[1]);
close(pfd4[0]); close(pfd4[1]);
execvp(cmd2[0], cmd2);
err_syserr("failed to execute '%s': ", cmd2[0]);
}
if ((pid3 = fork()) < 0)
err_syserr("failed to fork(): ");
else if (pid3 == 0)
{
err_remark("Child process 3n");
dup2(pfd2[0], 0);
dup2(pfd3[1], 1);
close(pfd1[0]); close(pfd1[1]);
close(pfd2[0]); close(pfd2[1]);
close(pfd3[0]); close(pfd3[1]);
close(pfd4[0]); close(pfd4[1]);
execvp(cmd3[0], cmd3);
err_syserr("failed to execute '%s': ", cmd3[0]);
}
if ((pid4 = fork()) < 0)
err_syserr("failed to fork(): ");
else if (pid4 == 0)
{
err_remark("Child process 4n");
dup2(pfd3[0], 0);
dup2(pfd4[1], 1);
close(pfd1[0]); close(pfd1[1]);
close(pfd2[0]); close(pfd2[1]);
close(pfd3[0]); close(pfd3[1]);
close(pfd4[0]); close(pfd4[1]);
execvp(cmd4[0], cmd4);
err_syserr("failed to execute '%s': ", cmd4[0]);
}
if ((pid5 = fork()) < 0)
err_syserr("failed to fork(): ");
else if (pid5 == 0)
{
err_remark("Child process 5n");
dup2(pfd4[0], 0);
close(pfd1[0]); close(pfd1[1]);
close(pfd2[0]); close(pfd2[1]);
close(pfd3[0]); close(pfd3[1]);
close(pfd4[0]); close(pfd4[1]);
execvp(cmd5[0], cmd5);
err_syserr("failed to execute '%s': ", cmd5[0]);
}
close(pfd1[0]); close(pfd1[1]);
close(pfd2[0]); close(pfd2[1]);
close(pfd3[0]); close(pfd3[1]);
close(pfd4[0]); close(pfd4[1]);
int status;
int corpse;
while ((corpse = wait(&status)) > 0)
printf("%d: child %d exited with status 0x%.4Xn", getpid(), corpse, status);
return 0;
}

注意,pid2pid3pid4的块几乎相同;pid1的块只将管道描述符复制到stdout,而pid5的块只会将管道描述符重复到stdin。

错误报告例程的代码在GitHub上的SOQ(堆栈溢出问题(存储库中以文件stderr.cstderr.h的形式存在于src/libsoq子目录中。Linux和BSD上的err(3)函数具有相似的功能,但函数名称不同。

这是根据上面所示的源代码pipe61.c编译的程序pipe61的示例运行的输出

pipe61: 2022-10-19 23:52:03.833 - pid=50391: Parent process
pipe61: 2022-10-19 23:52:03.834 - pid=50392: Child process 1
pipe61: 2022-10-19 23:52:03.834 - pid=50393: Child process 2
pipe61: 2022-10-19 23:52:03.834 - pid=50394: Child process 3
pipe61: 2022-10-19 23:52:03.834 - pid=50395: Child process 4
pipe61: 2022-10-19 23:52:03.834 - pid=50396: Child process 5
50391: child 50392 exited with status 0x0000
50391: child 50393 exited with status 0x0000
16
50391: child 50394 exited with status 0x0000
50391: child 50395 exited with status 0x0000
50391: child 50396 exited with status 0x0000

显然,这段代码不容易配置为处理管道中的6个或多个阶段,除非通过cut‘n‘paste编程,也不容易从管道中删除任何阶段(变量重命名(。对于"真实"代码,您需要使用阵列驱动的方法来避免不必要的代码重复。您可以将管道描述符存储在一个数组中;您将PID存储在一个数组中。您可能有一个指向命令参数列表的指针数组——三星级编程。您可能会使用一个函数来启动第N个子进程。

相关内容

  • 没有找到相关文章

最新更新