우당탕탕 개발일지
[C언어] 시그널 (feat. Sigaction) 본문
시그널
운영체제는 실행하고 있는 프로그램에 대해 예외적인 상황을 보고하기 위해서 시그널(signal)을 사용한다. 시그널은 비동기적으로 발생하며, 다음과 같은 3가지 경우에 발생한다.
- 0으로 나누기처럼 프로그램에서 예외적인 상황이 일어나는 경우
- 프로세스가 kill 함수와 같이 시그널을 보낼 수 있는 함수를 사용해 다른 프로세스에 시그널을 보내는 경우
- 사용자가 Ctrl+C와 같은 인터럽트 키를 입력한 경우

시그널을 받은 프로세스가 이를 처리하는 방법은 다음과 같이 4가지이다.
- 프로세스가 받은 시그널에 따라 기본 동작을 수행한다.
- 대부분 시그널의 기본 동작은 프로세스를 종료하는 것이다.
- 이외에 시그널을 무시하거나 프로세스의 수행 일시 중지/재시작 등을 기본 동작으로 수행한다.
- 프로세스가 받은 시그널을 무시한다.
- 프로세스가 시그널을 무시하기로 지정하면, 유닉스는 프로세스에 시그널을 전달하지 않는다.
- 프로세스는 시그널 처리를 위해 미리 함수를 지정해놓고, 시그널을 받으면 해당 함수를 호출해 처리한다.
- 시그널 처리를 위해 지정하는 함수를 시그널 핸들러라고 한다.
- 시그널을 받으면 기존 처리 작업을 중지한 후, 시그널 핸들러 동작 완료 후 수행한다.
- 프로세스는 특정 부분이 실행되는 동안 시그널이 발생하지 않도록 블록할 수 있다.
- 블록된 시그널은 큐에 쌓여 있다가 시그널 블록이 해제되면 전달된다.
시그널 종류

시그널 종류와 각 시그널에 따른 기본 동작이 미리 정해져 있다.
대부분 내부적으로 시그널 번호가 매핑되어 있다.
SIGKILL
프로세스를 죽여라
(슈퍼관리자가 사용하는 시그널로, 프로세스가 어떤 경우든 죽음)
SIGALARM
알람을 발생한다.
SIGSTP
프로세스를 멈춰라 (=Ctrl + z)
SIGCONT
멈춰진 프로세스를 실행해라 (Continue)
SIGINT
프로세스에 인터럽트를 보내서 프로세스를 죽여라 (=Ctrl + c)
SIGSEGV
프로세스가 다른 메모리영역을 침범했다.
✅ signal()
#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);
- sig : 시그널 핸들러로 처리하려는 시그널
- SIGKILL, SIGSTOP 시그널을 제외한 모든 시그널을 지정할 수 있다.
- func : 시그널 핸들러의 함수명
- 만약 sig 값이 SIGFPE, SIGILL, SIGSEGV 또는 계산 예외에 해당하는 다른 구현 정의 신호였다면, 동작은 정의되지 않는다.
- func 인자에는 다음 세 가지 중 하나를 설정해야 한다.
- SIG_DFL : 시그널을 무시하도록 지정
- SIG_IGN : 시그널의 기본 처리 방법을 수행하도록 지정 (SIGKILL, SIGSTOP는 무시할 수 없다.)
- 그 외의 경우, func는 신호가 발생했을 때 호출될 함수를 가리키는 포인터여야 한다.
SIGKILL, SIGSTOP 시그널에 대해서는 핸들러를 지정할수 없다. 이들 시그널은 무시할수도 없고 핸들러를 지정할수도 없이 단지 기본동작으로만 작동한다.
✅ 반환값
- 요청이 성공하면, signal()은 sig에 대해 가장 최근에 호출된 signal()에서의 func 값을 반환한다.
- 실패하면 SIG_ERR를 반환하며, errno에는 양의 값이 저장된다.
✅ sigaction()
#include <signal.h>
int sigaction(int sig, const struct sigaction *restrict act,
struct sigaction *restrict oact);
- 호출한 프로세스가 특정 시그널에 대해 어떤 동작을 수행할지 설정하는 함수로, 호출을 위해서는 sigaction 구조체 변수를 초기화해줘야 한다.
- sig : 시그널 핸들러로 처리하려는 시그널 번호
- SIGKILL, SIGSTOP 시그널을 제외한 모든 시그널을 지정할 수 있다.
- act : 시그널을 처리할 방법을 지정한 구조체 주소
- oact : 기존에 시그널을 처리하던 방법을 저장할 구조체 주소
- signal()로 설정된 핸들러의 정보는 sigaction()으로 조회할 경우 정확하지 않을 수 있다.
- 반환값 : 성공시 0을, 실패시 -1이 리턴되며, 새 핸들러는 생성되지 않는다.

✅ sigaction 구조체
sigaction 구조체는 시그널에 대해 수행할 동작을 기술한다.
struct sigaction {
int sa_flags; // sig_flag 기능 지정을 위한 상수
union {
void (*sa_handler)(int); // signal 핸들러 함수
void (*sa_sigaction)(int, siginfo_t *, void *);
// sa_flags가 SA_SIGINFO일때 sa_handler 대신에 동작하는 핸들러
} _funcptr;
sigset_t sa_mask; // 시그널 처리시 블록 지정할 시그널 마스크
}
sa_handler vs sa_sigaction
- sa_handler 와 sa_sigaction 은 메모리가 중첩된다.
- sa_flags에서 SA_SIGINFO 플래그가 꺼져 있으면 sa_handler를 사용한다.
- sa_flags에서 SA_SIGINFO가 켜져 있으면 sa_sigaction이 사용되며, 다음과 같이 호출된다.
-
void func(int signo, siginfo_t *info, void *context); - info : 시그널이 발생한 이유 등의 정보가 담긴 siginfo_t 구조체
- context : 시그널 발생 시점의 스래드 상태 (uncontext_t* 로 캐스팅 가능)
siginfo_t 구조체 주요 내용
- si_signo: 시그널 번호
- si_errno: 오류 번호 (0이 아닐 경우, 시그널 발생 원인 제공)
- si_code: 시그널 발생 원인을 식별하는 코드
sa_mask
- 시그널 핸들러가 동작 중일 때 블록할 시그널을 시그널 집합으로 지정한다.
- 시그널 핸들러가 시작되어 시그널을 전달할 때 이미 블록된 시그널 집합에 sa_mask로 지정한 시그널 집합을 추가한다.
- sa_flags에 SA_NODEFER를 설정하지 않으면 시그널 핸들러를 호출하게 한 시그널도 블록된다.
sa_flags
시그널 전달 방법을 수정할 플래그로, sa_flags에 지정할 수 있는 값은 <sys/signal.h> 파일에 정의되어 있다.
| flag | |
| SA_NOCLDSTOP | 이 값을 설정하고 시그널 SIGCHLD을 처리하는 경우, 자식 프로세스가 중지 혹은 재시작될 때에 부모 프로세스에 SIGCHLD를 전달하지 않는다. |
| SA_NOCLDWAIT | 이 값을 설정하고 시그널 SIGCHLD을 처리하는 경우, 시스템은 자식 프로세스가 종료될 때 좀비 프로세스를 만들지 않는다. |
| SA_NODEFER | 이 값을 설정하고 시그널을 받으면, 해당 시그널 처리 중에 Unix 커널이 해당 시그널을 자동으로 블록하지 못하도록 한다. |
| SA_NOMASK | SA_NODEFER 와 동일 |
| SA_ONSTACK | 이 값을 설정하고 시그널을 받으면, sigaltstack 함수를 호출하여 대체 시그널 스택이 있을 시 대체 스텍에서 처리하게 한다. |
| SA_RESETHAND |
|
| SA_ONESHOT | SA_RESETHAND 와 동일 |
| SA_RESTART | 시그널은 프로세스의 진행을 비동기적으로 interupt하는 성질이 있는데, 이 값을 설정하고 시그널을 발동시키면 원래 진행되던 프로세스는 시그널 핸들러 호출 및 처리 이후 재개된다. |
| SA_SIGINFO |
|
✅ siginfo_t 구조체
siginfo_t {
int si_signo; /* 시그널 번호*/
int si_errno; /* 시그널 관련 오류번호 */
int si_code; /* 시그널 발생 원인을 정의하는 코드 */
int si_trapno; /* Trap number that caused hardware-generated signal
(unused on most architectures) */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal 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) */
}
POSIX에서는 시그널을 개별적으로 처리하지 않고 복수의 시그널을 처리할 수 있게 시그널 집합 개념을 도입했다. 시그널 집합을 사용하면 여러 시그널을 지정해 처리할 수 있다.

✅ sigfillset()
#include <signal.h>
int sigemptyset(sigset_t *set);
- set이 가리키는 시그널 집합을 비어 있는 상태로 초기화한다.
- 반환값: 성공 시 0을 반환하고 오류 시 -1을 반환한다.
✅ sigfillset()
#include <signal.h>
int sigfillset(sigset_t *set);
- set을 가득 찬 집합, 즉 모든 시그널이 포함된 상태로 초기화한다.
- 반환값: 성공 시 0을 반환하고 오류 시 -1을 반환한다.
✅ sigaddset()
#include <signal.h>
int sigaddset(sigset_t *set, int signum);
- signum으로 지정된 시그널을 set에 추가한다.
- 반환값: 성공 시 0을 반환하고 오류 시 -1을 반환한다.
✅ sigdelset()
#include <signal.h>
int sigdelset(sigset_t *set, int signum);
- signum으로 지정된 시그널을 set에서 제거한다.
- 반환값: 성공 시 0을 반환하고 오류 시 -1을 반환한다.
✅ sigismember()
#include <signal.h>
int sigismember(const sigset_t *set, int signum);
- signum 시그널이 set 안에 포함되어 있는지를 확인한다.
- 반환값: 시그널이 포함되어 있으면 1, 포함되어 있지 않으면 0, 오류 발생 시 -1 을 반환한다.
🔷 시그널 API 함수 사용 프로그램
- 처음 SIGUSR1 시그널을 수신하면 "SIGUSR1 first"를 출력하고, 두번째 SIGUSR1 시그널을 수신하면 "SIGUR1 second"를 출력하고 세번째 SIGUSR1 시그널을 수신하면 프로그램을 종료하는 프로그램을 구현하시오.
- SIGTERM 시그널을 수신해도 프로그램은 종료되면 안된다. 프로그램은 3번째 SIGUSR1 시그널을 받을때만 종료되어야 한다.
- signal() 사용하는 버전과 sigaction() 사용하는 버전 2개를 프로그래밍한다.
[ signal ]
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
int count = 0;
void handler(int sig) {
count ++;
if(sig == SIGUSR1) {
if(count == 1) {
printf("SIGUSR1 first\n");
} else if (count == 2) {
printf("SIGUSR1 second\n");
} else if (count >= 3) {
printf("SIGUSR1 third\n");
exit(0);
}
} else if (sig == SIGTERM) {
printf("ignore SIGTERM signal\n");
}
}
int main(void) {
pid_t pid = getpid();
printf("현재 PID: %d\n", pid);
if(signal(SIGUSR1, handler) == SIG_ERR) {
printf("[ERROR] failed signal\n");
return -1;
}
while(1) {
pause();
}
}
[ sigaction ]
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
int count = 0;
void action(int sig, siginfo_t *info, void *context) {
count++;
if(sig == SIGUSR1) {
if(count == 1) {
printf("SIGUSR1 first\n");
} else if (count == 2) {
printf("SIGUSR1 second\n");
} else if (count >= 3) {
printf("SIGUSR1 third\n");
exit(0);
}
} else if (sig == SIGTERM) {
printf("ignore SIGTERM signal\n");
}
}
int main() {
pid_t pid = getpid();
printf("현재 PID: %d\n", pid);
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO; // use sa_sigaction
act.sa_sigaction = action;
sigaction(SIGUSR1, &act, NULL);
sigaction(SIGTERM, &act, NULL);
while(1) {
pause();
}
}'Server > Linux, C' 카테고리의 다른 글
| [C언어] IPC(2) (feat. 메세지 큐) (0) | 2025.10.19 |
|---|---|
| [C언어] IPC(1) (feat. 공유 메모리) (0) | 2025.10.19 |
| [C언어] 메모리 (feat. 주소록 프로그램) (0) | 2025.09.17 |
| [C언어] 동기화 API (feat. Rwlock API 사용해 은행 잔고 프로그램 구현하기) (2) | 2025.09.12 |
| [C언어] 동기화 (feat. Mutex API 사용해 은행 잔고 프로그램 구현하기) (0) | 2025.09.12 |