gpt4 book ai didi

c - 使多个管道等待每个进程的返回码

转载 作者:太空宇宙 更新时间:2023-11-04 03:23:34 26 4
gpt4 key购买 nike

我想用execve、dup2、fork、waitpid和pipe函数在C中重现UNIX Shell的管道系统。
对,例如,此命令:/bin/ls -l | /usr/bin/head -2 | /usr/bin/wc由以下人员复制:

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

int add_child_process(char **argv, int in, int out, char **envp)
{
int pid;

pid = fork();
if (pid == 0)
{
if (in != 0)
{
dup2(in, 0);
}
if (out != 1)
{
dup2(out, 1);
}
if (execve(argv[0], argv, envp) == -1)
perror("Execve failed");
}
else
return (pid);


}

int main(int av, char **ag, char **envp)
{
int in;
int pipes[2];
char *argv[3];
int pids[3];

/** Launch first process that read on original stdin (0) and write on out of pipe **/
pipe(pipes);
argv[0] = "/bin/ls";
argv[1] = "-l";
argv[2] = NULL;
pids[0] = add_child_process(argv, 0, pipes[1], envp);
close(pipes[1]);
in = pipes[0];

/** Launch second process that read on in of old pipe and write on out of new pipe **/
pipe(pipes);
argv[0] = "/usr/bin/head";
argv[1] = "-2";
argv[2] = NULL;
pids[1] = add_child_process(argv, in, pipes[1], envp);
close(in);
close(pipes[1]);
in = pipes[0];

/** Launch last process that read on in of old pipe and write on original stdout (1) **/
argv[0] = "/usr/bin/wc";
argv[1] = NULL;
pids[2] = add_child_process(argv, in, 1, envp);
close(in);

/** Wait for all process end to catch all return codes **/
int return_code;
waitpid(pids[0], &return_code, 0);
printf("Process 0 return : %d\n", return_code);
waitpid(pids[1], &return_code, 0);
printf("Process 1 return : %d\n", return_code);
waitpid(pids[2], &return_code, 0);
printf("Process 2 return : %d\n", return_code);
}

功,输出为:
2      11      60
Process 0 return : 0
Process 1 return : 0
Process 2 return : 0

喜欢 echo "/bin/ls -l | /usr/bin/head -2 | /usr/bin/wc" | bash
但是对于像 ping google.com | head -2 | wc这样的阻塞命令,显示如下:
      2      16     145

仍然被封锁。
下面是用这个命令更新的main:
int main(int av, char **ag, char **envp)
{
int in;
int pipes[2];
char *argv[3];
int pids[3];

/** Launch first process that read on original in (0) and write on out of pipe **/
pipe(pipes);
argv[0] = "/bin/ping";
argv[1] = "google.com";
argv[2] = NULL;
pids[0] = add_child_process(argv, 0, pipes[1], envp);
close(pipes[1]);
in = pipes[0];

/** Launch second process that read on in of old pipe and write on out of new pipe **/
pipe(pipes);
argv[0] = "/usr/bin/head";
argv[1] = "-2";
argv[2] = NULL;
pids[1] = add_child_process(argv, in, pipes[1], envp);
close(in);
close(pipes[1]);
in = pipes[0];

/** Launch last process that read on in of old pipe and write on original stdout (1) **/
argv[0] = "/usr/bin/wc";
argv[1] = NULL;
pids[2] = add_child_process(argv, in, 1, envp);
close(in);

/** Wait for all process end to catch all return codes **/
int return_code;
waitpid(pids[0], &return_code, 0);
printf("Process 0 return : %d\n", return_code);
waitpid(pids[1], &return_code, 0);
printf("Process 1 return : %d\n", return_code);
waitpid(pids[2], &return_code, 0);
printf("Process 2 return : %d\n", return_code);
}

我真的不明白为什么会发生这种行为,例如在Bash中: echo "ping google.com | head -2 | wc" | bashshow 2 16 145并且不要卡住。
如何处理阻塞命令?我需要得到我的进程的所有返回代码,以便根据最后的错误代码进行返回。

最佳答案

您的add_child_process()函数应该返回PID;它不返回。如果程序无法执行,它还应该在execve()之后进行错误处理。
第一个解决方案-不够好
有了这些修正(以及其他一些超过我的最低编译警告级别的更改,比如#include <stdio.h>以便在使用之前有一个printf()的声明),我得到了大致的预期输出。
我使用此代码(源文件pc67.c)来测试:

#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

static
int add_child_process(char **argv, int in, int out, char **envp)
{
int pid;

pid = fork();
if (pid == 0)
{
if (in != 0)
{
dup2(in, 0);
}
if (out != 1)
{
dup2(out, 1);
}
execve(argv[0], argv, envp);
abort();
}
return pid;
}

int main(int av, char **ag, char **envp)
{
int in;
int pipes[2];
char *argv[3];
int pids[3];
assert(ag[av] == 0);

/** Launch first process that read on original stdin (0) and write on out of pipe **/
pipe(pipes);
argv[0] = "/bin/ls";
argv[1] = "-l";
argv[2] = NULL;
pids[0] = add_child_process(argv, 0, pipes[1], envp);
close(pipes[1]);
in = pipes[0];

/** Launch second process that read on in of old pipe and write on out of new pipe **/
pipe(pipes);
argv[0] = "/usr/bin/head";
argv[1] = "-2";
argv[2] = NULL;
pids[1] = add_child_process(argv, in, pipes[1], envp);
close(in);
close(pipes[1]);
in = pipes[0];

/** Launch last process that read on in of old pipe and write on original stdout (1) **/
argv[0] = "/usr/bin/wc";
argv[1] = NULL;
pids[2] = add_child_process(argv, in, 1, envp);
close(in);

/** Wait for all process end to catch all return codes **/
int return_code;
waitpid(pids[0], &return_code, 0);
printf("Process 0 return : %d\n", return_code);
waitpid(pids[1], &return_code, 0);
printf("Process 1 return : %d\n", return_code);
waitpid(pids[2], &return_code, 0);
printf("Process 2 return : %d\n", return_code);
}

abort()没有启动;所有命令都执行了(我在运行代码之前检查了它们是否在您列出的目录中)。
我使用GCC 6.3.0在运行macOS Sierra 10.12.4的Mac上编译,命令行是:
$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes \
> -Wstrict-prototypes -Wold-style-definition pc67.c -o pc67
$

然后,我得到输出:
$ ./pc67
2 11 70
Process 0 return : 0
Process 1 return : 0
Process 2 return : 0
$

这11个字是“总计”和块的数目,然后9个字表示 ls -l输出的一行。我在命令前后重新运行了检查 ps的输出;没有杂散进程在运行。
第二种解决方案-规避和不治愈
谢谢,但不像Bash echo "ping google.com | head -2 | wc" | bash,它仍然在等待中被阻塞,并且不会终止程序。
我不清楚你的命令行和你的问题有什么关系。然而,当您引入 ping google.com而不是 ls -l作为命令时,可能会发生很多事情。你不能像那样合法地改变问题的参数。这完全是个新问题。
在shell代理项中,您没有指定程序的路径;您的代码无法处理该路径。(如果您使用 execvp()而不是 execve()—当您只是中继继承的环境时使用 execve()是没有意义的;环境无论如何都是继承的—那么命令的路径将是不相关的)。
然而,使用 "/sbin/ping""google.com"(在程序的副本中)似乎是挂起的。从另一个终端查看设置,我看到:
$ ps -ftttys000
UID PID PPID C STIME TTY TIME CMD
0 51551 51550 0 10:13AM ttys000 0:00.50 login -pf jleffler
502 51553 51551 0 10:13AM ttys000 0:00.14 -bash
502 54866 51553 0 11:10AM ttys000 0:00.01 pc23
502 54867 54866 0 11:10AM ttys000 0:00.00 /sbin/ping google.com
502 54868 54866 0 11:10AM ttys000 0:00.00 (head)
502 54869 54866 0 11:10AM ttys000 0:00.00 (wc)
$

pc23.chead进程都已终止(这些是僵尸的条目),但是 wc进程没有终止。看起来没什么作用,但也没死。
一段时间后,我得到:
$ ps -ftttys000
UID PID PPID C STIME TTY TIME CMD
0 51551 51550 0 10:13AM ttys000 0:00.50 login -pf jleffler
502 51553 51551 0 10:13AM ttys000 0:00.14 -bash
502 54866 51553 0 11:10AM ttys000 0:00.01 pc23
502 54867 54866 0 11:10AM ttys000 0:00.09 /sbin/ping google.com
502 54868 54866 0 11:10AM ttys000 0:00.00 (head)
502 54869 54866 0 11:10AM ttys000 0:00.00 (wc)
$

它使用了9个CPU秒。因此,在这种情况下,出于某种原因, ping没有注意到 ping信号。
如何修复?这需要实验,而且可能更复杂。最简单的解决方法是添加对我有效的选项,比如 SIGPIPE-c。我现在没有动力去寻找替代的解决方案。
3的macOS手册页部分地说:
ping
发送(和接收)计数回显响应数据包后停止。如果未指定此选项, -c count将一直运行到中断。如果此选项与 ping扫描一起指定,则每个扫描将包含计数数据包。
猜测“被打断”这个词有多精确是很有趣的。如果发送一个实际(控制C)中断或一个 ping信号( SIGTERM),程序终止。奇怪的是,它有时在 kill 54867上停止,有时不停止。
第三种解决方案-关闭文件描述符
进一步考虑,问题是代码没有关闭足够的文件描述符。这通常是管道未端接的问题。当 SIGPIPE作为第一个进程时,问题就隐藏起来了,因为 ls会自动终止,关闭散乱的文件描述符。
更改为 ls会暴露问题。这是代码的另一个修订版。它关闭 ping描述符(oops;拍拍自己的手腕)。它还确保通过 dup2()的新 tbc(待关闭)文件描述符参数关闭另一个管道描述符。我还选择了使用 add_child_process()而不是 execv(),从 execve()函数中删除一个参数,并允许 add_child_process(),因为程序不注意它的参数(这允许我丢失 int main(void),这是为了“确保”参数计数和向量参数被“使用”,只要您没有使用 assert()或等效程序进行编译)。
我还调整了命令生成代码,以便从 -DNDEBUG中添加/删除 -c3参数,或者从其他命令中添加/删除参数。
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

static
int add_child_process(char **argv, int in, int out, int tbc)
{
int pid;

pid = fork();
if (pid == 0)
{
if (tbc >= 0)
close(tbc);
if (in != 0)
{
dup2(in, 0);
close(in);
}
if (out != 1)
{
dup2(out, 1);
close(out);
}
execv(argv[0], argv);
abort();
}
return pid;
}

int main(void)
{
int in;
int pipes[2];
char *argv[10];
int pids[3];

/** Launch first process that read on original stdin (0) and write on out of pipe **/
pipe(pipes);
int argn = 0;
argv[argn++] = "/sbin/ping";
//argv[argn++] = "-c";
//argv[argn++] = "3";
argv[argn++] = "google.com";
argv[argn++] = NULL;
pids[0] = add_child_process(argv, 0, pipes[1], pipes[0]);
close(pipes[1]);
in = pipes[0];

/** Launch second process that read on in of old pipe and write on out of new pipe **/
pipe(pipes);
argn = 0;
argv[argn++] = "/usr/bin/head";
argv[argn++] = "-2";
argv[argn++] = NULL;
pids[1] = add_child_process(argv, in, pipes[1], pipes[0]);
close(in);
close(pipes[1]);
in = pipes[0];

/** Launch last process that read on in of old pipe and write on original stdout (1) **/
argn = 0;
argv[argn++] = "/usr/bin/wc";
argv[argn++] = NULL;
pids[2] = add_child_process(argv, in, 1, -1);
close(in);

/** Wait for all process end to catch all return codes **/
int return_code;
waitpid(pids[0], &return_code, 0);
printf("Process 0 return : %d\n", return_code);
waitpid(pids[1], &return_code, 0);
printf("Process 1 return : %d\n", return_code);
waitpid(pids[2], &return_code, 0);
printf("Process 2 return : %d\n", return_code);
}

有了这段代码(可执行的 pingpc73构建),我得到如下输出:
$ ./pc73
2 14 109
Process 0 return : 13
Process 1 return : 0
Process 2 return : 0
$

在出现的 pc73.c输出和另一个输出之间有一个短的、亚秒级的暂停; wc命令在尝试再次写入之前等待一秒钟。 ping的退出状态表明 13确实死于 ping信号。之前的问题是 SIGPIPE仍然打开了管道的读取端,因此它没有得到 ping信号。
要吸取的教训-关闭许多文件描述符
使用管道时,请确保正在关闭所有需要关闭的文件描述符。

关于c - 使多个管道等待每个进程的返回码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43283418/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com