sigaction 은 signal 함수보다는 조금 더 다양한 기능을 지원하는 시그널 함수입니다. sigaction 함수는 <sys/signal.h>에 정의된 sigaction 구조체를 활용하며, sigaction 구조체가 지원하는 다양한 기능을 사용할 수 있습니다.
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
// 성공시 0, 실패시 -1 할당
특정 시그널 넘버에 대해 작동할 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_NOMASK | SA_NODEFER 와 동일 |
SA_ONSTACK | 이 값을 설정하고 시그널을 받으면, sigaltstack 함수를 호출하여 대체 시그널 스택이 있을 시 대체 스텍에서 처리하게 한다. |
SA_RESETHAND | 시그널을 받고 핸들러 함수를 호출한 이후, 해당 시그널을 SIG_DFL로 설정한다. 즉 시스템 초기값으로 재설정한다. 처리 중 해당 시그널을 블록하지 않는다. |
SA_ONESHOT | SA_RESETHAND 와 동일 |
SA_RESTART | 시그널은 프로세스의 진행을 비동기적으로 interupt하는 성질이 있는데, 이 값을 설정하고 시그널을 발동시키면 원래 진행되던 프로세스는 시그널 핸들러 호출 및 처리 이후 재개된다. |
SA_SIGINFO | 이 값을 설정하고 시그널을 받으면, sa_handler 대신 sa_sigaction 이 발동됩니다. 시그널 핸들러에 개별 시그널에 대한 더 많은 정보를 제공하는 추가적인 인자를 넣을 수 있습니다. |
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이면 들어오는 시그널이 특정 프로세스에서 인위적으로 발생시킨 것이고, 양수인 경우에는 커널에서 프로세스에 대해 시그널을 전달해주었다고 보면 됩니다.
이글은 해당 블로그를 참고했습니다. 링크