2018년 11월 27일 화요일

[Linux/Unix] 시그널(Signal)

이번시간에는 프로세스에게 메시지를 전달하는 신호인 시그널(Signal)에대해서 살펴보도록 하겠다.


시그널(signal)이란?

현재 상태나 작업에 대한 의미를 전달하기 위해 사용하는 방법중 신호와 메시지가 있다.

프로세스가 실행중에 사용자가 특정 시그널을 통해서 프로세스 중지(대기), 종료 등의 작업을 요청할 수 있으며, 프로그램에서 어떤 작업을 처리하는 것에 대해 사용자로부터 요청을 받아서 동작이 되는 등 여러가지 동작상태를 제어할 수 있다.

신호는 1:1로 의미가 매칭 되어서 추상적이지 않고 구체적으로 어떤 신호인지 인식할 수 있다. 단순하게는 쉽고 빠르게 전달하는 반면에, 복잡한 의미에 대해 전달하는 것은 어려움을 갖는다.

리눅스 운영체제는 미리 약속되어있는 수십가지의 signal을 제공하는데, 대포적으로 kill을 명령어 사용시 미리 약속된 signal에 해당하는 옵션을 줄 수 있다.

# kill -1  // 1은 SIGHUP 시그널을 의미한다.

ex) kill -9 // 9는 SIGKILL 시그널을 의미..
 이 외에도 시그널의 이름과 시그널의 고유한 번호를 확인하면 의미를 알 수 있다.


Shell에서 signal은 키보드 입력으로 사용되기도 한다.
프로세스를 종료 시킬때, ctrl+c를 입력하여 (ctrl+c는 SIGINT를 의미함) 프로세스에게
SIGINT가 전달되어서 프로세스가 종료된다.

ex) 무한반복되는 프로그램에서 중단 혹은 프로그램을 종료하기위해서 ctrl+c를 입력 즉, SIGINT의 시그널을 통해서 종료를 하는경우가 있다.

이외에도 키보드로 입력받는 시그널은 ctrl+z SIGSTP : 프로세스 중단, ctrl+\ SIGQUIT : core dump를 남기고 프로세스를 종료 시킴. 등이 있다.

시그널은 고유의 의미를 내포하고 있으며, 시그널을 받은 실행객체인 프로세스는 그에 맞는 행동을 해야한다. 다음과 같이 행동을 취한다.

1. 요청받은 시그널을 무시.
2. 요청받은 시그널에 대해 처리되도록 등록된 함수를 호출.
3. 요청받은 시그널을 무시하지 않지만, 특별히 다른 함수를 호출하는 일을 하지 않음.

우리의 실생활에서도 다양한 시그널이 존재한다.
예를들어, 핸드폰에 전화가 왔을때, 전화를 받거나 무시하는 행동은 위의 2번과 1번에 해당하며, 만약 시그널을 무시하지도 않고 호출할 함수도 등록되지 않았다면, 시그널에 대한 기본행동을 취하게 된다.(3번에 해당함) 기본행동은 다음과 같다.

1. 프로세스가 죽는다. 대부분의 시그널에 대한 기본행동
2. 프로세스가 중단된다.
3. 아무일 없이 지나간다. -> 무시됨

ex) CTRL + C를 입력하면 SIGINT신호가 발생한다고 했다. 이때, 시그널에 대한 행동을 정하지 않았다면 프로세스는 기본행동에 해당하는 프로세스 종료를 취한다.


그렇다면, 모든시그널에 대해서는 제어할 수 있을까?

=> 아니다. 제어가능한 시그널과 그렇지못한 시그널이 존재한다.
대부분의 시그널은 catch가 가능하여 무시하거나 기본행동을 취하거나, 별도의 함수를 실행시킬 수 있지만, catch 불가능한 것들이 있다. 바로 SIGKILL과 같은 시그널이다.

SIGKILL은 프로세스를 강제로 종료하기 위한 시그널이다. 오동작 하거나 CPU자원을 무한대로 소비하는 프로세스 or 떠돌아다니는 좀비 프로세스같은 것들이 있다면, 종료시켜야한다.
하지만, SIGKILL이 만약에 제어가 가능하다면, catch되어 무시될것이다. 그러면 시스템 관리자는 이 프로세스에 대해서 어떠한 처리를 할 수 없다. 그래서 모든 프로세스들은 SIGKILL에 대해서는 catch하지 못하도록 한다.

비슷하게 SIGSTOP도 마찬가지로 catch할 수 없다. SIGSTOP을 받은 프로세스는 즉시 중단 되어야하며, 무시할 수도 없다. 위에서말한 시스템 관리를 위한 목적으로 사용되는 것이기 때문이다.

그렇다면 시그널 프로그래밍에 대해서 살펴보자.

1) SIGKILL구현하기.

▶소스코드 [killtest.c]
  1. #include<unistd.h>
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. int main(int argc, char * argv[]){
  5.         int p_id;
  6.         int kill_option;
  7.         if ( argc != 3){
  8.                 printf("command : [%s] [option]\n", argv[0]);
  9.                 exit(1);
  10.         }
  11.         p_id = atoi(argv[1]); //process id
  12.         kill_option = atoi(argv[2]); //Signal
  13.         //      true -> return 0.
  14.         //      false = return -1.
  15.         if( (kill(p_id, kill_option)) == 0){
  16.                 printf("Process %d kill\n", p_id);
  17.         }
  18.         return 0;
  19. }



▶실행결과 [백그라운드로 실행중인 프로세스 kill]

















2) 무한 루프 프로세스에서 CTRL +C (SIGINT)의 처리를 통해 종료하기.

▶소스코드 [loop.c]
  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. int main(){
  4.         int i = 0;
  5.         while(1){
  6.                 printf("%d\n", i++);
  7.                 sleep(1);
  8.         }
  9.         printf("is printed?\n");
  10.         return 0;
  11. }



▶실행결과 [시그널 SIGINT를 전달하여 프로세스 종료]
















3) CTRL + C (SIGINT)의 처리를 무시해보자 (첫번째 무시, 두번째는 프로세스 종료)

▶소스코드 [signal.c] 
  1. #include<unistd.h>
  2. #include<signal.h>
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. void sig_handler(int signo){
  6.         // SIGINT = 2.
  7.         printf("Signal handler : SIGNAL NUM[%d]\n", signo);
  8.         psignal(signo, "Received Signal");
  9. }
  10. int main(){
  11.         void(*hand)(int);
  12.         hand = signal(SIGINT, sig_handler);
  13.         if(hand == SIG_ERR){ // signal false -> retur SIG_ERR.
  14.                 perror("SIG_ERR");
  15.                 exit(1);
  16.         }
  17.         printf("1st wait.... Ctrl + c. \n");
  18.         pause();
  19.         printf("After 1st... \n");
  20.         printf("2st wait.... Ctrl + c. \n");
  21.         pause();
  22.         printf("After 2st... \n");
  23.         return 0;
  24. }


▶실행결과1 [시그널 SIGINT를 전달하여 프로세스 종료 - LINUX]



▶실행결과2 [시그널 SIGINT를 전달하여 프로세스 종료 - UNIX]




: CTRL+C를만나 종료되서 마지막 printf("After 2nd Signal Handler\n")문이 종료가 되어야한다. 운영체제마다 미세한 차이가 보인다. 유닉스와 경우 마지막 문장이 출력되지 않고 프로세스가 종료되나, 리눅스의 경우 출력까지 해버리고 종료시킨다.

3번 예제의 목적 : SIGINT를 한번 무시할 수 있으나, 두번째는 SIGINT시그널이 올때마다 시그널 핸들러 호출을 재 지정해야되는 것을 보임.
signal()함수가 실패하면 SIG_ERR를 리턴하고 sig_handler()함수인 시그널 핸들러는 한번 실행후에 시그널 처리 방법을 기본 처리방법(SIG_DFL)으로 재설정을 해버린다.
그래서 시그널 핸들러를 재지정해야 한다. (4번 예제참고)







4) CTRL + C (SIGINT)의 처리를 쭉 무시해버리는 프로그램

▶소스코드 [signal.c] 

  1. #include<unistd.h>
  2. #include<signal.h>
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. void multi_sig_handler(int signo){
  6.         void (*hand)(int);
  7.         hand = signal(SIGINT, multi_sig_handler);
  8.         if(hand == SIG_ERR){ // signal() error.
  9.                 perror("signal() error.");
  10.                 exit(1);
  11.         }
  12.         printf("Signal Handler : SIGNO[%d]\n", signo);
  13.         psignal(signo, "Received Signal");
  14. }
  15. int main(){
  16.         void (*hand)(int);
  17.         hand = signal(SIGINT, multi_sig_handler);
  18.         if(hand == SIG_ERR){
  19.                 perror("signal() error.");
  20.                 exit(1);
  21.         }
  22.         printf("1st wait.... ctrl+c\n");
  23.         pause();
  24.         printf("After 1st....\n");
  25.         printf("2st wait.... ctrl+c\n");
  26.         pause();
  27.         printf("After 2st....\n");
  28.         printf("3st wait.... ctrl+c\n");
  29.         pause();
  30.         printf("After 3st....\n");
  31.         return 0;
  32. }


▶실행결과1 [시그널 SIGINT를 전달하여 프로세스 종료2 - LINUX]














▶실행결과2 [시그널 SIGINT를 전달하여 프로세스 종료2 - UNIX]

















5) CTRL + C (SIGINT)를 SIGSET을 통해 쭉 무시해버리는 프로그램.
: signal핸들링을 한번만 지정하고 default값으로 하는게아닌, 계속 유지되는 설정.


▶소스코드 [signal2.c] 
  1. #include<unistd.h>
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include<signal.h>
  5. void handler(int signo){
  6.         printf("SIGNO : %d\n", signo);
  7.         psignal(signo, "Received signal");
  8. }
  9.  
  10. int main(){
  11.         if(sigset(SIGINT, handler) == SIG_ERR){
  12.                 perror("sigset");
  13.                 exit(1);
  14.         }
  15.         int i = 0;
  16.         while(1){
  17.                 printf("%dst Wait.....\n", ++i);
  18.                 pause();
  19.                 printf("After %d.....\n", i);
  20.         }
  21.         return 0;
  22. }



▶실행결과 [시그널 SIGINT를 전달하여 프로세스 종료2 - LINUX]







































6) Ctrl + c (SIGINT) && Ctrl + z (SIGTSTP)둘다 무시해버리는 프로그램.

▶소스코드 [signal.c] 
  1. #include<unistd.h>
  2. #include<signal.h>
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. void handler(int signo){
  6.         printf("SIGNO : %d\n", signo);
  7.         psignal(signo, "Received Signal");
  8.         if(signo == 2) // is SIGINT.
  9.                 printf("Key Input [ ctrl + c ]\n");
  10.         if(signo == 24) // is SIGTSTP
  11.                 printf("Key Input [ ctrl + z ]\n");
  12. }
  13. int main(){
  14.         int count = 0;
  15.         struct sigaction act;
  16.  
  17.         sigemptyset(&act.sa_mask);
  18.  
  19.         sigaddset(&act.sa_mask, SIGINT); // SIGINT handler.
  20.         sigaddset(&act.sa_mask, SIGTSTP); // SIGTSTP handler.
  21.  
  22.         act.sa_flags = 0;

  23.         // SigAction added handler.
  24.         act.sa_handler = handler;

  25.         // SIGINT and SIGTSTP add.
  26.         if( sigaction(SIGINT, &act, (struct sigaction *)NULL) < 0
  27.                         || sigaction(SIGTSTP, &act, (struct sigaction *)NULL) < 0){
  28.                 perror("sigaction : SIGINT or SIGTSTP not added.");
  29.                 exit(1);
  30.         }
  31.         // loop.
  32.         while(1){
  33.                 printf("%dst Wait.... \n", ++count);
  34.                 pause();
  35.                 printf("After %d....\n", count);
  36.         }
  37.         return 0;
  38.  
  39. }


▶실행결과1 [시그널 SIGINT, SIGTSTP에 대한 결과 - UNIX]





▶실행결과2 [시그널 SIGINT, SIGTSTP에 대한 결과 - LINUX]


































: Unix와 Linux의 약간의 차이가 있다.  Unix와 Linux의 SIGINT의 번호는 2로 매핑되어있다.
하지만, SIGTSTP[CTRL+Z]는 Unix에서 24번, Linux에서는 20번에 해당하는것을 확인할 수 있다. 두개를 종료시킬때는 SIGQUIT [CTRL + \]로 종료시켰다. [코어덤프 생성]






이로써 우리는 시그널에 대해서 살펴보았는데, 이것을 통해 프로세스의 스케줄링 작업 및 시스템 관리하는 방법을 깊이있게 배웠다.





댓글 없음:

댓글 쓰기

[Java] N-I/O(Non-Blocking) 파일 읽기 쓰기 - GatheringByteChannel, ScatteringByteChannel, ByteBuffer 사용.

우리는 지금까지 다음과 같이 살펴보았다. 1.  InputStream / OutputStream : 입, 출력 스트림을 바이트로 처리하여 읽기, 쓰기. 2.  FileInputStream / FileOutputStream : 입, 출력 스트림을 ...