시그널이란
비동기적으로 발생한 이벤트를 처리하기 위한 메커니즘을 제공하기 위한 소프트웨어 인터럽트
- 인터럽트 : 예상하지 못한 이벤트
- 인터럽트 핸들링
시그널의 라이프사이클
- 발생
- 프로그램에서 발생한 예외적 사항
- 사용자의 입력
- 프로세스 또는 커널에서 생성/전달
- 보관
- 시그널 전달 전까지, 커널이 보관
- 전달 가능해지면 해당 프로세스에게 전달
- 처리
- 지정된 방법에 따라 시그널 처리
- 시그널을 무시함
- 시그널을 캐치해서 핸들링함
- 디폴트 액션을 함 (정의해 놓지 않은 경우)
- 지정된 방법에 따라 시그널 처리
유닉스 계열에서의 시그널
심볼릭 상수
상수로 정의됨 (ex. SIGHUP : 1, SIGINT : 2, … )
- signal.h 라이브러리에 정의됨
- man signal에서 확인가능
- 시스템별로 번호가 다를 수 있음
맥에서는 위와 같이 정의가 되어있음. 과제에서 허용된 시그널인 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 : 보낼 시그널 번호
- pid : 시그널을 보낼 대상
- 리턴값
- 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을 반환
- how : 지정방법
- 리턴값
- 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