为什么 exec 在程序退出后打印其输出?

为什么 exec 在程序退出后打印其输出?

我正在编写一个程序将一个命令通过管道传输到另一个命令。输入将来自命令行:

$ ./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*()调用失败,子级也会继续执行相同的代码而不是退出。

相关内容