分叉进程后执行不会 return 程序完成前的结果
Exec after forking a process doesn't return the result before the program finishes
我正在尝试创建一个程序,该程序会分叉一次,当父进程等待子进程终止时,该子进程再次分叉,然后执行两个 exec。程序上有一个 Pipe,我已经检查了程序上每个 dup2() 和 pipe() 的 return 值——只是在这里省略它们以使其看起来更简洁——。问题是我只得到 ls -a | 的结果sort -r 在程序完成后。
代码是:
#include <cstdio>
#include <cstring>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
int main(int argc, char *argv[]) {
printf("Shell> \n"); fflush(stdout);
pid_t pid1;
pid_t pid2;
int status = 0;
int fd[2];
if(pipe(fd) < 0) {
printf("FATAL ERROR.\n");
}
pid1 = fork();
if(pid1 > 0) { // Parent
waitpid(pid1, &status, 0);
printf("\t\t------PID1 Complete-------\n\n");
}
else { // Child
if(pid1 == 0) {
printf("ON CHILD\n");
pid2 = fork();
if(pid2 > 0) { // Child -> Parent
printf("ON CHILD-Parent\n");
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
waitpid(pid2, &status, 0);
printf("ON CHILD-Parent after wait\n");
execlp("sort", "sort", "-r", NULL);
perror("Problem with execlp\n");
exit(1);
}
else { // Child -> Child
printf("ON CHILD->Child\n");
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
execlp("ls", "ls", "-a", NULL);
perror("Problem with execvp\n");
exit(1);
}
} // End of if(pid1 == 0)
} // End of Child
printf("\nEnd of program.\n");
return 0;
}
我当前的输出是:
Shell>
ON CHILD
ON CHILD-Parent
ON CHILD->Child
ON CHILD-Parent after wait
我认为问题出在等待中,但我就是不知道如何解决这个问题。有任何想法吗?谢谢!
问题是你在grandparent进程中调用了pipe
。在孙进程 (ls -a) 退出后,parent 进程 (sort -r) 无限期地阻塞等待从管道读取更多输入,因为某些进程 - grandparent - 持有一个打开的描述符管道的写入端。
如果您关闭 grandparent 进程中的管道描述符,或者更好的是将 pipe
调用移动到第一个分叉进程中,那么当最后一个进程具有管道出口写入端的打开描述符 (DEMO):
int main() {
// Turn off buffering of stdout, to help with debugging
setvbuf(stdout, NULL, _IONBF, 0);
printf("Shell> \n");
pid_t pid1 = fork();
if(pid1 < 0) {
perror("fork failed");
}
if(pid1 > 0) { // Parent
int status;
waitpid(pid1, &status, 0);
printf("\t\t------PID1 Complete (%d) -------\n\n", status);
} else { // Child
printf("ON CHILD\n");
int fd[2];
if(pipe(fd) < 0) {
perror("pipe failed");
return 1;
}
pid_t pid2 = fork();
if(pid2 < 0) {
perror("fork failed");
}
if(pid2 > 0) { // Child -> Parent
printf("ON CHILD-Parent\n");
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
execlp("sort", "sort", "-r", NULL);
perror("Problem with execlp");
return 1;
} else { // Child -> Child
printf("ON CHILD->Child\n");
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
execlp("ls", "ls", "-a", NULL);
perror("Problem with execvp");
return 1;
}
}
printf("\nEnd of program.\n");
}
该程序的另一个问题是 评论的问题:如果 "ls -a" 的输出太大而无法容纳,则对 waitpid
的调用可能会导致死锁管道的缓冲区。没有理由等待,所以应该简单地消除它。
这不是真正的答案,但我有一些想要分享的内容。
为了确保您的输出按应有的顺序出现,我比您冲洗得更多。请记住,当您调用 fork()
、clone()
、vfork()
、dup()
、dup2()
、close()
或任何 exec()
函数族你正在做的事情在 C 运行time 环境下,其中包括 stdio
。如果你这样做:
printf("cat");
fork();
fflush(stdout);
您很有可能获得:
catcat
作为你的输出,因为你已经复制了 stdout 结构,包括所有缓冲数据,所以除非 stdio 决定在 printf 函数结束之前无论如何都要刷新,然后 "cat" 在每个进程的标准输出缓冲区。
还有一个事实是,当您 运行 exec
系列中的函数时,数据可以保持缓冲,因此在您的程序被新程序替换之前,您的数据可能不会被刷新。当您的程序被 ls
或 sort
替换时,stdout 中的任何未决数据将永远丢失。
此外,当您使用 dup
时,您会遇到另一个问题,因为您正在将文件描述符从 stdio 下换出,因此它可能尚未刷新,数据可能最终会刷新到新文件复制之后。
由于这些事情,您应该更多地调用 fflush
,但我认为这不是您的问题。
我正在尝试创建一个程序,该程序会分叉一次,当父进程等待子进程终止时,该子进程再次分叉,然后执行两个 exec。程序上有一个 Pipe,我已经检查了程序上每个 dup2() 和 pipe() 的 return 值——只是在这里省略它们以使其看起来更简洁——。问题是我只得到 ls -a | 的结果sort -r 在程序完成后。 代码是:
#include <cstdio>
#include <cstring>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
int main(int argc, char *argv[]) {
printf("Shell> \n"); fflush(stdout);
pid_t pid1;
pid_t pid2;
int status = 0;
int fd[2];
if(pipe(fd) < 0) {
printf("FATAL ERROR.\n");
}
pid1 = fork();
if(pid1 > 0) { // Parent
waitpid(pid1, &status, 0);
printf("\t\t------PID1 Complete-------\n\n");
}
else { // Child
if(pid1 == 0) {
printf("ON CHILD\n");
pid2 = fork();
if(pid2 > 0) { // Child -> Parent
printf("ON CHILD-Parent\n");
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
waitpid(pid2, &status, 0);
printf("ON CHILD-Parent after wait\n");
execlp("sort", "sort", "-r", NULL);
perror("Problem with execlp\n");
exit(1);
}
else { // Child -> Child
printf("ON CHILD->Child\n");
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
execlp("ls", "ls", "-a", NULL);
perror("Problem with execvp\n");
exit(1);
}
} // End of if(pid1 == 0)
} // End of Child
printf("\nEnd of program.\n");
return 0;
}
我当前的输出是:
Shell>
ON CHILD
ON CHILD-Parent
ON CHILD->Child
ON CHILD-Parent after wait
我认为问题出在等待中,但我就是不知道如何解决这个问题。有任何想法吗?谢谢!
问题是你在grandparent进程中调用了pipe
。在孙进程 (ls -a) 退出后,parent 进程 (sort -r) 无限期地阻塞等待从管道读取更多输入,因为某些进程 - grandparent - 持有一个打开的描述符管道的写入端。
如果您关闭 grandparent 进程中的管道描述符,或者更好的是将 pipe
调用移动到第一个分叉进程中,那么当最后一个进程具有管道出口写入端的打开描述符 (DEMO):
int main() {
// Turn off buffering of stdout, to help with debugging
setvbuf(stdout, NULL, _IONBF, 0);
printf("Shell> \n");
pid_t pid1 = fork();
if(pid1 < 0) {
perror("fork failed");
}
if(pid1 > 0) { // Parent
int status;
waitpid(pid1, &status, 0);
printf("\t\t------PID1 Complete (%d) -------\n\n", status);
} else { // Child
printf("ON CHILD\n");
int fd[2];
if(pipe(fd) < 0) {
perror("pipe failed");
return 1;
}
pid_t pid2 = fork();
if(pid2 < 0) {
perror("fork failed");
}
if(pid2 > 0) { // Child -> Parent
printf("ON CHILD-Parent\n");
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
execlp("sort", "sort", "-r", NULL);
perror("Problem with execlp");
return 1;
} else { // Child -> Child
printf("ON CHILD->Child\n");
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
execlp("ls", "ls", "-a", NULL);
perror("Problem with execvp");
return 1;
}
}
printf("\nEnd of program.\n");
}
该程序的另一个问题是 waitpid
的调用可能会导致死锁管道的缓冲区。没有理由等待,所以应该简单地消除它。
这不是真正的答案,但我有一些想要分享的内容。
为了确保您的输出按应有的顺序出现,我比您冲洗得更多。请记住,当您调用 fork()
、clone()
、vfork()
、dup()
、dup2()
、close()
或任何 exec()
函数族你正在做的事情在 C 运行time 环境下,其中包括 stdio
。如果你这样做:
printf("cat");
fork();
fflush(stdout);
您很有可能获得:
catcat
作为你的输出,因为你已经复制了 stdout 结构,包括所有缓冲数据,所以除非 stdio 决定在 printf 函数结束之前无论如何都要刷新,然后 "cat" 在每个进程的标准输出缓冲区。
还有一个事实是,当您 运行 exec
系列中的函数时,数据可以保持缓冲,因此在您的程序被新程序替换之前,您的数据可能不会被刷新。当您的程序被 ls
或 sort
替换时,stdout 中的任何未决数据将永远丢失。
此外,当您使用 dup
时,您会遇到另一个问题,因为您正在将文件描述符从 stdio 下换出,因此它可能尚未刷新,数据可能最终会刷新到新文件复制之后。
由于这些事情,您应该更多地调用 fflush
,但我认为这不是您的问题。