我正在编写一个程序将一个命令通过管道传输到另一个命令。输入将来自命令行:
$ ./a.out ls '|' wc
c2 PID 6804
c1 PID 6803
PARENT PID 6802
$ 2 2 17
为什么提示返回后会打印输出。有什么办法可以防止这种情况发生吗?
这是我写的代码:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char * argv[])
{
if(argc <= 1 )
{
printf("ERROR: No arguments passed\n");
printf("USAGE: ./pipe <command 1> | <command 2>\n");
return 1;
}
char * cmd1[50];
char * cmd2[50];
int cmd1_arg = 0;
int cmd2_arg = 0;
int pipe_num = 0;
for(int cla = 1; cla<argc; cla++)
{
if( !strcmp(argv[cla],"|") )
pipe_num++;
else if(pipe_num == 0)
cmd1[cmd1_arg++] = argv[cla];
else if(pipe_num == 1)
cmd2[cmd2_arg++] = argv[cla];
}
cmd1[cmd1_arg] = (char *)NULL;
cmd2[cmd2_arg] = (char *)NULL;
if(pipe_num != 1)
{
printf("ERROR: Insufficient arguments passed\n");
printf("USAGE: ./pipe <command 1> | <command 2>\n");
return 1;
}
int pipe_fd[2];
pipe(pipe_fd);
pid_t pid = fork();
if(pid == -1)
{
perror("FORK FAILED");
return 1;
}
if(pid != 0)
{
pid_t cmd_pid = fork();
if(cmd_pid == -1)
{
perror("FORK FAILED");
return 1;
}
if(cmd_pid != 0)
{
waitpid(pid,NULL,0);
waitpid(cmd_pid,NULL,WNOHANG);
printf("PARENT PID %d\n",getpid());
}
if(cmd_pid == 0)
{
printf("c2 PID %d\n",getpid());
close(pipe_fd[1]);
int stdin_fd = dup(0);
close(0);
dup(pipe_fd[0]);
if(execvp(cmd2[0],cmd2) == -1 ) perror("CMD2 FAIL");
close(0);
dup(stdin_fd);
}
}
if(pid == 0)
{
printf("c1 PID %d\n",getpid());
close(pipe_fd[0]);
int stdout_fd = dup(1);
close(1);
int test = dup(pipe_fd[1]);
if( execvp(cmd1[0],cmd1) == -1 ) perror("CMD1 FAIL");
close(1);
dup(stdout_fd);
}
return 0;
}
答案1
你有:
waitpid(cmd_pid,NULL,WNOHANG);
通过包含该WNOHANG
选项,您可以告诉您,waitpid()
如果进程尚未终止,则不要等待进程终止。
我的猜测是您添加了它,因为如果您不包含它,您的程序就会挂起。这是因为原始父进程仍然有一个指向管道写入端的打开文件描述符,因此读取进程仍然被阻塞,等待该管道上的输入。
这是程序的修订版本,它关闭管道文件描述符,并且不使用WNOHANG
.
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
if (argc <= 1) {
printf("ERROR: No arguments passed\n");
printf("USAGE: ./pipe <command 1> | <command 2>\n");
return 1;
}
char *cmd1[50];
char *cmd2[50];
int cmd1_arg = 0;
int cmd2_arg = 0;
int pipe_num = 0;
for (int cla = 1; cla < argc; cla++) {
if (!strcmp(argv[cla], "|")) {
pipe_num++;
} else if (pipe_num == 0) {
cmd1[cmd1_arg++] = argv[cla];
} else if (pipe_num == 1) {
cmd2[cmd2_arg++] = argv[cla];
}
}
cmd1[cmd1_arg] = NULL;
cmd2[cmd2_arg] = NULL;
if (pipe_num != 1) {
printf("ERROR: Insufficient arguments passed\n");
printf("USAGE: ./pipe <command 1> | <command 2>\n");
return 1;
}
int pipe_fd[2];
if (pipe(pipe_fd) < 0) {
perror("pipe");
return 1;
}
const pid_t pid = fork();
if (pid < 0) {
perror("fork");
return 1;
} else if (pid != 0) {
const pid_t cmd_pid = fork();
if (cmd_pid < 0) {
perror("fork");
return 1;
} else if (cmd_pid != 0) {
printf("PARENT PID %d\n", getpid());
close(pipe_fd[0]);
close(pipe_fd[1]);
if (waitpid(pid, NULL, 0) < 0) {
perror("waitpid");
}
if (waitpid(cmd_pid, NULL, 0) < 0) {
perror("waitpid");
}
} else {
printf("c2 PID %d\n", getpid());
if (dup2(pipe_fd[0], STDIN_FILENO) < 0) {
perror("dup2");
return 1;
}
close(pipe_fd[0]);
close(pipe_fd[1]);
if (execvp(cmd2[0], cmd2) < 0) {
perror("CMD2 FAIL");
return 1;
}
}
} else {
printf("c1 PID %d\n", getpid());
if (dup2(pipe_fd[1], STDOUT_FILENO) < 0) {
perror("dup2");
return 1;
}
close(pipe_fd[0]);
close(pipe_fd[1]);
if (execvp(cmd1[0], cmd1) < 0) {
perror("CMD1 FAIL");
return 1;
}
}
return 0;
}
运行这个程序给我:
./a.out echo 1 2 3 '|' wc
PARENT PID 20412
c1 PID 20413
c2 PID 20414
1 3 6
$
答案2
您的原始进程(子进程的父进程)运行以下命令:
waitpid(pid,NULL,0);
waitpid(cmd_pid,NULL,WNOHANG);
printf("PARENT PID %d\n",getpid());
也就是说,它等待第一个子进程退出,进行系统调用来检查第二个子进程是否退出,但实际上WNOHANG
并没有等待它。无论如何,该检查是没有意义的,因为程序不使用 的返回值waitpid()
。然后父进程打印它的 PID,并继续执行该函数,通过其他两个if
语句,并通过 退出return 0
。
此时,第二个子进程可能仍在运行,也可能不运行。如果没有显式同步,就无法保证事情发生的顺序。此外,shell 不知道程序启动的子进程,也没有等待它们的机制。主要流程必须wait*()
为他们服务。
在继续编写代码之前,请摆脱混乱的嵌套 if 语句,并投资一些更清晰的结构。简化此类代码的常用方法是让子进程立即调用函数,_exit()
或者在函数返回后立即调用。现在,如果您的exec*()
调用失败,子级也会继续执行相同的代码而不是退出。