分叉进程后执行不会 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 系列中的函数时,数据可以保持缓冲,因此在您的程序被新程序替换之前,您的数据可能不会被刷新。当您的程序被 lssort 替换时,stdout 中的任何未决数据将永远丢失。

此外,当您使用 dup 时,您会遇到另一个问题,因为您正在将文件描述符从 stdio 下换出,因此它可能尚未刷新,数据可能最终会刷新到新文件复制之后。

由于这些事情,您应该更多地调用 fflush,但我认为这不是您的问题。