본문 바로가기
programming/Unix

[Unix Programming] 8. 시그널

by je0nsye0n 2024. 12. 12.

Signal이란?

  • software interrupt로 이해하면 된다.
  • 본래 목적은 실행 프로세스를 중단시키는 것이 목표이며, 자료 전송보다는 비정상적인 상황을 알릴 때 사용한다.

(예) 프로그램 수행 중 “CTRL+C”를 누르면, 커널이 문자를 감지한 후 해당 세션에 있는 모든 프로세스에게 SIGINT라는 시그널을 보낸다. 모든 프로세스는 시그널을 받으면 종료한다. 단, shell process는 이를 무시한다.

  • 시그널 종류 (자주 등장하는 것 위주로 정리)
2 SIGINT interrupt key(CTRL+C) 입력했을 때
9 SIGKILL 프로세스를 kill하기 위한 시그널 (catch or ingnore 될 수 없는 시그널)
10 SIGUSR1 user defined signal1
12 SIGUSR2 user defined signal2
14 SIGALRM alarm 시스템 콜 후, timer가 expire된 경우

- 출처 : https://jangpd007.tistory.com/90

  • 시그널의 기본처리
    • 종료(시그널에 의한 종료)
    • 코어덤프 후 종료(시그널에 의한 비정상 종료) : core file
    • 중지
    • 무시
  • 시그널의 종료 상태 확인하기
pid = wait(&status);

// 정상종료인지 확인
if(WIFEXITED(status)) 
	printf("정상적으로 종료");

// 시그널을 받고 종료
if(WIFSIGNALED(status))
	printf("시그널%d에 의한 종료",WTERMSIG(status)); // WTERMSIG를 통해 시그널 번호 확인

 

Signal 사용하기

  • 사용 헤더파일

#include <sys/types.h>

#include <signal.h>

 

< kill 시스템콜 >

int kill(pid_t pid, int sig);
  • pid : signal을 받게 될 프로세스 지정
  • sig : 보낼 시그널 저장

*** 단, pid를 사용하여 시그널을 받을 프로세스를 여러 방면으로 지정할 수 있음 ***

#1. pid > 0 : 해당 id의 프로세스에게 시그널 전달

#2. pid = 0 : sender와 같은 프로세스 그룹에 속하는 모든 프로세스(sender 포함)에게 시그널 전달 → 내가 속한 그룹

#3. pid = -1 : uid가 sender의 euid와 같은 모든 프로세스(sender 포함)에게 시그널 전달

#4. pid < 0 and pid ≠ -1 : 프로세스의 그룹 아이디가 pid의 절댓값과 같은 모든 프로세스에게 시그널 전달 → 내가 원하는 그룹 지정

다른 사용자의 프로세스에게 시그널을 보내면 -1 return

 

< raise 시스템콜 >

int raise(int sig);
  • 호출 프로세스에게 시그널을 보냄

 

< Signal Handling - sigaction 시스템콜 >

  • Signal Handling :
    • 기본 동작 수행(default action) : 각 시그널을 기본 동작이 지정되어 있음
    • 지정된 함수 수행(정의된 action) : 특정 함수 만들어서 실행
    • 시그널 무시
  • sigaction

→ sigaction 지정 : signal을 받았을 때의 취할 행동을 지정할 수 있다

(단, SIGSTOP, SIGKILL은 별도의 action을 지정할 수 없음)

// sigaction 방법
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);

// sigaction 구조체
struct sigaction{
	void (*sa_handler)(int);
	sigset_t sa_mask;
	int sa_flags;
	void (*sa_sigaction)(int, siginfo_t *, void *)
}
  • void(*sa_handler)(int);

-signo를 수신하면 취할 행동을 지정

-행동 리스트

  1. SIG_DFL : default 행동 = 종료
  2. SIG_IGN : 무시
  3. 정의된 함수 : signal을 받으면 함수(사용자가 설정)로 제어 이동. 함수 실행 후, 시그널을 받기 직전의 처리 문장으로 return.
  • sigset_t sa_mask;

-여기 정의된 시그널들은 sa_handler에 의해 지정된 함수가 수행되는 동안 blocking된다.

-당장 처리하지 않고 현재 처리 중인 것 끝내고 처리하겠다. 우선순위가 높은 시그널 우선 처리할 수 있도록 하게 함.

  • int sa_flags
    • SA_RESETHAND : handler로부터 복귀 시 signal action을 SIG_DFL로 재설정
    • SA_SIGINFO : sa_handler 대신 sig_action 사용

(예제1) sigaction 사용

  • 시그널 받았을 때 정의된 함수 실행시키는 법
#include <signal.h>

int main(int argc, char **argv) {
  static struct sigaction act;
  void catchint(int);

  act.sa_handler = catchint; // 시그널을 받으면 사용자가 정의한 함수인 catchint로 액션 수행
  sigaction(SIGINT, &act, NULL);

  printf("sleep call\\\\n");
  sleep(1);
  printf("exiting\\\\n");
  exit(0);
}

void catchint(int signo) {
  printf("CATCHINT : %d\\\\n", signo);
  psignal(signo, "[Received Signal]");
}

(예제2) 시그널 받으면 무시/종료 설정

// 1. 무시
act.sa_handler = SIG_IGN;
sigaction(SIGINT, &act, NULL); // 이 경우에는 컨트롤씨를 눌러도 어떠한 액션을 취하지 않음

// 2. 종료
act.sa_handler = SIG_DFL;
sigaction(SIGINT, &act, NULL);
  • 여러 개의 시그널 무시

→ 한 프로세스에서 무시되는 시그널은 exec() 후에도 계속 무시됨.

왜?) exec로 실행하면 프로세스가 동일하기 때문에 시그널을 계속 무시하게 된다.

 

< 시그널 집합 지정하기 >

  • sigemptyset → sigaddset : 전부 0으로 설정한 후 block할 시그널 설정
  • sigfillset → sigdelset : 전부 1로 설정한 후 block하지 않을 시그널 설정
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);

int sigaddset(sigset_ *set, int signo);
int sigdelset(sigset_ *set, int signo);
sigset_t mask1, mask2;

sigemptyset(&mask1);
sigaddset(&mask1, SIGINT);
sigaddset(&mask1, SIGQUIT);

sigfillset(&mask2);
sigdelset(&mask2, SIGCHLD);

 

< 과거 설정 복원하기 >

sigaction(SIGTERM, NULL, &oact); // 과거 설정 저장

act.sa_handler = SIG_IGN;
sigaction(SIGTERM, &act, NULL);
// do anything!
sigaction(SIGTERM ,&oact, NULL) 

 

Signal 사용하기 (2)

< 시그널 알람 설정 >

unsigned int alarm(unsigned int secs);
  • secs : 초 단위의 시간 → 시간 종료 후 SIGALRM을 보냄
  • alarm은 exec 후에도 계속 작동함 but! fork 후에는 자식 프로세스에ㅔ 대한 alarm은 작동하지 않음
  • alarm(0) → alarm 끄기
  • alarm은 누적되지 않는다 == 2번 사용되면 2번째 alarm으로 대체되는 것
    • 2번째 알람의 return 값 : 첫 alarm의 잔여시간

 

< 시그널 blocking >

int sigprocmask(int how, const sigset_t *set, ingset_t *oset);

-oset은 봉쇄된 시그널들의 현재 mask → 관심 없으면 NULL로 설정

-시그널 집합을 사용해서 한 번에 여러 시그널을 블록할 수 있음

  • how : 시그널을 블록할지 해제할지의 여부
  • set : 블록하거나 해제할 시그널 집합의 주소

*** 시그널 블록 ***

  • SIG_BLOCK
  • SIG_UNBLOCK
  • SIG_SETMASK

 

< pause 시스템 콜 >

int pause(void);
  • signal 도착까지 실행을 일시 중단
  • signal이 포착되면 처리 루틴 수행 or -1 return