시그널에 대한 더 자세한 내용과 그 외의 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

sigaction을 왜 사용할까?

이전 UNIX의 signal함수보다 더 정교한 작업이 가능한 함수입니다. 그 정교한 작업이라는 게 어떤 것들이 있을까요? 

  • 정교한 작업1 (sigaction 코드 예제 - 자식 프로세스의 현재 상태 확인)

예를 들어서 자식이 종료하면 자식 프로세스에서 SIGCHLD 신호를 자식 프로세스에서 발생이 됩니다. 그러면 부모프로세스는 자식이 종료하였는지를 알 수 있죠. 그런데 많은 분들은 자식이 종료(EXIT)할때만 SIGCHLD를 발생시키는 것으로 알고 있습니다. 그런데요. 자식 프로세스는  자신이 정지(STOP)되었을 때, 혹은 재개(CONTINUED)되었을 때, 하물며 다른 프로세스에 의해 죽었을때 (KILL) 역시 부모 프로세스에 SIGCHLD를 보내게 됩니다. 우리가 signal 콜만 이용했을 경우 이러한 차이점을 부모 프로세스가 알아서 세세하게 제어할 수가 없습니다. 그런데 sigaction을 그런 차이들을 알아내어 컨트롤이 가능합니다. 이에 대한 예제 코드는 sigaction에 대해서 설명한 이후에 등장합니다.

  • 정교한 작업2 (sigaction 코드 예제  - 시그널 함수 구현)

뿐만 아니라 read 시스템 콜이 발생하여 사용자로부터 입력을 기다리고 있는 도중에, 시그널이 발생했다고 가정해보세요. 이럴 경우 시그널 핸들러 수행 이후에 1)read를 다시 호출해서 사용자 입력을 받을까요? 아니면 그냥 read는 넘어가고 다음 코드부터 수행할까요? 이러한 제어는 어떻게 해야하는 건가요? 이렇게 재개를 할지, 말지도 sigaction을 통해서 정할 수 있습니다. 물론 재개할지 말지 정하는 sigaction 사용 예제 코드는 밑에 있습니다.  

그전에 이 함수를 알기 위해서는 어느정도 시그널에 대한 기본지식이 있어야합니다. 시그널 집합, 시그널 차단 등의 개념이 나오기 때문인데요. 아래의 포스팅을 통해서 개념을 잡고 오시면 될것 같네요.

https://reakwon.tistory.com/46

 

[리눅스] 시그널 (SIGNAL)1 시그널 다루기(시그널 핸들러)

시그널 의미전달에 사용되는 대표적인 방법은 메세지와 신호입니다. 메세지는 여러가지 의미를 갖을 수 있지만 복잡한 대신 , 신호는 1:1로 의미가 대응되기 때문에 간단합니다. 컴퓨터에서 신

reakwon.tistory.com

https://reakwon.tistory.com/53

 

[리눅스] 시그널 (SIGNAL) 2 시그널 함수 sigprocmask, sigfillset, sigemptyset, sigaddset, sigdelset

시그널 관련 함수 지난 시간에는 간단하게 시그널 기본적인 설명과 어떻게 핸들링하는지에 대해서 알아보았죠? 시그널 개념과 시그널 핸들러는 지난 포스팅을 참고하시기 바랍니다. https://reakwo

reakwon.tistory.com

 

https://reakwon.tistory.com/54

 

[리눅스] 시그널(Signal) 3 sigpending, sigismember, sigsuspend

시그널에 대해서 이야기하는 3번째 시간이 되겠네요. 지난 번에는 시그널 개념과 시그널 관련함수까지 다루어 봤습니다. 시그널 개념과 시그널 핸들러와 시그널 관련 함수(sigfillset, sigemptyset, s

reakwon.tistory.com

 

sigaction

이 함수를 이용하면 어느 특정 신호에 관련된 동작을 조회할 수 있고 수정할 수 있습니다. sigaction의 원형은 이렇습니다.

#include <signal.h>

int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);

 

사용하기 위해서는 signal.h 헤더 파일을 include시켜줘야합니다. 

▶ signum : signum은 시그널 번호를 의미합니다. signal함수의 처음 인자와 같습니다. 이런거 있잖아요. SIGINT, SIGQUIT 같은 시그널 번호말이죠. 단, 제어불가한 신호 번호는 SIGKILL과 SIGSTOP입니다. 

act : sigaction 구조체인 act 인자는 signum에 대해서 어떤 동작을 취할지에 대한 정보를 담고 있습니다. 즉, 시그널에 대한 동작을 수정하는 정보입니다.

oact : 역시 sigaction구조체인데, 이전에 설정된 동작에 대해서 돌려줍니다. 즉, 시그널에 대한 동작을 조회하는 정보입니다.

sigaction의 구조체를 한번 볼까요? 

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_handler : 앞서 signum에 대한 동작을 나타내는 함수의 포인터입니다. 설정되지 않으면 기본동작을 의미하는 SIG_DFL입니다.
  •  sa_sigaction : sa_flags로 SA_SIGINFO를 사용할때 설정할 수 있습니다. 이런 경우에는 sa_handler가 사용되지 않고 이 sa_sigaction이 대신 사용됩니다. sa_sigaction에서는 신호 처리부(신호를 처리하는 함수)에 두가지 정보를 더 담아서 보냅니다. siginfo_t와 프로세스 문맥의 식별자가 그것입니다. 
    • 가장 처음 int는 시그널 번호입니다.
    • siginfo_t는 시그널에 대한 부가적인 정보를 담은 구조체입니다. 어떤 정보를 포함하는지는 이 구조체를 참고하면 됩니다. 시그널 정보에 많은 정보들이 들어가기 때문에 필드가 많으니 리눅스 메뉴얼을 참고하시기 바래요! 짧막하게 보면 아래와 같은 정보가 들어갈 수 있습니다.
    • 이후 void* 는 커널이 저장해둔 signal context의 정보를 담습니다.
siginfo_t {
   int      si_signo;     /* Signal number */
   int      si_errno;     /* An errno value */
   int      si_code;      /* Signal code */
   int      si_trapno;    /* Trap number that caused
                             hardware-generated signal
                             (unused on most architectures) */
   pid_t    si_pid;       /* Sending process ID */
   uid_t    si_uid;       /* Real user ID of sending process */
   int      si_status;    /* Exit value or signal */
   clock_t  si_utime;     /* User time consumed */
   clock_t  si_stime;     /* System time consumed */
…
}
  •  sa_mask : 차단할 신호의 집합입니다. sigprocmask를 통해서 특정 신호를 BLOCK 시킬지, 말지를 정합니다.
  •  sa_flags : 신호 옵션들입니다. 아래와 같은 옵션들이 존재합니다. 
SA_NOCLDSTOP signum이 SIGCHLD인 경우 자식 프로세스가 정지되었을때, notification을 받지 않습니다. 자식이 SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU 신호를 받아 정지되었을때 신호를 안받는다는 겁니다. 
SA_NOCLDWAIT signum이 SIGCHLD일때, 자식 프로세스가 종료되었을때 시스템이 좀비프로세스를 만들지 않게 합니다.
SA_NODEFER 신호가 잡혀서 신호 처리 함수가 실행되는 도중에 다시 같은 신호가 발생됐을때, 시스템이 자동으로 차단하지 않습니다.
SA_ONSTACK sigaltstack으로 대체 스택을 선언해두었다면 신호가 대안 스택의 프로세스에 전달됩니다.
SA_RESETHAND 신호 처리 함수에 진입할때 이 신호의 처리 방식을 SIG_DFL로 재설정하고 SA_SIGINFO 플래그를 지웁니다. 
SA_RESTART interrupt된 시스템 콜 호출이 자동으로 재시작됩니다. 아래 예에서 보겠습니다.
SA_RESTORER 어플리케이션에서 사용할 의도로 만들어진 flag가 아닙니다. sa_restorer와 관련된 옵션입니다.
SA_SIGINFO 신호 처리부에 추가적인 두가지 정보를 전달합니다. 이때 sa_sigaction함수 포인터를 설정해야합니다. 위의 sa_sigaction 인자에 대한 설명을 참고하세요.
  •  sa_restorer : 이 필드는 앱 사용 목적으로 만들어진 필드가 아닙니다. sigreturn과 관련된 필드라고 하네요. 넘어가겠습니다.

 

sigaction 코드 예제 - 자식 프로세스의 현재 상태 확인

시그널 핸들러를 이용해서 자식 프로세스가 종료하여 SIGCHLD를 발생했을 때 wait을 호출해서 자식 프로세스의 종료 상태를 알 수 있습니다. 그런데 자식 프로세스의 종료뿐만 아니라 정지, 재개 상태로 바뀌었을 때도 이러한 SIGCHLD를 발생시킨다고 했었습니다. 그렇다면 자식 프로세스가 종료할 경우에만 딱 wait할 수 있는 방법이 있을까요?

SA_SIGINFO 플래그와 siginfo_t의 si_code를 이용하면 됩니다.

//sigchld_info.c

#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>

void action(int signo, siginfo_t *info, void* context){
        if(signo == SIGCHLD){
                printf("pid:%d, uid:%d\n", info->si_pid, info->si_uid);
                if(info->si_code == CLD_EXITED){
                        pid_t child_process = wait(NULL);
                        printf("[parent] child process(%d) exit\n", child_process);
                        exit(0);
                }
                if(info->si_code == CLD_KILLED){
                        pid_t child_process = wait(NULL);
                        printf("[parent] child process(%d) killed\n", child_process);
                        exit(1);
                }
                if(info->si_code == CLD_STOPPED)
                        printf("[parent] child process stopped\n");
                if(info->si_code == CLD_CONTINUED)
                        printf("[parent] child process continued\n");
        } 
}
int main(){
        pid_t pid;
        struct sigaction act;
        act.sa_flags = SA_SIGINFO;
        act.sa_sigaction = action;

        if((pid = fork()) < 0){
                printf("fork error \n");
                return 1;
        } else if(pid == 0){  //child process
                int count = 0;
                while(count < 30) {
                        printf("\t[child] count:%d\n", count++);
                        sleep(1);
                }
                exit(0);
        } else { //parent process 
                printf("[parent] child process : %d\n", pid);
                sigaction(SIGCHLD, &act, NULL);
                while(1) pause();
        }

}

sigaction 구조체에 SA_SIGINFO를 이용해서 sa_sigaction 핸들러를 활용합니다. SA_SIGINFO를 사용하게 되면 sa_sigaction을 사용할 수 있다고 위 설명해서 말씀드렸죠?

struct sigaction act;
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = action;

action 함수에서 siginfo_t의 si_code는 SIGCHLD 신호가 발생했을 때 부가적인 정보가 담겨져있습니다. 그래서 보다 정교한 제어를 하게 됩니다. 

void action(int signo, siginfo_t *info, void* context)

30초간 자식의 상태를 바꿀 수 있습니다. Kill 명령어를 통해서 정지, 재개를 해보세요. 그리고 kill -SIGKILL을 통해서 비정상 종료도 해보시면 자식 프로세스의 상태를 더 자세히 확인할 수 있습니다.

terminal1 terminal2
# ./a.out
[parent] child process : 236739
...
               [child] count:6
pid:236739, uid:0
[parent] child process stopped
pid:236739, uid:0
[parent] child process continued
                [child] count:7
                [child] count:8
...
                [child] count:29
pid:236739, uid:0
[parent] child process(236739) exit
# kill -SIGTSTP 236739
# kill -SIGCONT 236739

 

si_code의 값은 발생한 시그널에 따라서 다르게 설정이 됩니다. 아래의 표를 참고하시기 바랍니다.

 

sigaction 코드 예제  - 시그널 함수 구현

아래는 sigaction함수를 통해서 signal함수를 흉내낸 코드입니다. 

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

typedef void Sigfunc(int);

Sigfunc* my_signal(int signo, Sigfunc *func){
        struct sigaction act,oact;
        act.sa_handler = func;
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;

        if(sigaction(signo, &act, &oact) < 0)
                return SIG_ERR;
        return oact.sa_handler;

}
void sig_int(int signo){
        printf("sig_int start\n");
}

int main(){
        Sigfunc* origin;
        //그전의 신호 처리 함수 포인터가 origin에 저장됨. 지금은 SIG_DFL
        origin = my_signal(SIGINT, sig_int);
        printf("main start\n");
        //신호가 발생할때까지 대기
        pause();
        printf("main end\n");
}
main start
^Csig_int start      <-- Ctrl+C 입력
main end

 

뭔가 되는것같긴한데 문제점이 있습니다. 아래와 같이 main함수를 변경해봅시다. 

int main(){
        int n;
        char buf[64];
        my_signal(SIGINT,sig_int);
        if((n=read(0,buf,64)) < 0){
                printf("read failed\n");
        }else{
                printf("%s\n",buf);
        }

}

 

그리고 실행시켜보면 읽기가 실패했습니다. read하는 도중에 Ctrl+C를 눌러서 신호를 발생시켰고, 그때문에 신호 처리함수가 호출이 되었습니다. 이때 read는 interrupt되었지만, 다시 복구 되지 않고 있는 현상이 문제입니다.

^Csig_int start    <-- Ctrl + C 입력
read failed

그래서 SA_RESTART 플래그를 넣어서 시스템 콜이 재시작되도록 설정합니다.

Sigfunc* my_signal(int signo, Sigfunc *func){
        //... 중략
        act.sa_flags = 0;
        act.sa_flags |= SA_RESTART;
        //... 중략
}

이후의 실행은 아래와 같습니다.

hello^Csig_int start    <-- Ctrl + C 입력
world
world

 

hello를 입력하는 와중에 Ctrl+C를 입력시켜서 SIGINT신호를 발생시켰습니다. 그래서 sig_int start라는 출력문이 수행이되었고, 신호 처리 함수가 끝난 후 다시 read를 하게 됩니다. world를 출력하고 엔터를 치면 world만 출력이 되네요.

다시 read를 호출했기 때문입니다. 

지금까지 sigaction에 대한 설명과 sigaction을 활용한 예제 2가지를 보았습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

시그널에 대한 더 많은 정보를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

시그널

의미전달에 사용되는 대표적인 방법은 메세지와 신호입니다. 메세지는 여러가지 의미를 갖을 수 있지만 복잡한 대신 , 신호는 1:1로 의미가 대응되기 때문에 간단합니다. 컴퓨터에서 신호, 즉 시그널은 소프트웨어적인 interrupt입니다. 컴퓨터 용어에서 인터럽트라는 것은 하던일 A를 중간이 잠시 멈추고 다른일 B를 하고 난 후 다시 A로 돌아와서 멈춘 부분부터 일을 하는 것이죠.  자, 이 의미를 명확히 아셔야할 필요가 있습니다. 신호가 왜 SW적인 인터럽트인가가 포스팅 아래에 코드와 함께 설명이 됩니다.

우선 컴퓨터에서 시그널의 종류에는 어떤 것들이 있을까요? 리눅스 상에서 kill -l 명령어를 입력하면 모든 시그널의 종류와 대응되는 번호를 알 수 있습니다.

 

#kill -l

 

 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP

 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1

11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM

16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP

21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ

26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR

31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3

38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8

43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13

48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12

53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7

58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2

63) SIGRTMAX-1  64) SIGRTMAX

 
이 시그널 중에 몇가지만 알아보도록 하겠습니다. 

 
1. SIGHUP : 터미널과 연결이 끊겼을 때 발생합니다. 기본적인 처리는 프로세스가 종료되는 것입니다.
2. SIGINT : 인터럽트가 발생했을때 발생합니다. 기본으로 프로세스가 종료됩니다.
9. SIGKILL : 프로세스를 무조건 종료합니다. 절대 무시할 수 없으며 제어될 수도 없습니다.
11. SIGSEGV : 프로세스가 잘못된 메모리를 참조했을 때 발생합니다. 기본 동작은 코어덤프를 남기고 종료합니다.
19 SIGSTOP : 프로세스를 중단시킵니다. 종료한 상태는 아닙니다. 이 신호 역시 제어될 수 없습니다.

 

프로세스가 시그널을 받게 되면

 

1. 시그널에 해당되는 기본 동작을 하거나
2. 그 시그널을 무시하거나
3. 사용자가 정의한 함수를 통해 동작 방식을 바꿀 수 있습니다.
 
시그널은 다음과 같은 성질이 있습니다.
 
● 비신뢰성
시그널을 보내면 그 시그널이 제대로 도착했는지, 잘 전달되었는지 확인하지 않습니다. 때문에 신뢰성이 낮습니다.
 
 대기하지 않음
만약 시그널 처리 함수를 시그널을 처리하고 있는데 그 사이에 다시 시그널을 주게 되면 그 시그널은 무시됩니다.
 
 
프로세스를 직접 생성해서 시그널을 한번 줘보도록 합시다.

 

#include <signal.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
int main(){
        printf("give signal...\n");
        sleep(30);
        exit(0);
}

이후 컴파일을 하여 실행파일로 만들어줍니다. 이후 실행하면 30초간 프로그램이 실행될 겁니다.

 

이 상태에서 Ctrl+C를 누르게 되면 SIGINT 신호를 보내게 됩니다. 기본동작은 종료이므로 프로세스가 종료하게 됩니다.

 

또는 kill -시그널번호 프로세스 id 를 통해서 시그널을 보낼 수 있습니다. 그러니까 SIGINT를 보내려면 kill -2 pid 또는 kill -SIGINT pid를 통해서 SIGINT보낼 수 있습니다.

 

 

# ./a.out

give signal...

^C

#
 
우리는 이 시그널을 제어하고 싶습니다. SIGINT 신호 발생시 종료한다는 문자열을 출력하고 3초 이후에 종료하고 싶습니다.
 
그래서 리눅스는 signal핸들러를 제공합니다.
 
signal함수
원형은 다음과 같습니다.
 

void (*signal(int signum, void (*handler)(int)))(int);

 

signum은 시그널을 발생시키는 번호입니다. 아까 SIGINT는 2번이었죠?? 아니면 매크로를 쓸수도 있습니다. SIGINT 그대로가 매크로로 정의되어 있습니다.

 

두번째 인자로 handler라는 함수포인터가 보이네요. 여기에 함수를 인자를 주게 되면 시그널을 받았을때 그 함수가 호출됩니다.

 

직접 코드로 짜고 확인해보지요.

#include <signal.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void interruptHandler(int sig){
        printf("this program will be exited in 3 seconds..\n");
        sleep(3);
        exit(0);
}

int main(){
        signal(SIGINT, interruptHandler);
        printf("input Ctrl+C\n");
        while(1);
}


컴파일하고 실행 후에 Ctrl+C를 입력하면 메시지와 함께 3초 후 프로그램이 종료됩니다.

# ./a.out 
input Ctrl+C
^Cthis program will be exited in 3 seconds..

이번엔 SIGTSTP이라는 시그널을 보내보도록 할게요. 위 코드에서 단지 SIGINT를 SIGTSTP이라고 바꿔주기만 하면 됩니다. (아, 물론 printf안에 문자열도 바꾸면 이쁘장하겠죠?)

#include <signal.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void stopHandler(int sig){
        printf("this program will stop in 3 seconds..\n");
        sleep(3);
        exit(0);
}

int main(){
        signal(SIGTSTP, stopHandler);
        printf("input Ctrl+Z\n");
        while(1);
}

그후 Ctrl+Z를 입력하여 SIGTSTP을 보내면 적절히 핸들링이 되는 것을 확인할 수 있습니다. 

# ./a.out 
input Ctrl+Z
^Zthis program will stop in 3 seconds..

 

그렇다면 SIGSTOP은 어떨까요? 아래의 코드는 SIGSTOP을 핸들링하기 위한 코드입니다. 

#include <signal.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void stopHandler(int sig){
        printf("this program will stop in 3 seconds..\n");
        sleep(3);
        exit(0);
}

int main(){
        signal(SIGSTOP, stopHandler);
        printf("wait\n");
        pause();
}

a.outkill을 통해서 SIGSTOP을 보내기 위해서 잠시 백그라운드로 돌립니다. /proc/92089/status에는 92089에 대한 프로세스 정보들이 나와있습니다. 현재 StateSpeeping, , 우리가 pause를 했기 때문에 이러한 상태가 되는 것입니다. Kill 명령으로 SIGSTOP(19)를 그 PID로 보내게 되면 어떻게 될까요?

 

# ./a.out &
[2] 92089
# wait <-- a.out 프로그램에 의한 출력
# cat /proc/92089/status | head -5
Name:   a.out
Umask:  0022
State:  S (sleeping)
Tgid:   92089
Ngid:   0
# kill -19 92089
# cat /proc/92089/status | head -5
Name:   a.out
Umask:  0022
State:  T (stopped)
Tgid:   92089
Ngid:   0

[2]+  Stopped                 ./a.out

시그널을 보내고 다시 상태를 확인해보면StateTstop 상태인 것을 확인할 수 있습니다. 여기서 알 수 있는 것은 SIGSTOP은 제어할 수 없다는 것을 보여줍니다.

위에서 SIGSTOP 설명을 보고 오세요. 제어할 수 없다는 설명이 있습니다. SIGKILL과 SIGSTOP은 사용자가 절대 제어할 수 없다는 점을 알고 있으세요~! 그 이유는 어떤 이유로 인해 프로세스를 무조건 죽여야하는 경우가 있습니다. 만약 좀비프로세스를 계속해서 생성하는 프로세스가 있는데, 이 프로세스를 죽이지 못하면 안되겠죠. 그래서 2개는 핸들링을 하지 못합니다.

또한 핸들러에 전달인자 sig는 시그널의 종류를 나타냅니다. 그렇기 때문에 시그널의 종류에 따라 처리할 수 있죠.

 

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

void signalHandler(int sig){
        if(sig==SIGINT){
                printf("this program will stop in 3 seconds..\n");
                sleep(3);
                exit(0);
        }
        if(sig==SIGQUIT){
                printf("signal SIGQUIT\n");
        }
}

int main(){
        signal(SIGINT, signalHandler);
        signal(SIGQUIT, signalHandler);
        printf("input Ctrl+C or Ctrl+\\ \n");
        while(1);
}

 

프로그램을 실행하여 Ctrl+\를 입력해서 SIGQUIT신호를 보내도 종료하지 않고, Ctrl+C를 입력하여 SIGINT를 보냈을 때 3초안에 종료합니다.

# ./a.out

input Ctrl+C or Ctrl+\

^\signal SIGQUIT

^\signal SIGQUIT

^\signal SIGQUIT

^\signal SIGQUIT

^Cthis program will stop in 3 seconds..

 

 

SW적인 Interrupt

중간에 멈추고 다른일을 하고 다시 돌아와 아까 했던일을 처리하는것이 인터럽트라고 하였습니다. 아래의 코드가 있습니다. SIGINT와 SIGQUIT을 처리하는 함수 2개가 있죠.

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

void sig_int(int signo){
        volatile int k=0;
        int i,j;
        printf("sig_int start\n");

        for(i=0;i<300;i++)
                for(j=0;j<600000;j++)
                        k+=i*j;

        printf("sig_int end\n");
}

void sig_quit(int signo){
        volatile int k=0;
        int i,j;
        printf("sig_quit start\n");

        for(i=0;i<300;i++)
                for(j=0;j<600000;j++)
                        k+=i*j;

        printf("sig_quit end\n");
}
int main(){
        signal(SIGINT, sig_int);
        signal(SIGQUIT, sig_quit);
        //pause는 신호가 생기고 처리될때까지 대기하는 함수
        pause();
        printf("process end\n");
}

저는 SIGINT와 SIGQUIT 순서대로 시그널을 줄겁니다. for문을 도는 이유는 SIGINT가 끝나기 전에 SIGQUIT을 수행하기 위해서 시간 지연을 하는 역할을 합니다. 그럴때 결과를 여러분들이 예측해보세요.

아래와 같은 결과일까요? SIGINT를 처리하고 나서 SIGQUIT을 처리하니까요. 

^Csig_int start
^\sig_quit start
sig_int end
sig_quit end
process end

 

땡! 아래의 결과처럼 나오게 됩니다. 

^Csig_int start
^\sig_quit start
sig_quit end
sig_int end
process end

 

SIGINT신호가 발생되어 신호 처리부인 sig_int 수행합니다. for문을 도는 도중에 SIGQUIT 신호가 발생해서 sig_quit을 수행하러 갑니다. 그럼 sig_int는 sig_quit가 처리가 다 되고 나면 나중에 멈췄던 부분부터 다시 수행한다는 겁니다. 이런 사실을 망각하게 되면 아주 어려운 버그를 만날수가 있지요. 

느린 시스템 콜에서의 시그널

여기서부터는 라이브러리 함수가 아닌 시스템 콜과 시그널에 대해서 이야기합니다. 그 중에서도 반환이 느린 시스템 콜에 대해서 이야기합니다. 만약 read하는 동안 signal이 발생했다면 어떻게 될까요?

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

void sig_int(int signo){
        printf("SIGINT 발생!\n");
}
int main(){
        char buf[32];
        int n;

        signal(SIGINT, sig_int);
        //읽는 중 SIGINT를 발생 시키면 오류? 아니면 성공?
        if((n = read(STDIN_FILENO, buf,32)) < 0){
                printf("read error\n");
        }else{
                buf[n]='\0';
                printf("%s\n",buf);
        }
}

 

위의 코드를 리눅스 상에서 실행을 시키고 난 후 아래와 같이 실행시켜보았습니다. 

# ./a.out
^CSIGINT 발생!
^CSIGINT 발생!
hello^CSIGINT 발생!
reakwon
reakwon

 

read를 하는 와중에 SIGINT를 발생시켰는데 오류가 발생하지 않고 처음부터 다시 read를 하는 것처럼 보이는데요. 이렇게 리눅스에서는 read와 같은 대기 시간이 길어가 수행시간이 긴 시스템 콜이 발생할때 자동으로 그 시스템 콜을 재시작하게 됩니다. 그러므로 hello는 출력이 되지 않고, reakwon만 출력이 되는거죠. 재시작한다는 것이 중요한 포인트입니다. 

read뿐만 아니라, 느린 호출에 해당되는 ioctl, read, readv, write, writev, wait, waitpid가 있습니다. 

이런 재시작 처리 방식은 시스템마다 다른데요. SVR2, SVR3, Solaris 등의 시스템은 자동 재시작하지 않고 errno를 EINTR로 설정한 후 반환시킵니다. 그래서 프로그래머는 아래와 같이 errno를 검사하며 프로그래밍해야됩니다. 하지만 우리는 리눅스를 만지는 중이니까 이런 처리를 할 필요는 없죠. 

retry :
        if ((n = read(fd, buf, BUF_SIZE)) < 0){
                if(errno == EINTR){
                        goto retry:
                }
        }else{
                printf("%s\n",buf);
        }

 

이처럼 리눅스 신호의 대한 개념과 시그널을 어떻게 잡아서 다룰 수 있는지에 대해서 알아보는 시간을 가졌습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,