본문 바로가기
Study/System Programming

sigprocmask을 사용하여 signal block 하기

by 개발새-발 2021. 10. 16.
반응형

리눅스에서는 프로세스에 Signal을 전달할 수 있다. 그리고 프로세스는 이 Signal에 의한 처리를 막기 위해 원하는 Signal을 Block 할 수 있다. 이를 가능하게 하는 것이 sigprocmask이다.

sigset_t

우리는 원하는 Signal을 block 하기 위하여 특정 구조체를 사용하여야 한다. 그 구조체는 sigset_t이다. 사실 sigset_t의 구조는 그리 복잡하지 않다. 정수형 자료형의 배열이라고 생각해도 무관하다. 비트마스크를 사용하기 때문에 사용되는 Signal의 종류 수만큼의 비트 수가 필요하다. kill -l 명령어를 통해 어떤 signal이 몇번인지 볼 수 있다.

$ kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL
 5) SIGTRAP      6) SIGABRT      7) SIGBUS       8) SIGFPE
 9) SIGKILL     10) SIGUSR1     11) SIGSEGV     12) SIGUSR2
13) SIGPIPE     14) SIGALRM     15) SIGTERM     16) SIGSTKFLT
17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU
25) SIGXFSZ     26) SIGVTALRM   27) SIGPROF     28) SIGWINCH
29) SIGIO       30) SIGPWR      31) SIGSYS      34) SIGRTMIN
35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3  38) SIGRTMIN+4
39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12
47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14
51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10
55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7  58) SIGRTMAX-6
59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

만약 위의 64개의 경우를 모두 표현하고 싶다면 64비트 이상이 되도록 배열의 크기를 그에 맞게 조정하여 사용해야 하는데, 그 결과물이 sigset_t이다. sigset_t는 위의 경우들을 비트마스크로 모두 표현하고도 남는 크기를 가지고 있다. 64비트 ubuntu 18.04 버전 기준으로 sizeof(sigset_t)의 결과를 보면 128이라는 수를 볼 수 있다. 즉, 1024 비트 라는 것인데, 왜 이렇게 큰지는 모르겠으나 일단 64개의 조합의 경우의 수를 표현하는 데에는 문제가 없다. 이제 우리는 관련 함수들을 이용하여 block 하고싶은 Signal들을 sigset_t에 담을 것이다. 앞에서 길게 적어놓았지만, 단순하게 그저 다루고자 하는 signal의 집합이라고 생각하면 된다.

sigset_t 조작하기

sigset_t에 원하는 signal들을 담을 것이다. 그전에 어떤 sigset_t를 비우거나 꽉 채우는 함수 두개를 소개한다.

  • sigfillset(sigset_t* set) : set에 모든 signal을 추가한다. 성공시 0, 실패시 -1 반환
  • sigemptyset(sigset_t* set) : set의 모든 signal을 비운다. 성공시 0, 실패시 -1 반환

아래 함수는 sigset_t에 원하는 signal을 추가하거나 제거하는 함수들이다.

  • sigaddset(sigset_t set, int signo) : set에 signo에 해당하는 signal을 추가한다. 성공시 0, 실패시 -1을 반환한다.
  • sigdelset(sigset_t set, int signo) : set에서 signo에 해당하는 signal을 제거한다. 성공시 0, 실패시 -1을 반환한다.

아래는 사용 예시이다.

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

int main(void){
    sigset_t mask;

    sigfillset(&mask); // mask에 모든 signal이 추가된다.
    sigemptyset(&mask); // mask에서 모든 signal을 제거한다.
    sigaddset(&mask,SIGINT); // mask에 SIGINT signal을 추가한다.
    sigdelset(&mask,SIGINT); // mask에서 SIGINT signal을 제거한다.

    return 0;
}

sigprocmask

sigprocmask를 사용하면 SIGKILL 과 SIGSTOP을 제외하고 원하는 signal들을 block할 수 있다. (SIGKILL과 SIGSTOP은 block이 불가능하고, handler도 설정할 수 없다는 것을 기억하자.) sigprocmask의 형태는 다음과 같다.

  • sigprocmask(int how, const sigset_t *set, sigset_t *oset)

how 값에 따라 sigprocmask의 동작이 다르다. how값은 SIG_BLOCK, SIG_SETMASK, SIG_UNBLOCK 중 하나이어야 한다. 아래는 how값에 따른 동작이다.

  • SIG_BLOCK : set에 등록된 signal들의 block을 설정한다.
  • SIG_UNBLOCK : set에 등록된 signal들의 block을 해제한다.
  • SIG_SETMASK : set에 등록된 signal들을 block 하도록 설정하고 set에 등록되지 않은 signal들의 block은 해제한다.

여기서 SIG_BLOCKSIG_SETMASK의 차이는 SIG_BLOCKsigprocmask 사용 이전에 block 하던 signal들과 set에 등록한 signal들을 같이 block을 하지만, SIG_SETMASK은 set에 등록된 signal들만 block 한다는 차이점이 있다.

osetsigprocmask를 사용하기 이전에 block 하던 signal들의 정보를 담을 sigset_t*이다. 담고 싶지 않다면 null값을 주면 된다. 만약 set의 값이 null값인데 oset의 값이 설정되어 있다면, 지금 block 하도록 설정된 signal들의 집합을 oset에 담아서 주는 일 말고 다른 일은 하지 않는다.

sigprocmask 사용 예제

Ctrl-c를 누름으로 SIGINT signal을 보낼 수 있다. 프로세스가 SIGINT signal을 받으면 기본적으로는 해당 프로세스는 종료된다. 그렇다면 SIGINT signal을 block 하여 crtl+c를 눌러도 프로그램이 종료되지 않게 할 수 있을까?

아래 프로그램은 처음 시작하자마자 SIGINT signal을 block 한다. 그리고 10초 후 SIGINT를 block 하였던 것을 원래대로 되돌린다. 이후 10초간 아무것도 하지 않다가 프로그램을 종료한다. 아래 c 코드를 컴파일하여 만든 실행파일을 sigp라 하자.

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

int main(void){
    sigset_t mask,prev;

    sigemptyset(&mask);
    sigaddset(&mask,SIGINT);
    sigprocmask(SIG_BLOCK,&mask,&prev);

    printf("SIGINT blocked.\n");
    sleep(10);

    printf("SIGINT unblocked.\n");
    sigprocmask(SIG_SETMASK,&prev,0);
    sleep(10);

    printf("End\n");
}

아래는 몇 가지 경우에 대한 결과이다. 일단, 20초 동안 건들지 않은 경우에는 마지막 End까지 출력이 되면서 프로그램이 종료된다.

$ ./sigp
SIGINT blocked.
SIGINT unblocked.
End
$ 

SIGINT의 block을 해제한 후 Ctrl+c를 누르는 경우 프로그램이 바로 종료되는 것을 볼 수 있다.

$ ./sigp
SIGINT blocked.
SIGINT unblocked.
^C
$ 

그렇다면 SIGINT가 block 되어 있는 동안 Ctrl+c를 누르면 무슨 일이 일어날까?

$ ./sigp
SIGINT blocked.
^C
^C
^C
^C
SIGINT unblocked.
$ 

위 결과를 보면 SIGINT가 block 되어있는 동안 Ctrl+c를 아무리 많이 눌러도 프로그램이 종료되지 않는 것을 볼 수 있었다. 이후 SIGINT의 block을 해제해주면 block 되어있었던 SIGINT signal이 프로그램으로 들어오게 되고 그 결과 프로그램이 종료되는 것을 볼 수 있다.

반응형

'Study > System Programming' 카테고리의 다른 글

lseek, pread, pwrite 사용하기  (0) 2021.11.17
open, read, write, close 사용하기  (0) 2021.11.15
wait, waitpid의 사용  (0) 2021.10.19
fork 사용하기  (0) 2021.10.17

댓글