sigaction

Yongmin Park·2022년 9월 19일
0

sigaction 은 signal 함수보다는 조금 더 다양한 기능을 지원하는 시그널 함수입니다. sigaction 함수는 <sys/signal.h>에 정의된 sigaction 구조체를 활용하며, sigaction 구조체가 지원하는 다양한 기능을 사용할 수 있습니다.

sigaction 함수

#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
// 성공시 0, 실패시 -1 할당

특정 시그널 넘버에 대해 작동할 sigaction 구조체를 지정합니다.

sigaction 구조체

struct sigaction {
    void (*sa_handler)(int);    // signal 핸들러 함수
    void (*sa_sigaction)(int, siginfo_t *, void *);
    // 밑의 sa_flags가 SA_SIGINFO일때 sa_handler 대신에 동작하는 핸들러
    sigset_t sa_mask;             // 시그널 처리시 블록 지정할 시그널 마스크
    int sa_flags;                // sig_flag 기능 지정을 위한 상수
    void (*sa_restorer)(void);  // 응용프로그램 코드에서는 사용하지 말자.
}

sigaction 함수를 사용하기 위해서는 sigaction 구조체를 프로그램 코드 내부에 선언하여야합니다. signal 함수는 핸들러 함수를 등록하기 위해 사용하지만, sigaction 함수는 sigaction 구조체를 선언하고 내부에 핸들러를 등록한 뒤 그 구조체를 사용하기 때문입니다.

void ft_signal_handler(...)
{
    ...
}

...
struct sigaction sig_strct;
...

sig_strct.sa_handler = ft_signal_handler;
sigaction(SIGUSR1, sig_strct, NULL);

sa_handler, sa_sigaction
두 멤버는 메모리 영역 단위로 보았을 때 중첩됩니다. 즉, 두 멤버 모두 시그널이 들어왔을 때에 어떤 동작을 할 지 핸들러 함수를 지정할 때에 사용합니다.

두 멤버의 차이는 아래의 sa_flag에서 SIGINFO의 사용 여부에 따릅니다. SIGINFO를 사용하지 않을 경우, sa_handler에 시그널 핸들러 함수를 할당합니다. 그렇지 않고 SIGINFO를 사용하는 경우라면 sa_sigaction에 시그널 핸들러 함수를 할당합니다.

// error
sig_strct.sa_handler = ft_signal_handler;
sig_strct.sa_flag = SA_SIGINFO;
sigaction(SIGUSR1, sig_str, NULL);

// correct
sig_strct.sa_sigaction = ft_signal_handler;
sig_strct.sa_flag = SA_SIGINFO;
sigaction(SIGUSR1, sig_str, NULL);

sa_mask

sigaction 구조체의 핸들러가 동작 중일 때 처리를 블록할 signal_set을 정합니다. 기존에 블록하고 있던 시그널셋이 있다면, 이 구조체의 핸들러가 동작할 때 여기에서 정하여 준 시그널셋을 기존에 블록하고 있던 시그널셋과 더하여 블록합니다. (즉, 시그널 핸들러 발동시 기존에 블록되던 시그널셋 + 해당 구조체의 sa_mask 에 있는 시그널셋 모두 블록)

sa_flag에 SIG_NODEFER을 할당하면 시그널 핸들러를 호출하게 한 시그널은 블록하지 않습니다.

sa_flag

signal 함수와 sigaction 사이에 가장 큰 차이점은 sa_flag를 이용한 다양한 기능의 지원을 꼽을 수 있습니다. 아래의 플래그를 이용하여, 해당 sigaction 구조체의 핸들러가 발동될 때에 다양한 옵션들을 사용할 수 있도록 합니다.

sa_flag 에서는 or 연산자(||)를 활용하여 여러가지 플래그 값을 넣어줄 수 있습니다.

flag동작 내용
SA_NOCLDSTOP이 값을 설정하고 시그널 SIGCHLD을 처리하는 경우, 자식 프로세스가 중지 혹은 재시작될 때에 부모 프로세스에 SIGCHLD를 전달하지 않는다.
SA_NOCLDWAIT이 값을 설정하고 시그널 SIGCHLD을 처리하는 경우, 시스템은 자식 프로세스가 종료될 때 좀비 프로세스를 만들지 않는다.
SA_NODEFER이 값을 설정하고 시그널을 받으면, 해당 시그널 처리 중에 Unix 커널이 해당 시그널을 자동으로 블록하지 못하도록 한다.
SA_NOMASKSA_NODEFER 와 동일
SA_ONSTACK이 값을 설정하고 시그널을 받으면, sigaltstack 함수를 호출하여 대체 시그널 스택이 있을 시 대체 스텍에서 처리하게 한다.
SA_RESETHAND시그널을 받고 핸들러 함수를 호출한 이후, 해당 시그널을 SIG_DFL로 설정한다. 즉 시스템 초기값으로 재설정한다. 처리 중 해당 시그널을 블록하지 않는다.
SA_ONESHOTSA_RESETHAND 와 동일
SA_RESTART시그널은 프로세스의 진행을 비동기적으로 interupt하는 성질이 있는데, 이 값을 설정하고 시그널을 발동시키면 원래 진행되던 프로세스는 시그널 핸들러 호출 및 처리 이후 재개된다.
SA_SIGINFO이 값을 설정하고 시그널을 받으면, sa_handler 대신 sa_sigaction 이 발동됩니다. 시그널 핸들러에 개별 시그널에 대한 더 많은 정보를 제공하는 추가적인 인자를 넣을 수 있습니다.

SIGINFO 구조체

sigaction 구조체의 sa_flag 멤버에 SA_SIGINFO를 할당하고, 핸들러 함수에 siginfo_t siginfo*를 매개변수로 전달하면 사용이 가능한 SIGINFO 구조체는 <sys/signal.h>에 명시되어 있으며, 개별 시그널에 대한 더 많은 정보를 제공합니다.

siginfo_t {
               int      si_signo;     /* 시그널 넘버 */
               int      si_errno;     /* 에러 넘버 */
               int      si_code;      /* 시그널 발생 이유 */
               int      si_trapno;    /* 하드웨어 송신 시그널의 트랩넘버
               				(대부분 아키텍처에서 사용 x)*/
               pid_t    si_pid;       /* 시그널을 보낸 프로세스의 pid */
               uid_t    si_uid;       /* 시그널을 보낸 프로세스의 effective user id */
               int      si_status;    /* EXIT 값 혹은 시그널 */
               clock_t  si_utime;     /* 소요된 User time */
               clock_t  si_stime;     /* 소요된 System time */
               union sigval si_value; /* 시그널 발생시 전달할 값 */
               int      si_int;       /* POSIX.1b signal */
               void    *si_ptr;       /* POSIX.1b signal */
               int      si_overrun;   /* Timer overrun count;
                                         POSIX.1b timers */
               int      si_timerid;   /* Timer ID; POSIX.1b timers */
               void    *si_addr;      /* Memory location which caused fault */
               long     si_band;      /* Band event (was int in
                                         glibc 2.3.2 and earlier) */
               int      si_fd;        /* File descriptor */
               short    si_addr_lsb;  /* Least significant bit of address
                                         (since Linux 2.6.32) */
               void    *si_lower;     /* Lower bound when address violation
                                         occurred (since Linux 3.19) */
               void    *si_upper;     /* Upper bound when address violation
                                         occurred (since Linux 3.19) */
               int      si_pkey;      /* Protection key on PTE that caused
                                         fault (since Linux 4.6) */
               void    *si_call_addr; /* Address of system call instruction
                                         (since Linux 3.5) */
               int      si_syscall;   /* Number of attempted system call
                                         (since Linux 3.5) */
               unsigned int si_arch;  /* Architecture of attempted system call
                                         (since Linux 3.5) */
           }

si_code에서는 시그널의 발생 원인에 대해 추적할 수 있는데, 여기에서 쓰이는 값은 아래와 같습니다. 대개 음수나 0이면 들어오는 시그널이 특정 프로세스에서 인위적으로 발생시킨 것이고, 양수인 경우에는 커널에서 프로세스에 대해 시그널을 전달해주었다고 보면 됩니다.

이글은 해당 블로그를 참고했습니다. 링크

profile
기록으로 기적을

0개의 댓글