-
- 시그널
프로세스가 실행되는 도중 예외 상황에 발생하는 이벤트 또는 신호를 시그널이라 부른다.
시그널의 정보와 그 의미는 일대일 매칭되기 때문에, 빠르게 전달하고 처리할 수 있다.
Linux, UNIX 계열 운영체제에서는 kill -l 명령어로 시그널의 종류를 확인할 수 있다.
시그널은 다음의 두 가지 용도로 특히 잘 사용되는데,
- 비동기적인 이벤트의 발생을 통지하기 위한 용도(e.g. 프로세스 종료)
- 프로세스 간의 통신이나 동기화 하기 위한 용도(e.g. 알람)
시그널은 구현에 따라 비동기, 동기적으로 처리될 수 있다.
비동기적: 시그널이 특정 시간에 발생하는 것을 기다리지 않음
동기적: 동작을 중단하고, 시그널이 발생하기를 기다림
시그널은 각 기본동작이 정의되어 있다. (시그널 핸들러를 굳이 구현 안해도 되는 이유),
- Abort: 코어 덤프(예외 시점의 프로세스의 상태를 기록하여 저장한 파일)를 생성하고, 프로세스를 종료함.
- Exit: 코어 덤프를 생성하지 않고 프로세스를 종료함.
- Abnormal termination: 프로세스를 비정상 종료함.
- Stop: 프로세스를 정지함.
- Continue: 프로세스를 재게함.
- Ignore: 시그널을 무시함.
- Implementation dependent: 구현에 의존함.
이렇듯 시그널은 기본 동작이 있지만, 시그널 핸들러를 만들어 다른 동작을 하게 할 수 있다.
단, SIGKILL, SIGSTOP은 재정의 할 수 없다.
시그널 생성(Signal generated): 어떤 프로세스가 특정 프로세스에게 시그널을 생성해 전송함.
시그널 처리(Signal handled): 시그널을 수신한 프로세스가 해당 시그널을 인지하고 동작을 수행함.
시그널 보류(Signal pending): 스케줄러에 의해 프로세스가 대기 상태인 경우, 시그널이 블록처리된 경우,
시그널이 중첩된 경우 잠시 대기 중인 상태.
시그널을 받으면 무엇이든 동작을 해야하지만, 다음과 같은 시그널 마스크를 걸면 시그널의 동작을 바꿀 수 있다.
Block: 프로세스가 해당 시그널의 블록을 해제할 때까지 그 시그널은 pending 상태로 남는다.
Ignore: 시그널은 전달되지만, 프로세스가 이 시그널은 무시한다.
- 시그널 함수
1. int kill(pid_t pid, int signum);
프로세스(pid)에 시그널(signum)을 전송함.
2. sighandler_t signal(int signum, sighandler_t handler);
프로세스에 전송된 시그널(signum)의 기본동작을 handler로 바꾼다.
void sigHandler(int signum); ... signal(SIGUSR1, sigHandler);
SIGUSR1 시그널 수신시 sigHandler를 실행한다. sigHandler의 인자 signum은 수신된 시그널이 들어간다.
3. int pause(void);
프로세스가 시그널을 수신할 때 까지 동작을 중지시킨다. (동기식 처리 방법)
4. unsigned int alarm(unsigned int seconds);
seconds초 후 프로세스 자신에게 SIGALRM 시그널을 전송한다.
5. int sighold(int signum);
signum에 해당하는 시그널을 블록 상태로 변경한다.
6. int sigrelse(int signum);
signum에 해당하는 시그널의 블록 상태를 해제한다.
- 시그널 집합 함수
위에서는 시그널을 개별적으로 처리했다.
이번엔 sigset_t 자료형으로 시그널을 집합형태로 처리해본다.
1. int sigemptyset(sigset_t *set);
시그널 집합의 모든 비트를 0으로 설정함.
2. int sigfillset(sigset_t *set);
시그널 집합의 모든 비트를 1로 설정함.
3. int sigdelset(sigset_t *set, int signum);
시그널 집합에서 signum에 해당하는 시그널의 비트를 0으로 설정함.
4. int sigaddset(sigset_t *set, int signum);
시그널 집합에서 signum에 해당하는 시그널의 비트를 1로 설정함.
5. int sigismember(sigset_t *set, int signum);
시그널 집합에서 signum에 해당하는 시그널의 설정 유무를 확인한다.
6. int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how 플래그를 이용해서 set 시그널 집합의 시그널을 블록상태로 설정하거나 해제하고, 변경 전의 시그널 집합을 oldset에 저장한다.
sigset_t set; sigemptyset(&set); sigaddset(&set, SIGUSR1); sigprocmask(SIG_BLOCK, &set, NULL); signal(SIGUSR1, sigHandler);
위 코드는 시그널 집합 set을 0으로 초기화하고 SIGUSR1에 해당하는 시그널만 블록 상태로 변경한 상태이다.
이 때 SIGUSR1 시그널이 도착해도 sigHandler 함수가 호출되지 않는다. (SIGUSR1은 pending상태로 남는다)
7. int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
sigset_t set; struct sigaction action; sigemptyset(&set); sigaddset(&set, SIGUSR2); action.sa_flags = SA_NODEFER; action.sa_handler = sigHandler; action.sa_mask = set; sigaction(SIGUSR1, &action, NULL); signal(SIGUSR2, sigHandler); ... void sigHandler(int signum) { if (signum == SIGUSR1) {...} else if (signum == SIGUSR2) {...} }
이 경우에는 SIGUSR1 시그널이 처리되는 동안 SIGUSR2 시그널이 블록된다.
SIGUSR1 시그널의 처리가 끝나면 SIGUSR2 시그널이 처리된다.