about Signal

programming/C_C++ 2016. 4. 12. 12:23
반응형

http://badcob.tistory.com/149

 

Signal은 소프트웨어적인 interrupt로서 비동기적인 사건을 처리하는 수단이다.

    프로세스의 입장에서 보면 Signal은 아무때나 발생한다.
    프로세스에서 그냥 변수 하나를 판정하는 것만으로는 신호가 발생했는지의
    여부를 파악할 수 없다. 대신, 프로세스는 커널에게 이런 신호가 발생하면 이런 일을
    수행하라고 알려주어야 한다.

    신호가 발생했을 때 수행되는 일을 신호의 처분(disposition)
또는 신호에 연관된 행동
    이라고 부른다. 프로세스가 신호를 처분하는 방식은 크게
세가지이다,

    1) 신호를 무시한다. 대부분의 신호들을 무시하는 것이 가능하지만, SIGKILL과 SIGSTOP은
    그럴수 없다. 이 두 신호를 무시할 수 없는 이유는 커널과 슈퍼사용자가 임의의 프로세스를
    임의로 죽이거나 멈출 수 있도록 하기 위한 데 있다.

    2) 신호를 잡는다(catch) 이를 위해서는 커널에게 신호 발생 시 호출될 할수를 알려주어야 한다.
    그 함수에서는 신호 발생 조건을 처리하는 데 필요한 어떠한 일도 할 수 있다.

    3) 기본 행동이 적용되게 한다. 모든 신호는 기본행등을 가지고 있다. 대부분의 신호들의
    기본행동은 프로세스를 종료 시키는 것이다.

   signal 함수

    #include <signal.h>
    void (*signal(int signo, void(*func)(int)))(int);
    반환 값 : 성공 시 신호의 이전 행동 오류시 SIG_ERR

    signo 인수에는 위에 있는 신호 이름들중  하나를 넣는다.

    func 인수로는
    a) SIG_IGN 상수나
    b) SIG_DFL 상수
    c) 신호 발생 시 호출될 함수의 주소를 넣을 수 있다.
   
    SIG_IGN을 지정하는 것은 signo 로 지정된 신호를 무시하도록 시스템에게 알려주는 것에 해당한다.
    SIG_DFL을 지정하면 신호 발생 시 신호의 기본행동이 수행된다.
    신호 발생 시 호출될 함수의 주소를 지정하는 것은 프로세스가 해당 신호를 잡겠다 라고
    시스템에게 알려주는 것이다. 그러한 함수를 signal handler 또는 signal-catching function 이라고
    불린다.

    signal 함수의 원형에서 보듯이 이 함수는 2개의 인수를 받는다. 반환값은 아무것도 돌려주지 않는
    함수를 가리키는 포인터이다. signal 함수으 첫인수 signo는 정수이고 둘째 인수는 정수 인수를 하나
    받고 아무것도 돌려주지 않는 함수를 가리키는 포인터이다. signal 이 돌려주는 함수 포인터가 가리키는
    함수는 하나의 정수 인수를 받는다.  좀더 풀어서 말하면 하나의 signal handler 함수는 하나의 정수
    인수를 받고 아무것도 돌려주지 않는다. signal을 호출해서 신호 처리부를 설정할 때 둘째 인수에는
    그러한 신호 처리부를 가라키는 포인터를 넣는다. 이때 signal의 반환값은 이전에 설정되었던 신호
    처리부를 가리키는 포인터이다.

    신호 집합

    여러 개의 신호를 신호 집합(signal set) 으로 표현하느 자료 형식이 존재한다. 그리고 이러한 신호
    집합을 사용 또는 조작하는 여러 함수들이 있다. 예를 들어 sigprocmask 함수는 이 집합에 포함되어
    있는 신호들을 전달하지 말라고 커널에게 알려준다. 이 신호 집합을 조작하는 함수들은 5가지 이다.

    #include <signal.h>
    int sigemptyset(sigset_t *set);
    int sigfillset(sigset_t *set);
    int sigaddset(sigset_t *set, int signo);
    int sigdelset(sigset_t *set, int signo);
    반환 값 : 성공 시 0, 오류시 -1 - 네 함수 전부

    int sigismember(const sigset_t *set, int signo);
    반환 값 : 참이면 1, 거짓이면 0, 오류 시 -1

    sigemptyset 함수는 set이 가리키는 신호 집합을 빈 집합으로 만든다. 즉 신호 집합에서 모든
    신호를 제외시킨다.
    sigfillset 함수는 모든 신호를 신호 집합에 포함시킨다.
    신호 집합을 사용하는 응용프로그램은 반드시 신호 집합을 사용하기 전에 sigemptyset이나
    sigfillset을 호출해서 신호 집합을 초기화 해야 한다. sigaddset 함수는 하나의 신호를 기존 집합에
    추가하고, sigdelset 함수는 하나의 신호를 제거한다.

    sigprocmask 함수

    프로세스의 신호 마스크는 프로세스에 대한 전달이 현재 차단되어 있는 신호들을 나타낸다.
    프로세스는 자신의 신호 마스크를 조회 하거나, 수정하거나, 조회와 수정을 한번에  모두
    수행할 수 있다.
   
    #include <signal.h>
    int sigprocmask(int how, const sigset_t *restrict set, sigset_t *sigset_t *restrict oset);
    반환 값 : 성공 시 0, 오류 시 -1

    신호 마스크를 조회할 때는 oset 인수에 널 포인터가 아닌 포인터를 넣는다. 그러면 프로세스의
    현재 신호 마스크가  oset이 가리키는 곳에 설정된다.
    신호 마스크를 설정할 때에는 set 인수에 널 포인터가 아닌 포인터를 넣는다. 신호 마스크의
    구체적인 수정 방식은how 인수에 의해 결정된다.

    SIG_BLOCK    프로세스의 현재 신호 마스크와 set이 가리키는 신호 비합의 합집합이 프로세스의
                새 프로세스 마스크가 된다. 차단할 신호들을 추가할 때 유용하다.
    SIG_UNBLOCK    프로세스의 현재 신호 마스크와 set이 가리키는 신호 집합의 교집합이 프로세스의
                새 프로세스 마스크가 된다. 신호들의 차단을 해제할떄 유용하다.
    SIG_SETMASK    set이 가리키는 신호 집합의 값이 프로세스의 새 신호 마스크가 된다.

    set이 널 포인터이면 프로세스의 신호 마스크가 변하지 않으며 how는 무시된다.
    sigprocmask를 호출한 후 차단되지 않고 유보 중인 신호들이 존재하면, 신호들 중 적어도 하나가
    sigprocmask의 반환 전에 프로세스에 전달된다.
   
    sigpending 함수

    sigpending 함수는 호출한 프로세스에 대한 전달이 차단되어 있으며 현재 유보 중인 신호들의
    집합을 set 인수가 가리키는 곳에 저장한다.

    #include <signal.h>
    int sigpending(sigset_t *set);
    반환 값 : 성공 시 0, 오류 시 -1

    위의 함수들을 이용한 프로그램.

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <ERR_PRINT.H>
#include <SIGNAL.H>
  
static void sig_quit(int);
  
int main(void)
{
    sigset_t    newmask, oldmask, pendmask;
  
    if (signal(SIGQUIT, sig_quit) == SIG_ERR)
        err_print("can`t catch SIGQUIT");
  
    //SIGQUIT 신호를 차단하고 현재 신호 마스크를 보존해둔다.
  
    sigemptyset(&newmask);  //signal을 비우고
    sigaddset(&newmask, SIGQUIT);  //SIGQUIT를 더하고
    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) // 추가한 SIGQUIT만 차단, oldmask로 보존 해 둔다
        err_print("SIG_BLOCK error");
  
    sleep(5);
    if (sigpending(&pendmask) < 0) // 현재 차단되고 유보중인 신호 집합을 pendmask로 저장
        err_print("sigpending error");
  
    if (sigismember(&pendmask, SIGQUIT)) //pendmask에 SIGQUIT가 있으면 출력
        printf("\nSIGQUIT pending\n");
  
    // 신호 마스크를 복원한다. (SIGQUIT 차단이 해제된다)
  
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)  // oldmask 를 복원한다.
        err_print("SIG_SETMASK error");
  
    printf("SIGQUIT unblocked\n");
  
    sleep(5); // 여기서 SIGQUIT에 의해 프로세스가 종료되고 코어 파일이 생성된다.
  
    exit(0);
}
  
static void sig_quit(int signo)
{
    printf("caught SIGQUIT\n");
    if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)
        //SIG_DFL을 지정하면 신호 발생 시 신호의 기본 행동이 수행된다. 
        err_print("can`t reset SIGQUIT");
}


    sigaction 함수

    sigaction 함수는 특정 신호의 처분 방식을 조사하거나 설정하는 데 쓰인다.

    #include <signal.h>
    int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);
    반환 값 : 성공 시 0, 오류 시 -1

    signo 인수는 처분 방식을 조사하거나 수정할 신호의 번호이다.
    신호 처분 방식을 설정할 때에는 act 인수에 널이 아닌 포인터를 넣어야 한다.
    그 포인터는 새 설정을 담은 구조체를 가리킨다. oact 인수에 널이 아닌 포인터를 지정하면,
    함수는 oact가 가리키는 곳에 이전의 신호 처분 방식을 저장한다.


    struct sigaction {
        void (*sa_handler)(int) /*신호 처리부의 주소*/
        sigset_t sa_mask    /*추가로 차단할 신호들 */
        int        sa_flags    /*신호 옵션들*/
        void(*sa_sigaction)(int, siginfo_t *, void *);
    };

    신호의 처분 방식을 변경하는 경우, act가 가리키는 구조체의 sa_handler 필드에는 신호 처리부의
    주소를 설정해야 한다. 그리고 sa_mask 필드에는 신호 처리부가 호출되기 전에 프로세스의 신호
    마스크에 추가될 신호들에 해당하는 값을 설정한다.

    신호 처리부가 반환 되면 그 시점에서
프로세스의 신호 마스크가 원래의 값으로 재설정된다.
    이를 통해서, 신호 처리부가 호출될 떄마다
특정한 신호들을 차단할 수 있다.
    신호 처리부가 호출될 때 운영체제는 전달되는 신호를 신호 마스크에
포함시킨다.
    따라서 한 신호를 처리하는 도중 같은 신호가 더 발생해도 첫 신호의 처리가 끝날 때까지는

    추가적인 신호들이 차단된다. 같은 신호가 여러번 발생해도 추가 발생들이 대기열에 쌓이는게 아니다.
    신호가 차단된 상태에서 신호가 다섯 번 더 발생해도, 신호의 차단을 해재했을 때 그 신호에 대한
    신호 처리부는 한번만 호출되는 것이 보통이다.

    sigaction 함수를 이용한 signal 함수를 구현.

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <SIGNAL.H>
  
Sigfunc *signal(int signo, Sigfunc *func)
{
    struct sigaction    act, oact;
  
    act.sa_handler = func;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
  
    if (signo == SIGALRM) {
#ifdef SA_INTERRUPT
        act.sa_flags |= SA_INTERRUPT;
#endif
    }
    else {
#ifdef SA_RESTART
        act.sa_flags |= SA_RESTART;
#endif
    }
  
    if (sigaction(signo, &act, &oact) < 0)
        return(SIG_ERR);
    return (oact.sa_handler);
}


     sigsuspend 함수

    신호 마스크를 재 설정하는 연산과 프로세스를 재우는 연산을 하나의 원자적인 연산으로
    수행할 수 있는 함수가 sigsuspend 이다

    #include <signal.h>
    int sigsuspend(const sigset_t *sigmask);
    반환 값 : -1, errno는 EINTR 로 설정됨

    이 함수를 호출하면 프로세스의 신호 마스크가 sigmask 가 가리키는 값으로 설정된다. 그런후 프로세스는
    어떠한 신호가 잡히거나 신호에 의해 프로세스가 종료될 때 까지 일시 정지된다.  신호가 잡히고 그것을
    처리한 신호 처리부가 반환되면 sigsuspend의 호출이 반환되어서 프로세스가 실행을 재개한다.
    이 때 프로세스 마스크는 sigsuspend 호출 이전의 값으로 재 설정된다.

 

 


반응형
Posted by 공간사랑
,