반응형

시그널

시그널이란 소프트웨어 인터럽트이다.

프로세스에 뭔가 발생했음을 알리는 간단한 메시지를 비동기적으로 보내는 것을 의미한다.

 

시그널이 발생하는 경우는 다음과 같다.

0으로 나누기처럼 프로그램에서 예외적인 상황이 일어나는 경우나

Kill 함수처럼 시그널을 보낼 수 있는 함수를 사용해서 다른 프로세스에 시그널을 보내는 경우,

사용자가 CTRL+C와 같이 인터럽트 키를 입력한 경우가 존재한다.

 

 

시그널 처리방법

시그널이 프로세스에 도달하면 프로세슨느 각 시그널에 지정된 기본 동작을 수행한다.

대부분의 기본 동작은 프로세스를 종료하는 것이다.

하지만 프로그래밍을 통해 시그널을 무시하거나,

시그널 처리를 위한 함수(핸들러)를 지정할 수 있고

블럭처리를 할 수 있다.

 

 

시그널의 종류

시그널의 종류

 

시그널 보내기

kill -9 3255

kill -[시그널번호] PID

kill은 프로세스에 시그널을 보내는 명령어이다.

3255번 프로세스에 9번 시그널(SIGKILL)을 보내면 프로세스가 강제 종료 된다.

 

 

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);
import os
import signal
os.kill(pid, sig) # signal.SIGALRM와 같이 시그널 변수를 사용한다.


os.killpg(pgid, sig) # 프로세스 그룹에 시그널을 보낼 때 사용한다.

 

pid > 0 인 경우 pid로 지정한 프로세스에 시그널을 발송한다.

pid < -1 일 때는 프로세스 그룹 ID가 pid의 절대값인 프로세스 그룹에 속하고

시그널을 보낼 권한을 가지고 있는 모든 프로세스에 시그널을 발송한다.

 

pid == 0 인 경우

특별한 프로세스를 제외하고 프로세스 그룹ID가 시그널을 보내는 프로세스의 프로세스 그룹 ID과 같은

모든 프로세스에게 시그널을 발송한다.

 

pid == -1 인 경우

시그널을 보내는 프로세스의 유효 사용자 ID가 root가 아니라면,

특별한 프로세스를 제외하고 프로세스의 실제 사용자ID가

시그널을 보내는 프로세스의 유효 사용자ID와 같은 모든 프로세스에 시그널을 발송한다.

 

#include <unistd.h>
#include <signal.h>
#include <stdio.h>

int main() {
    printf("Before SIGCONT Signal to parent.\n");

    kill(getppid(), SIGCONT);

    printf("Before SIGQUIT Signal to me.\n");

    kill(getpid(), SIGQUIT);

    printf("After SIGQUIT Signal.\n");
    
    return 0;
}
#결과

root@ac265c3fef72:/source/signal# ./ex8_1
Before SIGCONT Signal to parent.
Before SIGQUIT Signal to me.
Quit

SIGQUIT 시그널을 보내면 코어 덤프로 종료되는 것을 알 수 있다.

 

 

 

raise 함수를 이용하면 함수를 호출한 프로세스에 시그널을 발송한다.

// c
int raise(int sig);


//python
signal.raise_signal(signal)

 

abort함수를 이용하면 함수를 호출한 프로세스에 SIGABRT 시그널을 발송한다.

SIGABRT 시그널은 프로세스를 비정상적으로 종료 시키고 코어 덤프를 생성한다.

// c
void abort(void);

// python
os.abort()

 

 

시그널 핸들러

시그널 핸들러는 시그널을 받았을 때 이를 처리하기 위해 지정된 함수이다.

프로세스를 종료하기 전에 처리할 것이 있는 경우

특정 시그널에 대해 종료하고 싶지 않을 경우 지정하거나 (empty function)

특정 시그널에 다른 일을 하고 싶은 경우 지정한다. (do something function)

 

SIGKILL, SIGSTOP을 제외한 모든 시그널을 핸들러에 지정할 수 있다.

 

// c
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

// python
signal.signal(signalnum, handler)

 

handler는 sig로 지정한 시그널을 받았을 때 처리할 방법을 나타낸다.

사용자자 정의 시그널 핸들러 함수명을 이용한다.

SIG_IGN : 시그널을 무시하도록 지정한다.

SIG_DFL : 기본 처리 방법으로 처리하도록 지정한다.

 

실패시 SIG_ERR을 반환하고, 성공시 이전 핸들러를 반환한다.

 

signal 함수는 시스템 마다 다르게 동작하는데

system V 계열의 경우 시그널 핸들러를 한번 사용 하면 시그널 처리 방법을 기본 처리 방법(SIG_DFL)로 재설정한다.

시그널이 들어올 때마다 시그널 핸들러를 호출하려면 매번 시그널 핸들러를 재지정해야한다.

BSD 계열의 경우는 기본 처리 방법(SIG_DFL)로 재설정 하지 않고 계속 시그널 핸들러를 계속 사용하고,

리눅스 계열의 경우 커널의 signal(2) 시스템 콜은 시스템 V와 같은 방식으로 동작하고,

gcc의 glibc 2부터 signal(3) 함수는 signal(2)를 호출하지 않고 sigaction(2)를 호출해 BSD 형식으로 동작한다.

 

 

시그널 집합

시그널을 개별적으로 처리하지 않고 복수의 시그널을 처리하기 위해 도입한 개념이다.

POSIX에서 도입했다.

 

시그널 집합의 처리를 위한 구조체 sigset_t

typedef struct{
	unsigned int __sigbits[4];
} sigset_t;

시그널을 비트 마스크(128bit)로 표현한다. 각 비트가 특정 시그널과 1:1로 연결된다.

비트 값이 1이면 해당 시그널이 설정된 것이고, 0이면 시그널 설정이 안된 것이다.

 

 

시그널 처리 함수

//sigemptyset(3)을 활용하여 시그널 집합에서 모든 시그널을 0으로 설정할 수 있다.
int sigemptyset(sigset_t *set);

//시그널 집합에서 모든 시그널을 1로 설정
int sigfillset(sigset_t *set);

//시그널 집합에 시그널 설정 추가
int sigaddset(sigset_t *set, int signo);

//시그널 집합에 시그널 설정 삭제
int sigdelset(sigset_t *set, int signo);

//시그널 집합에서 시그널이 포함되어 있는지 확인
int sigismember(sigset_t *set, int signo);

 

#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>

int main() {
    sigset_t st;

    sigemptyset(&st);

    sigaddset(&st, SIGINT);
    sigaddset(&st, SIGQUIT);

    if(sigismember(&st, SIGINT))
        printf("SIGINT is setting.\n");

	//__val은 시스템 환경마다 다른 변수 이름을 사용해야한다 ubuntu에서는 __val 사용
    printf("** Bit Pattern: %x\n", st.__val[0]);

    return 0;
}

실행 결과

SIGINT is setting.
** Bit Pattern: 6

 

sigchild로 자식 프로세스 정리

#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>

static void sig_child(int);

int main() {
    pid_t pid;
    int i;
    signal(SIGCHLD, sig_child);

    pid = fork();
    
    //자식인 경우 pid가 0이므로 sleep후 종료한다
    if(pid == 0){
        sleep(1);
        exit(0);
    }
    while(1){
    //부모인 경우 계속 while문을 돈다.
        i = i;
        sleep(1);
    }
    return 0;
}


static void sig_child(int signo){
    pid_t pid; int status;
    
    //wait함수로 자식프로세스 종료를 기다린다.
    pid = wait(&status);
    
    printf("child %d finished\n %d", pid, status);
}

 

SIGFPE로 divide by zero 에러 처리

#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>

static void sig_fpe(int);

int main() {
    pid_t pid;
    int i;
    signal(SIGFPE, sig_fpe);

    i = i/0;
}


static void sig_fpe(int signo){
    pid_t pid; int status;
    printf("Divide by 0 Error\n");
    
    exit(1);
}

 

 

기타 시그널 처리 함수

// 시그널 정보 출력
// s에 지정한 문자열을 붙여 정보 출력
void psignal(int sig, const char *s);

//인자로 받은 시그널을 가리키는 이름을 문자열로 리턴
char *strsignal(int sig);

//시그널 블록킹과 해제
//인자로 받은 시그널을 시그널 마스크에 추가하거나 해제한다
int sighold(int sig);
int sigrelse(int sig);

 

 

#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>


void handler(int signo){
    char *s;
    s = strsignal(signo);
    printf(stdout, "Received Signal : %s\n", s);
}

int main() {
    if (sigset(SIGINT, handler) == SIG_ERR){
        perror("sigset");
        exit(1);
    }

    sighold(SIGINT);
    pause();

    return 0;
}

이 경우 CTRL+C로 SIGINT를 보내도 프로그램이 종료되지 않는다.

 

 

//시그널 집합 블록과 해제
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);

how : 시그널을 블록할 것인지, 해제할 것인지 여부 결정 

SIG_BLOCK : set에 지정한 시그널 집합을 시그널 마스크에 추가

SIG_UNBLOCK : set에 지정한 시그널 집합을 시그널 마스크에서 제거

SIG_SETMASK : set에 지정한 시그널 집합으로 현재 시그널 마스크를 대체

 

set은 블록하거나 해제할 시그널 집합 주소를 의미하고, oset은 NULL 또는 이전 설정값을 저장한 시그널 집합 주소이다.

 

#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>


int main() {
    sigset_t new;

    sigemptyset(&new);
    //시그널 집합에 SIGINT, SIGQUIT 설정
    sigaddset(&new, SIGINT);
    sigaddset(&new, SIGQUIT);
    sigprocmask(SIG_BLOCK, &new, (sigset_t *) NULL);

    printf("Blocking Signals : SIGINT, SIGQUIT\n");
    printf("Send SIGQUIT\n");
    //SIGQUIT 시그널 보내기
    kill(getpid(), SIGQUIT);


    sleep(5);
    printf("UnBlocking Signals\n");

    //시그널 집합 블록 해제
    sigprocmask(SIG_UNBLOCK, &new, (sigset_t *) NULL);

    return 0;
}

위 코드처럼 시그널을 블록해두면 해당 시그널을 처리하지 않고 대기상태로 두었다가,

시그널 집합 블록을 해제하면 그때 대기해두었던 시그널들을 처리한다.

 

 

시그널 대기 함수

// 시그널 대기
//sig : 시그널이 올 때까지 대기할 시그널(1개)
int sigpause(int sig);

//set: 기다리려는 시그널을 지정한 시그널 집합
int sigsuspend(const sigset_t *set);

 

 

sigset(집합)과 sigsuspend 사용으로 시그널을 기다릴 수 있다.

#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>

void handler(int signo){
    psignal(signo, "Received Signal:");
}


int main() {
    sigset_t set;
    sigset(SIGQUIT, handler);

    sigfillset(&set);
    sigdelset(&set, SIGQUIT);

    printf("Wait...\n");

    sigsuspend(&set);


    return 0;
}
Wait...
^Z^X^C
^C
^C
^\Received Signal:: Quit

 

 

//시그널 기다리기
int sigwait(const sigset_t *set, int *sig)

set에 포함된 시그널만 기다리고, 다른 시그널은 블럭되지 않는다.

sig에는 시그널을 수신하였을 때, 수신한 시그널 번호를 저장한다.

 

#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>

void handler(int signo){
    psignal(signo, "Received Signal:");
}

int main() {
    sigset_t set;
    int sig;
    sigset(SIGQUIT, handler);

    sigfillset(&set);
    sigdelset(&set, SIGQUIT);

    printf("Wait...\n");

    //시그널이 올때까지 가디렸다가 다 처리한다.
    sigwait(&set, &sig);

    psignal(sig, "Continued by signal :");
    fflush(stdout);

    return 0;
}

 

시그널 보내기 sigsend

int sigsend(idtype_t idtype, id_t id, int sig);

idtype은 id에 지정한 값의 종류, id는 시그널을 받을 프로세스나 프로세스 그룹, sig는 보내려는 시그널을 의미한다.

 

sigsend idtype

시그널 무시처리 sigignore

int sigignore(int sig);

인자로 지정한 시그널의 처리방법을 SIG_IGN으로 설정한다.

 

 

 

 

SIGNAL 함수 호환성

svr4 시스템과 BSD 시스템 간의 동작이 다르다.

앞으로 개발할 때는 이식성을 고려하여 signal을 사용하지 말고 sigaction을 사용한다.

 

 

시그널과 프로그램 시작

프로세스가 fork될 때

자식 프로세스는 부모 프로세스의 시그널 처리를 상속한다.

 

프로그램이 exec될 때는

기본 동작이 정의된 모든 시그널은 기본 동작으로 변경하고,

기본 동작이 없는 시그널은 그대로 둔다.

 

대화형 쉘(Interactive Shell)에서 실행하는 경우

백그라운드 프로세스는 인터럽트 및 종료 신호의 처리를 무시하도록 설정하고

많은 대화형 프로그램은

백그라운드에 있지 않을 때만 신호를 포착한다.

int sig_int(), sig_quit()

if(signal(SIGINT, SIG_IGN) != SIG_IGN) signal(SIGINT, sig_int);
if(signal(SIGQUIT, SIG_IGN) != SIG_IGN) signal(SIGQUIT, sig_quit);

 

+ Recent posts