본문 바로가기

42 Seoul

fork && wait && exit && 자식프로세스의 시그널처리

미니쉘을 만들면서 자식프로세스의 시그널 처리에 대해서 정말 많은 생각을 했다. 

자식프로세스를 만들고 시그널을 보내면 부모는 당연하고 자식, 손자 프로세스 모두 시그널이 전달돼서 꼬이는걸 확인할 수 있었는데

execve로 실행한 프로그램 내부에서 시그널을 어떻게 변경할지는 알 수 없기 때문에 자식프로세스의 시그널을 맘대로 집어넣을수 없었다. (cat같은 경우엔 SIGINT는 DFL로 변경되고, SIGQUIT는 부모프로세스 그대로 사용하는것 같았다.)

일단 만들고 제출했던 미니쉘에서는 fork하는 순간 부모프로세스의 시그널을 IGN 하는 방법으로 시그널 중복을 막을 수 있었지만, 출력을 어떻게 하는지 몰라서 처리를 못했었다.

 

결론부터 말하자면 자식프로세스는 종료될때 어떤 시그널로 종료됐는지 부모에게 넘겨주고, 부모는 그걸로 시그널에 따라 어떤 출력을 할지 정하고 리턴값도 세팅할 수 있다.

static int  set_fork_ret(int fork_ret)
{
    int signo;

    if (WIFEXITED(fork_ret))
        return (WEXITSTATUS(fork_ret));  // 정상종료는 exit값을 세팅해서 리턴
    else
    {
        if (WIFSIGNALED(fork_ret))
        {
            signo = WTERMSIG(fork_ret);  // 어떤 시그널로 자식이 죽었는지 리턴됨
            if (signo == SIGINT)
                ft_putstr_fd(2, "^C\n");
            else
                ft_putstr_fd(2, "^\\Quit: 3\n");
            return (128 + signo);   // 시그널로 종료됐을땐 signo + 128 값이 리턴값이다.
        }
    }
    return (-1);
}

int exec_program(t_lst *node, t_stat *stat)
{
    pid_t   pid;
    int     fork_ret;
    int     i;

    fork_ret = 0;
    set_signal(SH_DFL, SH_DFL);    // 자식프로세스를 위해 시그널을 일단 DFL로 설정
    pid = fork();
    if (pid == 0)
    {
        i = 0;
        while (node->cmd[i] && node->cmd[i] != '/')
            i++;
        if (i == ft_strlen(node->cmd))
            node->cmd = find_path(node->cmd, stat);
        execve(node->cmd, node->argv, stat->env);
        print_exec_err(i, node->cmd, stat);
        exit(EXEC_ERR_CMDNF);   // execve가 실패한경우는 command not found가 유력
    }
    set_signal(SH_IGN, SH_IGN); // 시그널 중복을 막기위해 부모프로세스는 IGN으로 설정
    wait(&fork_ret);            // 자식프로세스 리턴 대기 
    fork_ret = set_fork_ret(fork_ret);  // 리턴값에 따라 에러출력 등 처리 후 리턴값 재세팅
    set_signal(SH_SHELL, SH_IGN); 
    return (fork_ret);
}

 

 

 

 

fork와 wait에 대해서...

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    int pipe_fd[2];
    int ret;
    pid_t   pid;

    pid = fork();
    if (pid == 0)
    {
        ret = execve("/bin/cat", argv, 0);
        printf("execve error!!!\n");
        exit(ret);
    }
    signal(SIGINT, SIG_IGN);
    signal(SIGQUIT, SIG_IGN);
    wait(&ret);
    signal(SIGINT, SIG_DFL);
    signal(SIGQUIT, SIG_DFL);
    printf("%d, %d, %d\n", ret, WIFEXITED(ret), WTERMSIG(ret));
}

 

 

리턴값을 세팅하기 위해서 미리 정의된 매크로함수의 사용이 필요하다.

리턴값은 int이지만, 하위 8비트는 실패했을때, 상위 24비트는 성공했을때 세팅하는 값이라고 판단된다.

WIFEXITED(status)

자식이 정상적으로 종료되었다면 non-zero 이다.

WEXITSTATUS(status)

exit()를 호출하기 위한 인자나 return 값이 설정되고 종료된 자식의 반환 코드의 값을 세팅해준다. 

wait으로 받은 값을 >> 8 연산한 결과나 마찬가지이다. 

WIFSIGNALED(status)

자식프로세스가 어떤 신호때문에 종료되었다면 참을 반환한다.

WTERMSIG(status)

자식프로세스를 종료하도록한 신호의 번호를 반환한다. 

WIFSTOPPED(status)

반환의 원인이 된 자식프로세스가 현재 정지되어 있다면 참을 반환한다.

WSTOPSIG(status)

자식을 정지하도록 야기한 신호의 숫자를 반환한다. 

 

fork된 자식 프로세스가 정상 종료된 경우.  WIFEXITED(ret) = non-zero

1. execve 성공인 경우

execve가 정상적으로 실행된 경우. wait으로 세팅된 값은 0. 매크로 함수에 따라 다른 리턴값을 가져올 수 있다.

execve에서 실행한 프로그램 자체 에러 ex) cat sdnfjkansdfkj 는 에러출력도 그 프로그램에서 하며, 정상 실행으로 간주한다.

wait으로 세팅된 값은 256

 

2. execve가 실패한 경우. (execve의 리턴값이 -1)

execve가 실패한것이지, fork된 자식프로세스는 타고내려가서 exit로 정상 종료되었다.

execve가 실행해야 하는 프로그램이 없는경우. (절대경로나 상대경로만 가능함. PATH에서 찾아서 직접 넣어줘야한다)

errno에 에러번호가 세팅되며 그에맞는 에러를 자식프로세스에서 출력할 수 있다. wait으로 리턴된 값은 (unsigned char)-1(=255)의 << 8 한 65280값이다.

 

 

자식 프로세스가 비정상 종료한 경우. WIFEXITED(ret) = 0

1. 시그널에 의한 종료

SIGINT, SIGQUIT, KILL 등 시그널을 받으면 종료되는데 이것을 fork의 비정상 종료로 판단한다.

이때 WTERMSIG(ret) 매크로를 통해 종료시킨 시그널의 signo 값을 받아올 수 있고

부모프로세스에서 자식이 어떤 시그널로 죽었는지 판단 가능하다.

실제로 시그널로 죽은 프로세스의 리턴은 signo + 128 값을 사용한다.

SIGINT = 2, SIGQUIT = 3

 

'42 Seoul' 카테고리의 다른 글

NETPRACTICE  (0) 2022.07.07
cpp module  (0) 2022.05.27
exam rank 04  (0) 2022.05.26
minishell  (0) 2022.04.21
philosophers - 식사하는 철학자 문제  (0) 2022.02.19