리눅스 환경에서 강건성(robustness)테스트나 디버깅을 진행하다보면 여러가지 오류로인해 프로그램이 종료된다. 이 때 코어덤프가 있으면 디버깅에 유용하지만 로그만 남아 있는 경우도 있다. 이럴때 단서가 되는 부분이 시그널인 것 같다. 그래서 시그널에 대해서 다시한번 상기시켜보려고 한다.
(사실 주로 발생하는 시그널은 SIGABRT,SIGKILL,SIGPIPE,SIGSEGV 정도이다.)
시그널의 정의를 보면 시그널은 소프트웨어 인터럽트로, 프로세스에 어떤 이벤트가 발생했음을 알리는 간단한 비동기 메세지이다.
인터럽트는 크게 하드웨어 인터럽트와 소프트웨어 인터럽트로 구분된다. 하드웨어 인터럽트는 외부에서 전기적 신호가 발생(이벤트)했을때를 말하고 소프트웨어 인터럽트는 CPU가 연산중에 어떠한 조건에 맞는 이벤트가 발생했을때를 말한다.
비동기적인 메세지라는 말은 프로세스 입장에서 일을 하고 있는 도중에 시그널이 오면 잠시 일을 멈추고 시그널에 대한 처리를 한 뒤 다시 본래의 일로 돌아옴을 의미한다. 일반적으로 시그널 핸들러를 정의해두지 않는다면 시그널의 기본동작에 의해서 프로세스는 시그널을 처리하게 된다.(시그널에 의해 종료되거나 ,시그널을 무시하거나 등)
참고로 SIGKILL이랑 SIGSTOP은 핸들러를 등록할 수 없다. (SIGKILL과 SIGSTOP을 핸들러로 등록해서 처리해 버리면 시스템 관리에 문제가 생길 수 있다.)
시그널은
커널 -> 프로세스
프로세스 -> 프로세스
스레드 -> 스레드로 전달이 가능하다.
시그널을 전달 받게 되면 진행중인 테스크를 잠시 중단하고 시그널 핸들링(무시,프로그램종료 또는 사용자가 지정한 핸들러 함수) 한 뒤 다시 원래의 테스크로 돌아온다.
사실 내부적으로 조금더 복잡하게 동작한다. signal을 처리하는 것은 커널이지만 handler를 등록했다면 다시 user영역으로 빠지게 된다. user영역에서 handler함수를 호출 한 뒤 다시 커널영역으로 돌아가서 본래 테스크의 context를 이용해서 signal이 불린 시점으로 다시 돌아가게 된다.
사진출처http://slideplayer.com/slide/10812592/
아무튼 간단하게 생각해서 시그널이 발생하면 원래 하던 일을 잠시 멈추고 시그널 처리를 한 뒤 다시 돌아와서 원래 일을 하는 것이다.
그러나 시그널은 신뢰성을 보장하지 못한다. 예를 들어 SIGINT를 처리하는 도중에 SIGINT가 또 들어오게 되면 큐잉의 개념이 없기때문에 처리중에 들어오는 동일한 시그널을 무시되어 버린다. 그리고 SIGINT를 처리중 SIGQUIT등의 다른 시그널이 들어오면 현재 처리중이던 SIGINT는 무시되어지고 SIGQUIT을 처리한다.(파일 입출력이나 데이터 변경시 치명적)
쉘에서 kill -l을 입력해보면 현재 시스템에서 사용하는 시그널의 종류와 번호를 볼 수 있다.(시스템마다 시그널의 번호는 다를 수 있다.)
주로 일을 하면서 본 시그널은
시그널 |
기본처리 |
발생 요건 |
SIGABRT |
코어 덤프 |
abort 함수에 의해 발생 |
SIGKILL | 종료 | 강제종료(안드로이드의 LMK(low memory killer 처럼 메모리 모니터링 데몬에서 메모리 부족시 죽이는 경우) |
SIGPIPE |
종료 |
잘못된 파이프 처리로 발생(닫힌 pipe에 write하는 경우등) |
SIGSEGV |
코어 덤프 |
세그멘테이션 폴트 발생 |
시그널이 발생했을때 추가적으로 핸들러를 등록하지 않으면 기본 동작을 수행한다. 시그널 핸들러는 signal함수나 sigaction함수를 통해서 등록할 수 있다.
signal함수 보다 향상된 기능을 sigaction함수에서 제공하고 signal함수의 경우 한번 시그널을 처리하면 재등록해야 하는 단점이 있기때문에 sigaction을 사용하면 된다.
int sigaction (int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
- sa_flags 는 시그널 전달 방법을 지정할 수 있다. SA_ONSTACK,SA_RESETHAND 등 시그널이 발생했을 때 처리하는 방식을 지정해 준다.
- sa_handler 와 sa_sigaction sa_flags값이 SA_SIGINFO일때 sa_sigaction을 사용하고 나머지는 sa_handler를 사용하면 된다.
- sa_mask는 시그널 핸들러가 작동중일 때 block할 시그널을 설정하는 변수이다.
예제코드
#include<unistd.h>#include<signal.h>#include<stdlib.h>#include<stdio.h>void handler(int signo){psignal(signo, "Signal Receved!:");sleep(5);printf("After Sleep\n");}int main(void){struct sigaction act;sigemptyset(&act.sa_mask);sigaddset(&act.sa_mask, SIGQUIT); //block SIGQUITact.sa_flags = 0;act.sa_handler = handler;if(sigaction(SIGINT, &act, (struct sigaction *)NULL) < 0){perror("sigaction");exit(1);}fprintf(stderr, "Input SIGINT: ");pause();fprintf(stderr, "After Signal Handler\n");return 0;}
'리눅스' 카테고리의 다른 글
[Linux] Daemon Process란? (0) | 2018.03.25 |
---|---|
[glib] 메인루프 (0) | 2018.03.04 |
pthread에서 메모리 침범과 pthread_cancel (1) | 2018.01.21 |
정적 라이브러리(static library)와 공유 라이브러리(shared library) (0) | 2018.01.14 |
Dead lock과 pthread에서 mutex lock하는 방법 (2) | 2018.01.07 |