프로그램의 세그먼트 폴트 등의 비정상적인 종료가 발생하는 경우 커널에서 해당 프로세스와 관련된 메모리를 덤프시킨 파일
이 파일을 가지고 디버깅하여 문제의 원인을 찾을 수 있다.
코어 덤프의 코어는 메모리를 가리킨다.
오래전의 컴퓨터는 자기 코어라는 것으로 만든 코어 메모리를 사용했기 때문에 메모리를 코어라고 불렀다. 현재는 코어 메모리를 사용하지 않게 되었지만 이름은 그대로 남아있는 상황이다.
void (*signal(int sig, void (*func)(int)))(int);
//typedef를 통해 sighandler_t는 int 타입을 인자로 취하고
//반환값이 void인 함수 포인터 타입이라고 정의
typedef void (*sighandler_t)(int);
sighandler_t signal(int sig, sighandler_t func);
signal(2)은 두번째 인자로 함수 포인터를 받고 함수 포인터를 반환하는 함수
sig의 시그널을 받았을 때 func함수를 호출하도록 변경
두번째 인자에 사용할 수 있는 특별한 값
int *n
포인터 변수 n은 int 타입을 담고있는 메모리 주소를 가리킴
char *str
포인터 변수 str은 char 타입을 인자로 받고, 반환값이 없는 함수를 가리킴
void (*f)(int)
포인터 변수 f는 int 타입을 인자로 받고, 반환값이 없는 함수를 가리킴
int plus1(int n){
return n+1;
}
int main(int argc, char *argv[]){
int (*f)(int); //함수를 가리키는 포인터 변수 f를 정의
int result;
f = plus1; //plus1 함수에 대한 포인터 변수를 f에 대입
result = f(5); //f에 대입한 함수 plus1을 실행
printf("%d\n", result); //6이 출력됨
exit(0);
}
시그널은 프로세스의 상태를 고려하지 않고 언제라도 날아온다. 실행 중에 함부로 끼어들기 때문에 본질적으로 번거로운 문제점이 발생한다.
핸들러 초기화
운영체제에 따라서는 프로그래머가 지정한 시그널 핸들러를 수행한 후 원래 설정으로 되돌리는 경우가 있다. 그러면 한번 시그널이 포착된 후 다시 핸들러를 등록하기 전까지 날아온 시그널은 포착하지 못하게 된다.
시스템 콜 수행 중에 시그널 발생
시그널은 read()나 write()와 같은 시스템 콜을 실행하고 있는 도중에도 날아올 수 있다. 그러한 경우의 동작도 운영체제에 따라 다르다. 어떤 운영체제는 시스템 콜 수행 중에 시그널이 날아오면 에러를 반환하고 종료하고, 어떤 운영체제는 시그널 처리와 시스템 콜 처리를 중재해서 프로그래머에게는 문제가 보이지 않게한다. 전자의 경우에는 read()나 write()등의 시스템 콜을 사용할 때 시그널을 염두에 두고 코딩해야하므로 번거롭다.
중복 호출해서는 안되는 함수를 중복 호출
어떤 함수를 실행하던 중 시그널 핸들러에 의해 해당 함수가 다시 호출될 수도 있다. 이때, 함수 내에서 전역변수를 사용하고 있다면 의도치 않은 문제가 발생하게 된다. 안타깝게도 c 표준 라이브러리 내에도 이러한 함수가 많이 존재한다.
시그널 블록
여러 번 이야기하지만 시그널은 시도 때도 없이 언제라도 날아온다. 즉, 시그널 핸들러를 실행하고 있는 동안에도 날아온다. 그러한 경우 시그널 핸들러가 복수로 동시에 실행되는 것은 일반적으로 있을 수 없는 동작이므로, 현재의 시스템에서는 시그널 핸들러가 실행 중에는 같은 종류의 시그널 전달을 보류할 수 있도록 하고 있다. 이것을 '시그널을 블록한다'고 말한다. signal(2)에서는 블록에 관한 동작을 설정할 수 없다.
signal(2)는 단순하기는 하나 여러가지 문제점이 있고, 이식성도 부족하기 때문에 새로운 시스템 콜이 만들어졌다.
새롭게 프로그램을 작성할 때는 무조건 sigaction(2) 사용하는 것이 좋다.
#include <signal.h>
int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact);
sig: sig에 해당하는 시그널에 대한 시그널 핸들러를 등록
act: 시그널 핸들러를 지정, 상수 SIG_IGN, SIG_DFL 또는 임의의 함수 포인터 중 하나 지정
oldact: sigaction()를 호출할 때 설정되어 있던 시그널 핸들러가 기재, 불필요하면 NULL 지정
RETURN: 성공하면 0을 실패하면 -1
struct sigaction{
/* sa_handler나 sa_sigaction 중 하나만 사용한다 */
void (*sa_handler)(int); //시그널을 처리하기 위한 핸들러
//SIG_DFL, SIG_IGN 또는 핸들러 함수
void (*sa_sigaction)(int, siginfo_t*, void*); //밑의 sa_flags가 SA_SIGINFO 일때 sa_handler 대신에 동작하는 핸들러
sigset_t sa_mask; //시그널을 처리하는 동안 블록화할 시그널 집합의 마스크
int sa_flags;
}
sa_flags
- SA_NOCLDSTOP
signum이 SIGCHILD일 경우, 자식 프로세스가 멈추었을 때, 부모 프로세스에 SIGCHILD가 전달되지 않는다.
SA_ONESHOT/SA_RESETHAND
시그널을 받으면 설정된 행동을 취하고 시스템 기본 설정인 SIG_DFL로 재설정된다.
SA_RESTART
시그널 처리에 의해 방해 받은 시스템 호출은 시그널 처리가 끝나면 재시작한다.
SA_NOMASK/SA_NODEFER
시그널을 처리하는 동안에 전달되는 시그널은 블록되지 않는다.
act.sa_flags = 0;
act.sa_flags = SA_NOMASK; //SA_NODEFER
SA_SIGINFO
이 옵션이 사용되면 sa_handler대신에 sa_sigaction이 동작되며, sa_handler 보다 더 다양한 인수를 받을 수 있습니다. sa_sigaction이 받는 인수에는 시그널 번호, 시그널이 만들어진 이유, 시그널을 받는 프로세스의 정보입니다.
핸들러 재설정
sigaction()은 운영체제와 관계없이 한번 설정한 시그널 핸들러가 계속 유지됨
시스템 콜의 재기동
sigaction()은 sa_flags 멤버에 플래그 SA_RESTART를 추가하면 시스템 콜을 재기동하고, 그렇지 않은 경우에는 재기동하지 않는다. 앞서 설명한 대로 일반적으로는 재기돌하는 것이 편리하기 땜누에 SA_RESTART는 항상 추가해 두는 것이 좋다.
시그널 블록
struct sigaction의 멤버 sa_mask에 블록할 시그널을 지정할 수 있다. 그러나 시그널 핸들러를 수행할 때는 처리중인 시그널이 자동으로 블록되므로 대부분의 경우 sa_mask를 비워두면 된다. sa_mask을 비워두기 위해서는 후술하는 sigemptyset()을 사용한다.
//sig
typedef void (*sighandler_t)(int);
sighandler_t trap_signal(int sig, sighandler_t handler){
struct sigaction act, old;
//시그널 핸들러 지정
act.sa_handler = handler;
//시그널 처리 중 블록될 시그널은 없음
sigemptyset(&act.sa_mask); //sigfillset은 모든 시그널을 블록함
//시스템 콜은 자동으로 재기동되는 것이 편리하므로
//SA_RESTART 설정
act.sa_flags = SA_RESTART;
//sig를 지정하면서 act에 이전 정보를 구한다.
if(sigaction(sig, &act, &old) < 0)
return NULL;
return old.sa_handler;
}
sigaction의 siginfo_t
siginfo_t { int si_signo; /* 시그널 넘버 */ int si_errno; /* errno 값 */ int si_code; /* 시그널 코드 */ pid_t si_pid; /* 프로세스 ID 보내기 */ uid_t si_uid; /* 프로세스를 전송하는 실제 사용자 ID */ int si_status; /* Exit 값 또는 시그널 */ clock_t si_utime; /* 소모된 사용자 시간 */ clock_t si_stime; /* 소모된 시스템 시간 */ sigval_t si_value; /* 시그널 값 */ int si_int; /* POSIX.1b 시그널 */ void * si_ptr; /* POSIX.1b 시그널 */ void * si_addr; /* 실패를 초래한 메모리 위치 */ int si_band; /* 밴드 이벤트 */ int si_fd; /* 파일 기술자 */ }
sigset_t (시그널)집합
//sigemptyset(3) : 시그널 집합 비우기 int sigemptyset(sigset_t *set); //set - 비우려는 시그널 집합의 주소 ![](https://velog.velcdn.com/images%2Fkyy806%2Fpost%2F578c9c5a-0d25-4222-9699-700489eaca69%2Fimage.png) // sigfillset(3) : 시그널 집합에 모든 시그널 설정 int sigfilleset(sigset_t *set); //set - 설정하려는 시그널 집합의 주소 ![](https://velog.velcdn.com/images%2Fkyy806%2Fpost%2Faa5dd120-5ecc-4802-97a1-7f9e0ecf96f7%2Fimage.png) // //sigaddset(3) : 시그널 집합에 시그널 설정 추가 int sigaddset(sigset_t *set, int signo); //set - 시그널을 추가하려는 시그널 집합의 주소 //signo - 시그널 집합에 추가로 설정하려는 시그널 //성공시 0, 실패시 -1 // //sigdelset(3) : 시그널 집합에서 시그널 설정 삭제 int sigdelset(sigset_t *set, int signo); //set - 시그널을 삭제하려는 시그널 집합의 주소 //signo - 시그널 집합에서 삭제하려는 시그널 //성공시 0, 실패시 -1 // //sigismember(3) : 시그널 집합에 설정된 시그널 확인 int sigismember(sigset_t *set, int signo); //set - 확인하려는 시그널 집합의 주소 //signo - 시그널 집합에 설정되었는지를 확인하려는 시그널
시그널 블록
sigaction의 sa_mask 멤버를 사용하여 설정할 수 있었다.
한편 블록한 시그널을 다시 받기 위한 API는 다음과 같다.//현재 프로세스의 시그널 마스크를 설정 int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); // //보류된 시그널을 set에 써넣는다. int sigpending(sigset_t *set); //성공 0 실패 -1 // //시그널 마스크 mask를 설정함과 동시에 프로세스를 시그널 대기 상태로 만듬 //차단한 시그널을 해제하고, 보류되어있던 시그널을 처리할 때 사용 int sigsuspend(const sigset_t *mask); //항상 -1 반환, 언제든지 시그널에 끼어들어 종료(EINTR)하기 때문
how: how 플래그를 이용하여 설정
- SIG_BLOCK: set에 포함되는 시그널을 시그널 마스크에 추가
- SIG_UNBLOCK: set에 포함되는 시그널을 시그널 마스크에서 삭제
- SIG_SETMASK: 시그널 마스크를 set으로 대체
설정 명령 | default 설정키 | 의미 |
---|---|---|
erase | ^? | 입력된 한 문자 삭제 ( ^?는 DELETE key / ^H를 주로 설정 backspace key ) |
kill | ^u | 입력된 행을 모두 삭제 |
intr | ^c | 진행중인 프로세스를 종료 |
quit | ^\ | core dump와 함께 현재의 프로세스 중단 |
eof | ^d | 파일의 끝을 알림 (입력 종료) |
eol | ^d | 행의 끝 |
eol2 | ^d | 한 행을 마치기위한 별도의 문자 |
swtch (np) | ^d | 다른 쉘 계층으로 스위칭 |
start | ^q | 화면으로의 출력을 시작 |
stop | ^s | 화면으로의 출력을 중지 |
susp | ^z | 현재의 프로세스를 suspend (일시 중지) |
dsusp (np) | ^y | 입력을 flush 한 후 프로세스를 suspend |
rprnt (np) | ^r | 화면을 다시 갱신 |
flush | ^o | stty 입력 및 출력을 즉시 버림 |
werase (np) | ^w | 입력한 한 단어를 삭제 |
inext (np) | ^v | 특수문자라 할지라도 다음 문자는 글자 그대로 입력 |