멀티 프로세스에서의 sigaction함수 이용

Hyun·2023년 5월 18일
0

멀티 프로세스에서 시그널, 시그널 핸들러의 특징

sleep()

sleep(n) 함수는 n초만큼 블러킹 상태가 되게 한다. 하지만 시그널 발생시 블로킹 상태에 있던 프로세스가 깨어난다.

alarm(n) 함수는 n초 후에 현재 프로세스에 SIGALRM 시그널을 발생시킨다. 따라서 아래 코드처럼 alarm(5)인 경우 5초뒤에 현재 프로세스에 SIGALRM 시그널을 발생시키게 되므로 sleep(10) 이지만 실제로는 sleep(5)가 된다. 결과적으로 아래 코드는 5초마다 SIGALRM 시그널을 현재 프로세스에 발생시키는 동작을 나타낸다.

sigaction 기능을 등록할 프로세서 내부

struct sigaction alrm_act;

alrm_act.sa_handler = child_timeout;
sigemptyset(&alrm_act.sa_mask);
alrm_act.sa_flags = 0;

sigaction(SIGALRM, &alrm_act, 0);

printf("Child process created\n");

for (int i = 0; i < 5; i++)
{
  alarm(5);
  sleep(10); // wake up when call SIGALRM
}

시그널 핸들러 함수

void child_timeout(int sig)
{
    // SIGALRM from chld process
    count += 1; // count of timeout function called
    time += 5;  // timeout function called per 5 sec

    // child_time starts at 5, child_count starts at 1
    printf("[Child] Time out: 5, elapssed time: %d seconds(%d)\n", time, count);
}

SIGINT

ctrl + c 버튼을 눌렀을때 SIGINT 시그널이 호출된다.
SIGINT 시그널은 멀티 프로세스 상의 모든 프로세스에게 전달된다.

만약 프로세스A가 sigaction 함수를 통해 SIGINT 시그널과 시그널 핸들러를 쌍으로 등록시켜 주었다면 곧바로 종료되는 것이 아니라 시그널 핸들러 함수가 호출된다. 예를 들어 아래 코드와 같이 조건에 따라 해당 프로세스를 종료시키는 방법(시그널 핸들러)이 있다.

=> 정리하자면, 해당 시그널에 대한 시그널 핸들러를 따로 정의해서 등록해줬으므로 원래 해당 시그널을 받으면 하는 동작을 하지 않고, 시그널 핸들러 함수를 호출하게 된다.

시그널, 시그널 핸들러 쌍으로 등록
특정 프로세스에서 sigaction 함수를 통해 시그널과 시그널 핸들러를 쌍으로 등록할 수 있다. 만약 프로세스B가 특정 시그널에 대해 sigaction 함수를 이용해 시그널 핸들러를 등록하지 않았다면, 해당 시그널이 호출됐을때 본연의 기능이 수행된다. (ex)SIGINT -> 종료)

sigaction 기능을 등록할 프로세서 내부

// SIGINT
struct sigaction quit_act;
quit_act.sa_handler = parent_quit;//sigaction 구조체에 시그널 핸들러 함수 추가 
sigemptyset(&quit_act.sa_mask);
quit_act.sa_flags = 0;
sigaction(SIGINT, &quit_act, 0);

시그널 핸들러 함수

void parent_quit(int sig)
{
    printf("Do you want to exit (y or Y to exit)? ");
    char ch;
    scanf("%c", &ch);
    printf("\n");
    if(ch == 'y' || ch == 'Y') exit(1);
}

과제를 풀면서 처음에는 부모와 자식 프로세스 모두 SIGALRM 시그널을 받게 되므로 sigaction 구조체를 fork() 이전의 위치에 하나만 만들고, 시그널 함수도 하나만 만들어서 시그널 함수에 부모 프로세스와 자식 프로세스가 필요한 변수들을 모두 static으로 선언한 다음, pid값을 이용해 현재 위치가 자식 프로세스인지 부모 프로세스인지 구분하여 그에 맞게 처리하려고 했다.

즉, 두개의 프로세스에서 발생하는 시그널을 통합하여 하나의 sigaction 구조체와 시그널 핸들러 함수로 처리하려고 했었다. 하지만 이렇게 처리하려고 코드를 작성해보니 너무 복잡했다.

따라서 각각의 프로세스마다 sigaction으로 시그널에 대해 시그널 핸들러를 등록하는 방식을 이용함으로써 가독성이 좋아지고 기존의 두개의 프로세스에서 동시에 사용하려고 했었던 시그널 핸들러의 부피가 줄어들었다.

핵심으로는 fork() 이후의 자식, 부모 프로세스의 위치에 독립적으로 sigaction 함수를 이용해 시그널과 시그널 핸들러를 쌍으로 등록해주면 된다. fork() 되기 이전의 위치에서 작성하면 자식, 부모 프로세스가 모두 접근할 수 있으므로 생각하기 어려워진다.

개선된 코드

int main(int argc, char *argv[])
{

    pid_t pid;//typedef int pid_t

    //----- make a new process -----
    pid = fork();

    // child process
    if (pid == 0)
    {
        //----- enroll sig handler to sig in Child Process -----
        struct sigaction alrm_act;

        alrm_act.sa_handler = child_timeout;
        sigemptyset(&alrm_act.sa_mask);
        alrm_act.sa_flags = 0;

        sigaction(SIGALRM, &alrm_act, 0);

        printf("Child process created\n");

        for (int i = 0; i < 5; i++)
        {
            alarm(5);
            sleep(10); // wake up when call SIGALRM
        }

        return 5;
    }
    // parent process
    else
    {
        //----- enroll sig handler to sig in Parent process -----
        // SIGALRM
        struct sigaction alrm_act;
        alrm_act.sa_handler = parent_timeout;
        sigemptyset(&alrm_act.sa_mask);
        alrm_act.sa_flags = 0;
        sigaction(SIGALRM, &alrm_act, 0);

        // SIGCHLD
        struct sigaction chld_act;
        chld_act.sa_handler = child_terminated;
        sigemptyset(&chld_act.sa_mask);
        chld_act.sa_flags = 0;
        sigaction(SIGCHLD, &chld_act, 0);

        // SIGINT
        struct sigaction quit_act;
        quit_act.sa_handler = parent_quit;
        sigemptyset(&quit_act.sa_mask);
        quit_act.sa_flags = 0;
        sigaction(SIGINT, &quit_act, 0);

        printf("Parent process created\n");

        alarm(2);

        // to wait child process's termination
        while (1)
        {
            sleep(1);
        }
    }
}

signal 메세지를 보내는 주체

시그널(signal)

운영체제가 프로세스에게 특정한 상황이 발생했음을 알리는 메세지
=> 시그널은 "운영체제"가 발생시킨다.

따라서 아래 코드를 보면 alarm(5) 다음에 sleep(10)이 오기 때문에, alarm(5)가 5초를 기다린 후 SIGALRM 시그널을 발생시키는거 아니야? 라고 생각할 수 있는데, 그게 아니고 5초뒤에 SIGALRM 시그널을 발생시키도록 운영체제에 "예약"하고 바로 그 다음 코드인 sleep(10)으로 넘어간다. 즉, alarm(5)는 예약의 역할을 할뿐, 실제로 5초뒤에 SIGALRM 시그널을 발생시키는건 운영체제의 역할이다.

for (int i = 0; i < 5; i++)
{
  alarm(5);
  sleep(10); // wake up when call SIGALRM
}
profile
better than yesterday

0개의 댓글