프로그램이 종료된 후 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.

이 시점에서 두 번째 하위 프로세스가 아직 실행 중일 수도 있고 실행되지 않을 수도 있습니다. 명시적인 동기화가 없으면 상황이 발생하는 순서를 보장할 수 없습니다. 게다가 쉘은 프로그램에 의해 시작된 하위 프로세스에 대한 지식이 없으며 이를 기다리는 메커니즘도 없습니다. 기본 프로세스가 wait*()이를 제공해야 합니다.

코드 작성을 계속하기 전에 복잡하게 중첩된 if 문을 제거하고 좀 더 명확한 구조에 투자하세요. 이러한 코드를 단순화하는 일반적인 방법은 하위 프로세스가 즉시 함수를 호출하도록 하거나 _exit()함수가 반환된 직후에 함수를 호출하도록 하는 것입니다. 이제 exec*()호출이 실패하면 자식도 종료하는 대신 동일한 코드를 계속 실행합니다.

관련 정보