Home Minitalk ② 배경 지식 : 시그널
Post
Cancel

Minitalk ② 배경 지식 : 시그널

시그널이란

비동기적으로 발생한 이벤트를 처리하기 위한 메커니즘을 제공하기 위한 소프트웨어 인터럽트

  • 인터럽트 : 예상하지 못한 이벤트
  • 인터럽트 핸들링

interrupt

시그널의 라이프사이클

  1. 발생
    • 프로그램에서 발생한 예외적 사항
    • 사용자의 입력
    • 프로세스 또는 커널에서 생성/전달
  2. 보관
    • 시그널 전달 전까지, 커널이 보관
    • 전달 가능해지면 해당 프로세스에게 전달
  3. 처리
    • 지정된 방법에 따라 시그널 처리
      • 시그널을 무시함
      • 시그널을 캐치해서 핸들링함
      • 디폴트 액션을 함 (정의해 놓지 않은 경우)

유닉스 계열에서의 시그널

심볼릭 상수

상수로 정의됨 (ex. SIGHUP : 1, SIGINT : 2, … )

  • signal.h 라이브러리에 정의됨
  • man signal에서 확인가능
  • 시스템별로 번호가 다를 수 있음

signum
맥에서는 위와 같이 정의가 되어있음. 과제에서 허용된 시그널인 SIGUSR1, SIGUSR2의 경우 각각 30, 31로 정의되어있음.

시그널 처리

Signal handler

  • 특정 시그널을 처리하기 위해 지정된 함수
  • Default handler을 대체할 수 있음 (단, SIGKILL, SIGSTOP은 예외)

signal 함수

시그널이 들어올 때 처리해줄 핸들러를 등록하는 것. 이 자체만으로는 특별한 동작을 하진 않음!

1
2
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
  • 매개변수
    • signum : 처리할 시그널의 번호
    • handler : 시그널 핸들러의 함수포인터
  • 반환값
    • 기존 핸들러의 함수포인터
    • SIG_ERR : 에러

handler 등록

예제 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void sigint_handler(int signo)
{
    printf("Caught SIGINT!\n");
    psignal(signo, "Received signal");
}

int main(void)
{
    if (signal(SIGINT, sigint_handler) == SIG_ERR)
    {
        fprintf(stderr, "Cannot handle SIGINT!\n");
        exit(EXIT_FAILURE);
    }

    for (;;)
        pause(); // pause(2) : waiting a signal (any signal)
    return 0;
}

위 코드를 실행시킨 후 ctrl + c 를 누르면 아래의 메시지가 출력됨

실행 결과

1
2
^CCaught SIGINT!
Received signal: Interrupt

이 상태에서 종료하고 싶으면 ctrl + z (SIGTSTP)로 백그라운드로 이동해서 ps로 pid를 확인해서 kill -9 <pid> (9 : SIGKILL)로 종료시키면 됨

1
2
kill -9 87116
[1]  + 87116 killed     ./a.out

handler, signal 상속 방법

On fork()

  • 시그널 처리 방식 상속받음
    • signal handler, ignore, default
  • pending 시그널은 상속 x
    • 해당 시그널은 부모에게 전달된 것임
    • pending signal : 아직 처리되지 않은 시그널

On exec()

  • signal handler 상속 x
    • ignore, default는 상속받음
  • pending signal 상속

시그널 전송

관련 함수

int kill(pid_t pid, int signo)

프로세스에 시그널을 보냄

  • 매개변수
    • pid : 시그널을 보낼 대상
      • 0 : 자신이 속한 프로세스 그룹 내 모든 프로세스
      • -1 : 현재 프로세스가 가진 권한으로 시그널을 보낼 수 잇는 모든 프로세스
      • -1보다 작은 값 : GID == pid 인 프로세스 그룹
    • signo : 보낼 시그널 번호
  • 리턴값
    • 0 : Success
    • -1 : Fail
      • errno = EINVAL : 유효하지 않은 signo
      • errno = EPERM : 시그널을 보낼 권한이 없음
      • errno = ESRCH : 대상 프로세스가 존재하지 않음 (또는 zombie process)
        • zombie process : 프로세스가 종료되었는데 시스템상에 해당 정보가 남아있는 경우

전송 권한

다른 프로세스에게 시그널을 보내기 위해서는 적합한 권한이 필요함

필요 권한

  • Sender’s RUID/EUID == Receiver’s RUID/SUID
  • root는 모든 프로세스에게 시그널 전달 가능

권한 체크

  • null signal 활용 (실제 시그널을 전달하지 않으나 error checking을 수행함)
1
2
3
4
5
6
7
int ret;

ret = kill(1722, 0);
if (ret != 0)
	// 권한 없음
else
	// 권한 있음

시그널 차단

필요한 이유

  • 시그널은 임의의 순간 발생함 (중요 작업을 하는 중에 시그널이 들어올 수 있음)
  • 위처럼 Critical region의 보호를 위해 시그널의 차단이 필요함

Signal set

  • 복수개의 시그널을 처리하기 위해 사용
  • sigset_t (=bit mask) : 각 비트가 시그널 번호와 1대 1 매핑

예제 코드

1
2
3
4
5
6
7
8
9
10
// init
int sigemptyset(sigset_t *set); // the set to empty
int sigfillset(sigset_t *set); // the set to full

// add/delete signum to the set
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);

// return 1 if signum is in the set
int sigismember(const sigset_t *set, int signum);

관련 함수

int sigprocmask (int how, const sigset_t *set, sigset_t *oldset);

시그널을 블록시킬지 말지를 결정함

  • 매개변수
    • how : 지정방법
      • SIG_SETMASK : set을 blocking mask로 적용
      • SIG_BLOCK : blocking 대상을 추가
      • SIG_UNBLOCK : blocking 대상에서 제거
    • set : 적용할 signal set
      • NULL : how를 무시(signal mask 유지), 현재 signal mask → oldset
    • oldset : 적용 전 signal set을 반환
  • 리턴값
    • 0: 성공
    • -1 : 실패
  • pending signal은 차단이 풀리면 전달됨

예제 코드

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
32
33
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void handler_SIGINT(int _signo)
{
    printf("Received Signal : %s\n", strsignal(_signo));
}

int main(void)
{
    sigset_t new;

    if (signal(SIGINT, handler_SIGINT) == SIG_ERR)
    {
        perror("signal SIGINT");
        exit(1);
    }

    sigemptyset(&new);
    sigaddset(&new, SIGINT);
    sigaddset(&new, SIGQUIT);
    sigprocmask(SIG_BLOCK, &new, (sigset_t *)NULL);

    sleep(5);

    printf("UnBlocking Signals\n");
    sigprocmask(SIG_UNBLOCK, &new, (sigset_t *)NULL);

    return 0;
}

위 코드를 실행한 후에 SIGINT를 주면 아래와 같이 5초간은 신호가 가지 않다가 5초 후에 신호를 받는 것을 알 수 있음.

실행 결과

1
2
^CUnBlocking Signals
Received Signal : Interrupt: 2

시그널 대기

관련 함수

int sigsuspend(const sigset_t *mask);

Signal mask를 임시 교체 후, block 되지 않은 시그널이 도착할 때 까지 대기함

  • 매개변수
    • mask : 교체할 signal set의 주소
  • 반환값
    • 항상 -1
    • errno = EINTR → signal에 의해 interrupt 발생

예제 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <unistd.h>
#include <signal.h>
#include <stdio.h>

void handler(int signo) {
    psignal(signo, "Received Signal:");
}

int main(void) {
    sigset_t set;

    signal(SIGALRM, handler);

    sigfillset(&set);
    sigdelset(&set, SIGALRM);

    alarm(3);

    printf("Wait...\n");

    sigsuspend(&set);

    return 0;
}

실행 결과

1
2
Wait...
Received Signal:: Alarm clock

Ref.

https://www.youtube.com/watch?v=ezVbjE29Lpg

This post is licensed under CC BY 4.0 by the author.

함수 포인터

Minitalk ③ 배경 지식 : 기타