Sigaction의 모든 것

이동윤·2024년 11월 8일

1. sigaction이란?

sigaction은 리눅스와 유닉스 기반 시스템에서 신호 처리를 설정하기 위한 함수입니다. 프로세스가 특정 신호를 받았을 때 해당 신호를 처리하는 방법을 정의할 수 있도록 도와줍니다. sigaction을 사용하면 signal 함수보다 더 많은 옵션과 제어가 가능하여 더 유연한 신호 처리가 가능합니다

특징 : sigaction은 함수이고, 동시에 구조체도 사용합니다. sigaction 함수는 신호를 처리할 때 사용할 설정을 지정하기 위해 struct sigaction 구조체를 인수로 받습니다.


Q. 그럼 sigaction은 signal의 어떤 단점을 개선했나요?

A. 대표적으로는 signal의 중첩 문제를 개선했습니다!

signal 시스템의 문제점

  • signal이 발생된 원인을 모름
    signal handler에게 어떤 signal이 발생되었는지는 통보(signum)
    – 해당 signal이 발생된 원인에 대해서는 알 수 없음

  • signal handler에서 signal을 처리하는 동안 다른 signal을 안전하게 차단할 수 없음


signal에서는 신호 핸들러가 실행되는 동안 다른 신호를 일시적으로 막는 기능이 부족했습니다. 이로 인해 동시에 여러 신호가 발생할 경우 핸들러가 중첩되어 실행되는 등의 문제가 발생할 수 있었습니다. 즉, 하나의 시그널을 처리하는것은 효과적이나 여러 시그널이 도착하면 문제가 생긴다는 겁니다.

그러나 sigaction에서는 sa_mask 필드를 통해 특정 신호가 처리되는 동안 블록할 신호를 지정할 수 있습니다. 이를 통해 신호 간의 충돌을 방지하고 더욱 안전하게 신호를 처리할 수 있습니다.

그럼 이 좋은거 후두다닥 배워보러 갑시다


2. sigaction 메뉴얼

sigaction

- signal() 함수를 대체

- 어떤 signal을 처리할 것인지, 해당 signal을 어떻게 처리할 것인지 지정


3. sigaction 구조체

sigaction은 함수이고, 동시에 구조체도 사용합니다. sigaction 함수는 신호를 처리할 때 사용할 설정을 지정하기 위해 struct sigaction 구조체를 인수로 받습니다.

struct sigaction {
    void (*sa_handler)(int);       // 신호 핸들러 함수
    void (*sa_sigaction)(int, siginfo_t *, void *); // 대체 신호 핸들러 
    sigset_t sa_mask;              // 핸들러가 실행되는 동안 블록할 신호 집합
    int sa_flags;                  // 동작 플래그 (SA_RESTART, SA_SIGINFO 등)
};

당장은 약간 안와닿을 수 있습니다!

그럼 두 핸들러를 비교해보면서 확인해볼까연?


3-1. sa_handler vs sa_sigaction

struct sigaction에는 sa_handlersa_sigaction 이라는 두 가지 신호 핸들러를 설정할 수 있습니다
이 두 핸들러는 신호가 발생했을 때 호출될 함수를 지정하는 방식이지만, 그 용도와 기능에는 차이가 있습니다.


1. sa_handler (기본 핸들러 방식)

sa_handler는 기존 스타일의 신호 핸들러입니다.
신호 번호 int signo 하나만을 매개변수로 받는 단순한 핸들러 함수를 설정할 때 사용됩니다.
이 핸들러는 발생한 신호의 번호만 전달되므로 신호에 대한 추가 정보를 얻을 수 없습니다.
예를 들어, 다음과 같이 단순히 신호를 받았을 때 실행될 함수를 지정합니다:

void handler(int signo) {
    printf("Received signal %d\n", signo);
}

2. sa_sigaction (새로운 스타일 핸들러):

sa_sigaction은 보다 많은 정보를 제공하는 새로운 스타일의 핸들러로, 신호 발생 시 추가 정보 (siginfo_t)를 받을 수 있습니다.
이 핸들러는 세 개의 매개변수를 받습니다:
int signo: 발생한 신호의 번호
siginfo_t *info: 신호에 대한 추가 정보를 담고 있는 포인터 (si_pid, si_uid 등)
void *context: CPU 컨텍스트에 대한 포인터 (보통 잘 사용되지 않음)

sa_sigaction을 사용하려면 struct sigactionsa_flagsSA_SIGINFO 플래그를 설정해야 합니다.
그렇지 않으면 sa_handler가 기본 핸들러로 사용됩니다.
예를 들어, 다음과 같이 신호를 보내온 프로세스의 PID와 같은 추가 정보를 얻을 수 있습니다:

void siginfo_handler(int signo, siginfo_t *info, void *context) {
    printf("Received signal %d\n", signo);
    printf("Sent by process with PID: %d\n", info->si_pid);
}

같은 신호 핸들러라고는 하지만 둘중에 하나만 사용해야겠죠?

지금부터는 두 핸들러를 어떻게 호출하는지 알아봅시다~


int main() {
    struct sigaction sa;

    // 1. 기존 스타일 핸들러 설정
    sa.sa_handler = old_handler;  // sa_handler 사용
    sa.sa_flags = 0;              // 기본 플래그
    sigaction(SIGINT, &sa, NULL); // SIGINT에 대해 old_handler 사용

    // 2. 새로운 스타일 핸들러 설정
    sa.sa_sigaction = new_handler; // sa_sigaction 사용
    sa.sa_flags = SA_SIGINFO;      // SA_SIGINFO 설정으로 sa_sigaction 활성화
    sigaction(SIGTERM, &sa, NULL); // SIGTERM에 대해 new_handler 사용

    while (1) {
        printf("Waiting for SIGINT or SIGTERM...\n");
        sleep(2);
    }

    return 0;
}

3-2. sa_flags (handler의 동작 방식을 제어)

SA_RESETHAND: signal을 수신하고 handler를 호출한 이후, 해당 signal을 SIG_DFL로 설정

  • 이 플래그를 설정하면 신호를 한 번 처리한 후에 핸들러가 기본 동작으로 되돌아갑니다. 다시 말해, sigaction으로 설정한 핸들러가 더 이상 적용되지 않고 기본 동작이 실행됩니다.

SA_NODEFER : 해당 signal을 처리하는 중에 다시 같은 signal이 발생하면 kernel이 자동으로 blocking 하지 못하도록 설정

  • 기본적으로 신호가 처리되는 동안 동일한 신호는 자동으로 블록(지연)됩니다. SA_NODEFER 플래그를 설정하면 같은 신호가 발생해도 지연 없이 핸들러가 중첩되어 호출됩니다.
    예를 들어, SIGINT 신호를 처리하는 중에 다시 SIGINT가 발생하면, SA_NODEFER가 설정된 경우 즉시 핸들러가 중첩 호출됩니다.

SA_RESTART : signal handler에 의해 중지된 시스템 호출을 handler 처리 이후 자동으로 재시작

  • 이 플래그를 설정하면 신호 핸들러가 실행된 후 자동으로 중단된 시스템 호출이 재시작됩니다.
    예를 들어, read, write, select 같은 시스템 호출이 신호에 의해 중단되었을 때, 이 플래그가 있으면 호출이 자동으로 재시작되어 중단되지 않고 이어집니다.

SA_SIGINFO : 해당 값을 설정하고 signal을 수신하면 sa_sigaction을 수행
해당 값을 설정하지 않으면, sa_handler를 수행

int main() {
    struct sigaction sa;
    sa.sa_sigaction = sigint_handler; // 새로운 스타일 핸들러 설정
    sa.sa_flags = SA_SIGINFO | SA_RESTART; // 플래그 설정 (여러 플래그 사용시 |(or) 사용)

    sigemptyset(&sa.sa_mask); // sa_mask 초기화
    sigaction(SIGINT, &sa, NULL); // SIGINT에 대해 핸들러 

    return 0;
}

여러가지 플래그를 사용해서 핸들러에 속성을 부여한다! 라는 느낌입니다


3-3 sa_mask

sa_mask란?

• Handler에서 발생된 signal을 처리하는 동안, 다른 signal을 차단할지 설정하는 필드입니다
• 차단할 signal들의 집합을 설정하는 방식을 사용합니다

이 설정은 핸들러가 처리 중일 때 다른 신호로 인해 방해받는 것을 방지하기 위해 사용됩니다!

설정 방법

  • sigemptyset(&sa.sa_mask): sa_mask를 초기화하여 비워둡니다.
  • sigaddset(&sa.sa_mask, SIGNAL_이름): sa_mask에 특정 신호를 추가합니다.

Q. sigaction에서 sa_mask를 설정하지 않으면, 어떻게 되나요?

A. 기본적으로 동일한 신호만 자동으로 블록됩니다. 즉, 예를 들어 SIGINT가 발생해 핸들러가 실행 중이면, 다시 발생한 SIGINT는 처리 후 실행됩니다. sa_mask에 특정 신호를 추가하면, 해당 신호도 처리 중에 블록되어 핸들러가 끝난 후에 처리됩니다.

0개의 댓글