C 使用每个进程的等待返回代码创建多个管道
我想使用execve、dup2、fork、waitpid&pipe函数在C中重现UNIX Shell的管道系统 右,例如,此命令:C 使用每个进程的等待返回代码创建多个管道,c,unix,pipe,fork,dup,C,Unix,Pipe,Fork,Dup,我想使用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> in
/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);
}
像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”| Bash
show2 16 145
,不要卡住
如何处理阻塞命令?我需要根据上一个错误代码获取我的进程的所有返回代码以进行返回。您的
add\u child\u process()
函数应该返回PID;没有。如果程序无法执行,它还应该在执行execve()之后进行错误处理
第一个解决方案-不够好
有了这些修复(以及其他各种更改,以超过我的最低编译警告级别,例如#include
,以便在使用printf()
之前有一个声明),我大致得到了预期的输出
我使用此代码(源文件pc67.c
)测试:
这样,我就得到了输出:
$ ./pc67
2 11 70
Process 0 return : 0
Process 1 return : 0
Process 2 return : 0
$
这11个字是“总计”和块数,然后是一行ls-l
输出的9个字。我已在命令前后重新运行检查ps
的输出;没有正在运行的游离进程
第二种解决方案——规避而非治愈
谢谢,但与Bashecho“ping google.com | head-2 | wc”| Bash
不同,它仍然在等待中被阻止,并且不会终止程序
我不清楚你的替代命令行和你的问题有什么关系。然而,当您引入ping google.com
而不是ls-l
作为命令时,可能会发生很多事情。你不能这样合法地改变你问题的参数。这完全是一个新问题
在shell代理中,您没有指定程序的路径;你的代码无法处理这个问题。(如果您使用execvp()
而不是execve()
——当您只是中继继承的环境时,使用execve()
是毫无意义的;环境仍然是继承的——那么到命令的路径将是无关的)
然而,使用“/sbin/ping”
和“google.com”
(在程序pc23.c
的副本中)似乎是挂起的。从另一个终端查看设置,我看到:
$ 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)
$
head
和wc
进程都已终止(这些是僵尸的条目),但ping
进程并未消亡。它似乎做的不多,但它也不会死
一段时间后,我得到:
$ 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
不关注SIGPIPE
信号
如何修复?这需要实验,可能更复杂。最简单的修复方法是添加对我有效的选项,如-c
和3
。我现在没有动力去寻找替代的修复方法
ping
的macOS手册页部分说明:
-c计数
发送(和接收)计数回显响应数据包后停止。如果未指定此选项,ping
将一直运行,直到中断。如果此选项与ping
sweep一起指定,则每次扫描将由计数数据包组成
推测“中断”一词的精确性是很有趣的。如果发送了实际(Control-C)中断或SIGTERM
信号(kill 54867
),则程序终止。奇怪的是,它有时会在SIGPIPE上停止,有时则不会
第三种解决方案-关闭文件描述符
进一步考虑,问题是代码没有关闭足够的文件描述符。这是管道未终止时最常见的问题。当ls
作为第一个进程时,问题就隐藏了,因为ls
会自动终止,关闭散乱的文件描述符。
更改为ping
会暴露问题。这是代码的另一个修订版。它关闭dup2()
描述符(oops;将self拍打在手腕上)。它还确保通过add\u child\u process()
的新tbc
(待关闭)文件描述符参数关闭另一个管道描述符。我还选择使用execv()
而不是execve()
,从add\u child\u process()
函数中删除一个参数,并允许int main(void)
,因为程序不注意其参数(这允许我丢失assert()
,只要您没有使用-DNDEBUG
或等效工具进行编译,它就可以“确保”参数计数和向量参数被“使用”
我还调整了命令构建代码,以便可以轻松地从ping
添加/删除-c
和3
参数,或者从其他命令添加/删除参数
#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);
}
出现的wc
输出和另一个输出之间有一个短的、亚秒的暂停;ping
命令在尝试再次写入之前等待一秒钟。13
的退出状态表明ping
确实是由SIGPIPE
信号造成的。前面的问题是,ping
仍然打开管道的读取端,因此它没有获得SIGPIPE
$ 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)
$
$ 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)
$
#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);
}
$ ./pc73
2 14 109
Process 0 return : 13
Process 1 return : 0
Process 2 return : 0
$