Notice
Recent Posts
Recent Comments
Link
«   2026/01   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

우당탕탕 개발일지

[C언어] 시그널 (feat. Sigaction) 본문

Server/Linux, C

[C언어] 시그널 (feat. Sigaction)

YUDENG 2025. 9. 30. 13:18

 

시그널

 

운영체제는 실행하고 있는 프로그램에 대해 예외적인 상황을 보고하기 위해서 시그널(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
  • 시그널을 받고 핸들러 함수를 호출한 이후, 해당 시그널을 기본 동작(SIG_DFL)로 설정하는 동시에 SA_SIGINFO도 비활성화된다. 즉 시스템 초기값으로 재설정한다.
  • 처리 중 해당 시그널을 블록하지 않는다.
SA_ONESHOT SA_RESETHAND 와 동일
SA_RESTART 시그널은 프로세스의 진행을 비동기적으로 interupt하는 성질이 있는데, 이 값을 설정하고 시그널을 발동시키면 원래 진행되던 프로세스는 시그널 핸들러 호출 및 처리 이후 재개된다.
SA_SIGINFO
  • 이 값을 설정하고 시그널을 받으면, sa_handler 대신 sa_sigaction 이 발동된다.
  • 시그널이 발생한 원인을 알 수 있다.

 

 
 

 

✅ 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 함수 사용 프로그램

  1. 처음 SIGUSR1 시그널을 수신하면 "SIGUSR1 first"를 출력하고, 두번째 SIGUSR1 시그널을 수신하면 "SIGUR1 second"를 출력하고 세번째 SIGUSR1 시그널을 수신하면 프로그램을 종료하는 프로그램을 구현하시오.
  2. SIGTERM 시그널을 수신해도 프로그램은 종료되면 안된다. 프로그램은 3번째 SIGUSR1 시그널을 받을때만 종료되어야 한다.
  3. 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();
	}
}
728x90