C Exeve和管道问题-如何恢复原始管道?
我一直在做一个简单的外壳,它可以做管道 下面是一些用于操作管道语法的代码C Exeve和管道问题-如何恢复原始管道?,c,linux,unix,pipe,dup,C,Linux,Unix,Pipe,Dup,我一直在做一个简单的外壳,它可以做管道 下面是一些用于操作管道语法的代码 int fd[2]; int stdin_copy; int stdout_copy; int status; char * msg; if (pipe(fd) == -1) { perror("pipe"); exit(1); } // fd[0] : process read from fd[0] // fd[1] : process write to fd[1] if (execok(pr_wo
int fd[2];
int stdin_copy;
int stdout_copy;
int status;
char * msg;
if (pipe(fd) == -1) {
perror("pipe");
exit(1);
}
// fd[0] : process read from fd[0]
// fd[1] : process write to fd[1]
if (execok(pr_words) == 0) { /* is it executable? */
status = fork(); /* yes; create a new process */
if (status == -1) { /* verify fork succeeded */
perror("fork");
exit(1);
} else if (status == 0) { /* in the child process... */
stdout_copy = dup(1);
close(1); // close standard output
dup(fd[1]);
close(fd[0]);
close(fd[1]); // close and fd[1] will be stdout
pr_words[l_nwds] = NULL; /* mark end of argument array */
status = execve(path, pr_words, environ); /* try to execute it */
perror("execve"); /* we only get here if */
exit(0); /* execve failed... */
}
/*------------------------------------------------*/
/* The parent process (the shell) continues here. */
/*------------------------------------------------*/
else if (status > 0) { // in the parent process....
wait( & status); /* wait for process to end */
if (execok(af_words) == 0) {
if (pipe(fd2) == -1) {
perror("pipe");
exit(1);
}
status = fork();
if (status == -1) {
perror("fork");
exit(1);
} else if (status == 0) { // in the child process...
stdin_copy = dup(0);
close(0);
dup(fd[0]);
close(fd[1]);
close(fd[0]);
read(fd[0], readbuffer, sizeof(readbuffer));
af_words[r_nwds] = NULL; /* mark end of argument array */
status = execve(path, af_words, environ); /* try to execute it */
} else if (status > 0) {
wait( & status);
msg = "over";
write(2, msg, strlen(msg));
close(fd[0]);
close(fd[1]);
dup2(stdin_copy, 0);
dup2(stdout_copy, 1);
close(stdin_copy);
close(stdout_copy);
printf("%s", "hi");
}
} else {
/*----------------------------------------------------------*/
/* Command cannot be executed. Display appropriate message. */
/*----------------------------------------------------------*/
msg = "*** ERROR: '";
write(2, msg, strlen(msg));
write(2, af_words[0], strlen(af_words[0]));
msg = "' cannot be executed.\n";
write(2, msg, strlen(msg));
}
}
} else {
/*----------------------------------------------------------*/
/* Command cannot be executed. Display appropriate message. */
/*----------------------------------------------------------*/
msg = "*** ERROR: '";
write(2, msg, strlen(msg));
write(2, pr_words[0], strlen(pr_words[0]));
msg = "' cannot be executed.\n";
write(2, msg, strlen(msg));
}
pr_字和af_字是包含命令、管道右侧和左侧的二维指针。(例如ls | cat->pr_words=“ls\0”,af_words=“cat\0”)
首先,我使用fork()创建子进程,并为标准输出注册fd[1]。(并在关闭stdin之前保存stdin文件描述符)并在执行命令左侧后,进行其他子进程来处理命令右侧
类似地,我在关闭标准输出之前保存了标准输出文件描述符,并进行了fd[0]标准输入。通过使用execve函数的第一个结果的输入,我认为每个结果都会保存在fd[1]中。(因为它当前已注册为std输出)
最后,将管道输入和输出恢复为标准输出。(我不想使用dup2,但由于缺乏知识,我别无选择)
但是,在执行此代码时,在输入'ls | cat'后,没有输出。此外,我设置终端的每个条目都将打印“#”。(这意味着“#ls”或“#cat”…)但在输入上述管道命令后,该程序甚至不打印“#”
我猜这个程序的输入和输出流在处理管道命令后完全扭曲了
我怎样才能修好它?我的意思是,我希望将第一次执行的结果保存到fd[1]中,并在使用此fd[1]执行第二次执行后,通过标准输出文件描述打印最终结果 我发现您的代码至少存在一些问题: 首先,您不应该在启动第二个进程之前等待第一个进程()。管道中只有几KB的缓冲区,在此之后,如果第一个子进程试图继续在那里写入,shell将挂起。您需要先启动两个孩子,然后再等待他们中的每一个。只需将第一个等待(&status)呼叫向下移动到另一个呼叫旁边即可。您可能希望稍后使用waitpid或其他方法,以便知道哪个先完成,哪个状态进入哪个状态,但一旦基本工作开始,您就可以解决这个问题 其次,当您使用fork()时,会复制程序中的所有变量和文件描述符映射。因此,您不需要在任一子进程中保存stdin或stdout,因为您在子进程中所做的任何更改都不会影响父进程。此外,由于只在子进程中初始化stdin_copy和stdout_copy,因此在第二个fork()之后在父进程中使用的变量的版本将被取消初始化。这就是导致父shell的I/O在执行此代码后出错的原因。在第二次分叉之后,实际上不需要在父级中执行任何操作来维护原始的stdin和stdout——在此之前,您永远不会在该过程中更改它们。您可能希望从post fork父代码中删除所有这些内容:
close(fd[0]);
close(fd[1]);
dup2(stdin_copy, 0);
dup2(stdout_copy, 1);
close(stdin_copy);
close(stdout_copy);
第三,为什么在第二个孩子中调用execve()之前要阅读管道?这只会将数据从管道中剥离出来,而您的执行孩子将永远看不到这些数据。这可能是导致管道本身无法工作的原因。您可能希望删除以下内容:
read(fd[0], readbuffer, sizeof(readbuffer));
最后,这一行可能需要放在execok()调用之前(对于另一个类似的调用也是如此):
代码的框架应该如下所示,不进行错误处理和execok检查,如果您想知道哪个状态代码是哪个子级的,则演示waitpid()的用法:
int child_pid[2];
child_pid[0] = fork();
if (child_pid[0] == 0) {
// first child, close stdout and replace with pipe, then exec
} else {
child_pid[1] = fork();
if (child_pid[1] == 0) {
// second child, close stdin and replace with pipe, then exec
} else {
// parent, and now we have the pids of the children
waitpid(child_pid[0], &status, 0); // wait for first child
waitpid(child_pid[1], &status, 0); // wait for second child
// *do not* mess with stdin/stdout, they are okay here
}
}
我真的采纳了你的建议。然而,在阅读了你的答案之后,我还有一个问题。除了第一个,其他一切都是可以理解的。要连续编写fork()函数吗?status1=fork();status2=fork();像这样吗?不,不要修改调用fork()的方式,这一部分很好。您需要将fork()之间的wait(&status)调用移动到这两个fork()之后,因此在流程结束时,您将连续两次调用wait(&status)。
int child_pid[2];
child_pid[0] = fork();
if (child_pid[0] == 0) {
// first child, close stdout and replace with pipe, then exec
} else {
child_pid[1] = fork();
if (child_pid[1] == 0) {
// second child, close stdin and replace with pipe, then exec
} else {
// parent, and now we have the pids of the children
waitpid(child_pid[0], &status, 0); // wait for first child
waitpid(child_pid[1], &status, 0); // wait for second child
// *do not* mess with stdin/stdout, they are okay here
}
}