[리눅스] 시그널 (SIGNAL) 2 시그널 함수 sigprocmask, sigfillset, sigemptyset, sigaddset, sigdelset
컴퓨터/운영체제(주로 리눅스) 2018. 12. 31. 01:34시그널에 대한 더 많은 정보를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.
https://reakwon.tistory.com/233
시그널 관련 함수
지난 시간에는 간단하게 시그널 기본적인 설명과 어떻게 핸들링하는지에 대해서 알아보았죠?
시그널 개념과 시그널 핸들러는 지난 포스팅을 참고하시기 바랍니다.
https://reakwon.tistory.com/46
여러개의 신호를 간편하게 다루기 위해서는 신호를 집합으로 표시하는 자료 형식이 필요하게 됩니다. 단순히 생각해보면 int 형식을 한비트마다 하나의 신호로 대응시켜 집합으로 표시할 수도 있지만, int의 비트보다 더 많은 신호가 존재할 수 있기 때문에 신호만의 새로운 자료 형식이 필요하게 되었습니다. int의 비트 32비트이고 신호는 32개가 넘게되니까요. 이러한 자료 형식이 sigset_t라는 자료 형식입니다. sigset_t는 신호의 집합임을 기억합시다.
이런 신호 집합을 왜 쓰게 될까요? 앞에서 얘기했다시피 많은 신호를 간편하게 다루기 위함입니다. 모든 신호를 막는다거나(BLOCK), 막은 신호를 다시 푼다던가(UNBLOCK), 신호가 발생했지만 Block되어서 대기(PENDING) 중인 신호가 무엇이 있는가, 이러한 작업을 쉽게 할 수 있습니다. 프로세스는 신호 집합을 가지고 있고 관리합니다. 이와 연관된 함수가 추가로 필요하게 되는데, 그것이 오늘 소개할 함수들입니다.
시그널 중에서 SIGSTOP와 SIGKILL은 절대 제어할 수 없으므로 알아두시기 바랍니다.
o sigfillset, sigemptyset
int sigfillset(sigset_t *set);
int sigemptyset(sigset_t *set);
이 두 함수는 setset_t라는 집합에서 모든 시그널을 추가하거나 모든 시그널을 제거합니다. 성공시 0, 실패시 -1을 반환하구요. 아래는 sigfillset과 sigemptyset의 동작과정을 말해줍니다.
sigfillset을 사용하면 모든 시그널을 집합에 추가하는데요. 반대로 sigemptyset은 set이라는 집합에서 시그널을 전부 깨끗이 지워줍니다.
o sigaddset, sigdelset
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
이 함수들은 이름에서 알 수 있듯 signal을 추가하거나 삭제합니다. 역시 성공시 0, 실패시 -1을 반환합니다.
기존의 SIGSEGV와 SIGHUP이 있는 set에서 SIGINT를 추가하면 set은 {SIGINT, SIGSEGV, SIGHUP}가 있게 됩니다. 거기서 SIGHUP을 sigdelset으로 제거하면 결국 set에는 {SIGSEGV, SIGINT}가 존재하게 되는 것입니다.
o sigprocmask
int sigprocmask(int how, const sigset_t *set, sigset_t oldset);
sigprocmask는 시그널을 블록시킬지 말지를 결정합니다.
how : 시그널을 어떻게 제어할지를 말해줍니다. how에는 세가지 동작이 있는데요.
1. SIG_BLOCK : 기존에 블록된 시그널이 있다면 두 번째 인자는 set의 시 그널을 추가하라는 의미입니다.
2. SIG_UNBLOCK : 기존의 블록된 시그널에서 set의 시그널을 제거합니 다.
3. SIG_SETMASK : 기존의 블록된 시그널을 전부 제거시키고 새로운 set 의 시그널들을 블록시킵니다.
set : how가 동작하는 방식에 따라 기존의 block된 시그널에 대해 set을 추가시킬지, 제거시킬지, 아니면 전부 set으로 설정할지를 의미합니다. 그러니 set은 이번에 설정할 시그널이라고 기억해두세요.
oldset : 이 전에 블록된 시그널들을 저장합니다.
예제 코드 1)
sigprocmask는 우선 이해하기가 조금 까다로운데요. 주석을 통해서 설명을 하긴 했지만 그림이 조금 필요하겠네요. 아래는 sigprocmask를 통해서 시그널을 제어하는 코드입니다.
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main(){
sigset_t set, oldset;
sigemptyset(&set);
sigemptyset(&oldset);
sigaddset(&set,SIGINT);
sigaddset(&set,SIGQUIT);
sigprocmask(SIG_BLOCK,&set,NULL);
printf("SIGINT와 SIGQUIT는 블록되었습니다.\n");
printf("Ctrl+C와 Ctrl+\\ 눌러도 반응이 없습니다.\n");
//만약 Ctrl + \(SIGQUIT)을 눌렀다면 5초후 Coredump가 생기고 종료
//SIGQUIT의 기본동작은 Coredump + 종료
sleep(5);
//현재 set에서 SIGINT를 뺌. set에는 SIGQUIT만 있는 상태
//중요한것은 프로세스에 적용하지 않은 상태
sigdelset(&set,SIGINT);
//프로세스에 Unblock을 set에 적용. SIGQUIT은 이제 Block되지 않음
sigprocmask(SIG_UNBLOCK,&set,&oldset);
printf("만약 Ctrl+\\을 눌렀다면 종료합니다.\n");
printf("현재 남은 시그널은 SIGINT입니다.\n");
printf("Ctrl+C를 눌러도 반응이 없습니다.\n");
sleep(5);
set=oldset;
sigprocmask(SIG_SETMASK,&set,NULL);
printf("다시 SIGINT와 SIGQUIT이 블록됩니다.\n");
printf("Ctrl+C와 Ctrl+\\ 눌러도 반응이 없습니다.\n");
sleep(5);
sigprocmask(SIG_UNBLOCK,&set,NULL);
//아무 시그널(Cntl +C 혹은 Cntl+\)을 주지 않았다면 아래의 메시지가 출력되고 종료
printf("모든 시그널이 해제되었습니다.\n");
}
이제 하나하나씩 까보도록 합시다.
1.
초기에 현재 프로그램의 블록된 시그널은 없습니다. set과 oldset도 역시 깨끗이 비워줍니다.
sigset_t set, oldset;
sigemptyset(&set);
sigemptyset(&oldset);
아래 그림을 보세요. 저의 머리처럼 비어있는 걸 알 수 있습니다.
이후 sigaddset으로 set에 SIGINT와 SIGQUIT을 추가합니다. 중요한것은 blocked signal에 추가한 것이 아닙니다. SIGINT의 단축키는 Ctrl+C, SIGQUIT의 단축기는 Ctrl+\랍니다.
2.
sigaddset(&set,SIGINT);
sigaddset(&set,SIGQUIT);
아래 그림에도 set 집합에서 SIGINT와 SIGQUIT이 추가된 것을 알 수 있군요.
3.
이 set에 있는 시그널들을 block 시키기 위해서 sigprocmask를 호출하는데 how의 인자는 SIG_BLOCK입니다.
sigprocmask(SIG_BLOCK,&set,NULL);
현재 블록된 시그널에 set의 시그널이 추가되었음을 알 수 있네요.
아까도 봤다시피 SIG_BLOCK은 현재 블록된 시그널에서 set의 시그널을 추가하는 겁니다.
만약 blocked signal에 SIGHUP이 존재한다면 sigprocmask(SIGBLOCK, &set, NULL) 호출 후 blocked signal 집합에는 {SIGHUP, SIGINT, SIGQUIT)이 됩니다.
그렇기 때문에 이제 SIGINT와 SIGQUIT는 이제 블록됩니다.
이후 5초간 SIGINT와 SIGQUIT 신호를 보내도 아무런 반응이 없는 걸 알 수 있나요?
4.
sigdelset(&set,SIGINT);
sigprocmask(SIG_UNBLOCK,&set,&oldset);
5초가 지나면 set에서 SIGINT를 제거하는 군요. 그렇다면 set에는 SIGQUIT만 남게 되죠. sigprocmask로 set에 있는 집합을 blocked signal에서 제거합니다.
하지만 oldset이 설정되어있었네요. 그러니까 oldset은 기존에 블록된 시그널이 잡히게 됩니다. 기존에 블록된 시그널은 위의 그림에서 SIGINT와 SIGQUIT이었군요. 그 후 blocked signal에서 set에 있는 시그널을 제거하게 되면 SIGINT만 남겠네요.
아래 그림에서 보여주듯 blocked signal에는 결국 SIGINT만 남게됩니다. 따라서 5초이내에 SIGQUIT을 보냈다면 프로그램이 코어덤프와 함께 종료됩니다. SIGQUIT의 블록 상태가 풀리게 되니까요.
5.
이후 set은 oldset의 값을 복사하네요.
set=oldset;
6.
sigprocmask(SIG_SETMASK,&set,NULL);
이제 SIG_SETMASK로 set에 있는 시그널들을 blocked signal에 설정합니다. 설정하는 것입니다. 추가하는 것이 아니에요.
그러니 blocked signal에는 SIGINT와 SIGQUIT이 다시 설정되는 것을 알 수 있죠.
그래서 다시 SIGINT와 SIGQUIT을 보내도 5초간 아무 응답이 없게 되지요.
7.
sigprocmask(SIG_UNBLOCK,&set,NULL);
5초가 지난 후 set에 있는 시그널들을 blocked signal에서 제거하게 되는데요. 만약 5초 전에 SIGINT나 SIGQUIT 신호를 주었다면 바로 종료하게 되는 겁니다.
왜냐면 5초가 지나면 아래의 그림처럼 blocked signal에서 SIGINT와 SIGQUIT이 존재하지 않으니 블록된 시그널은 풀리게 되면서 프로그램이 종료가 됩니다.
만약 SIGINT 또는 SIGQUIT를 보내지 않았다면 프로그램은 "모든 시그널이 해제되었습니다" 라는 메세지와 함께 종료될겁니다.
process의 signal 정보는 어디에?
그렇다면 프로그램 실행시, 이 process의 block된 시그널의 정보는 어디에 저장이 되고 관리가 될까요? 즉, 위의 그림에서 blocked signal은 누가 관리해주냐는 것입니다. 이에 대한 정보들은 kernel의 task_struct 구조체로 인해 관리가 됩니다. task_struct는 process나 thread가 실행될때마다 하나씩 생성이 되는데, 여기에 정말 많은 정보들이 들어있습니다. 다음은 signal 관련 field들입니다.
// linux/sched.h
struct task_struct {
#ifdef CONFIG_THREAD_INFO_IN_TASK
...
/* Signal handlers: */
struct signal_struct *signal;
struct sighand_struct *sighand;
sigset_t blocked;
sigset_t real_blocked;
/* Restored if set_restore_sigmask() was used: */
sigset_t saved_sigmask;
struct sigpending pending;
unsigned long sas_ss_sp;
size_t sas_ss_size;
unsigned int sas_ss_flags;
};
task_struct의 멤버를 보게되면 blocked, real_blocked라는 이름이 보이시죠? sigset_t 어디서 많이 보았죠? 이처럼 kernel에서 task_struct를 통해서 관리하여진다는 것입니다.
이상으로 signal 집합을 제어하는 함수들을 소개하고 알아보았습니다. 다음 포스팅은 sigpending과 관련된 함수에 대해서 알아보도록 하겠습니다.
'컴퓨터 > 운영체제(주로 리눅스)' 카테고리의 다른 글
[리눅스] 스레드(Thread) 개념과 예제(pthread_create, pthread_join, pthread_detach) (2) | 2019.01.06 |
---|---|
[리눅스] 시그널(Signal) 3 sigpending, sigismember, sigsuspend (0) | 2018.12.31 |
[리눅스] 시그널1 신호(SIGNAL)의 개념과 자세히 들여다 보기 - 시그널 핸들러 (0) | 2018.12.10 |
[리눅스] 프로세스 생성과 특징, 종료 (fork, wait), 예제 코드 (0) | 2018.12.10 |
[리눅스] 파일 정보 얻어오기(lstat), 사용 예제 (0) | 2018.11.19 |