プログラムが終了した後に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());  

つまり、最初のサブプロセスが終了するのを待ち、2番目のサブプロセスが終了することを確認するためにシステムコールを実行しますが、WNOHANG実際には待機しません。それにもかかわらず、プログラムは戻り値を使用しないため、確認は意味がありませんwaitpid()。その後、親プロセスはPIDを印刷し、引き続き関数を実行し、他の2つのifステートメントを渡して終了しますreturn 0

この時点で、第2の子プロセスがまだ実行中であっても実行されていなくてもよい。明示的な同期がないと、状況が発生する順序を保証することはできません。さらに、シェルはプログラムによって開始されたサブプロセスについての知識がなく、それを待つメカニズムもありません。基本プロセスはwait*()これを提供する必要があります。

コードを書き続ける前に、複雑にネストされたifステートメントを削除して、より明確な構造に投資してください。これらのコードを単純化する一般的な方法は、子プロセスにすぐに関数を呼び出すこと、または関数が返された直後_exit()に関数を呼び出すことです。呼び出しが失敗した場合、子も終了するのではexec*()なく、同じコードを実行し続けます。

関連情報