signal, sigaction

sesame·2022년 1월 18일
0

교육

목록 보기
17/46

코어 덤프

프로그램의 세그먼트 폴트 등의 비정상적인 종료가 발생하는 경우 커널에서 해당 프로세스와 관련된 메모리를 덤프시킨 파일
이 파일을 가지고 디버깅하여 문제의 원인을 찾을 수 있다.

코어 덤프의 코어는 메모리를 가리킨다.
오래전의 컴퓨터는 자기 코어라는 것으로 만든 코어 메모리를 사용했기 때문에 메모리를 코어라고 불렀다. 현재는 코어 메모리를 사용하지 않게 되었지만 이름은 그대로 남아있는 상황이다.

signal(2)

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함수를 호출하도록 변경

두번째 인자에 사용할 수 있는 특별한 값

  • SIG_DFL: 커널의 디폴트 액션을 사용
  • SIG_IGN: 커널 레벨에서 시그널 무시하도록 함

함수 포인터

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);
}

signal(2)의 문제점

시그널은 프로세스의 상태를 고려하지 않고 언제라도 날아온다. 실행 중에 함부로 끼어들기 때문에 본질적으로 번거로운 문제점이 발생한다.

  • 핸들러 초기화
    운영체제에 따라서는 프로그래머가 지정한 시그널 핸들러를 수행한 후 원래 설정으로 되돌리는 경우가 있다. 그러면 한번 시그널이 포착된 후 다시 핸들러를 등록하기 전까지 날아온 시그널은 포착하지 못하게 된다.

  • 시스템 콜 수행 중에 시그널 발생
    시그널은 read()나 write()와 같은 시스템 콜을 실행하고 있는 도중에도 날아올 수 있다. 그러한 경우의 동작도 운영체제에 따라 다르다. 어떤 운영체제는 시스템 콜 수행 중에 시그널이 날아오면 에러를 반환하고 종료하고, 어떤 운영체제는 시그널 처리와 시스템 콜 처리를 중재해서 프로그래머에게는 문제가 보이지 않게한다. 전자의 경우에는 read()나 write()등의 시스템 콜을 사용할 때 시그널을 염두에 두고 코딩해야하므로 번거롭다.

  • 중복 호출해서는 안되는 함수를 중복 호출
    어떤 함수를 실행하던 중 시그널 핸들러에 의해 해당 함수가 다시 호출될 수도 있다. 이때, 함수 내에서 전역변수를 사용하고 있다면 의도치 않은 문제가 발생하게 된다. 안타깝게도 c 표준 라이브러리 내에도 이러한 함수가 많이 존재한다.

  • 시그널 블록
    여러 번 이야기하지만 시그널은 시도 때도 없이 언제라도 날아온다. 즉, 시그널 핸들러를 실행하고 있는 동안에도 날아온다. 그러한 경우 시그널 핸들러가 복수로 동시에 실행되는 것은 일반적으로 있을 수 없는 동작이므로, 현재의 시스템에서는 시그널 핸들러가 실행 중에는 같은 종류의 시그널 전달을 보류할 수 있도록 하고 있다. 이것을 '시그널을 블록한다'고 말한다. signal(2)에서는 블록에 관한 동작을 설정할 수 없다.

sigaction(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이 받는 인수에는 시그널 번호, 시그널이 만들어진 이유, 시그널을 받는 프로세스의 정보입니다.

signal의 문제 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으로 대체

stty -a

설정 명령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^ostty 입력 및 출력을 즉시 버림
werase (np)^w입력한 한 단어를 삭제
inext (np)^v특수문자라 할지라도 다음 문자는 글자 그대로 입력

부동 소수점
https://physicallaw.tistory.com/64

0개의 댓글