Researcher to Developer

시그널 (signal)- 동작 원리, 주요 시그널, 프로세스와 관계 본문

카테고리 없음

시그널 (signal)- 동작 원리, 주요 시그널, 프로세스와 관계

Probe29 2021. 1. 2. 19:09

#시그널 (signal)
시그널은 IPC 기법 중 하나로 사용될 수 있지만 그 외 사용 방법도 있다.
유닉스에서 30년 이상 사용된 전통적인 기법이다.
커널 또는 프로세스에서 다른 프로세스에 어떤 이벤트가 발생되었는지를 알려주는 기법

시그널 사용 예
Default로 정의되어 있는 시그널이 OS에서 해당 프로세스에 전달 된다.
Ctrl + C  : 프로세스 종료시키기
Ctrl + Z  : 프로세스가 background 프로세스로 바뀐다.




#주요 시그널
시그널 종류와 각 시그널에 따른 기본 동작이 미리 정해져 있다.
대부분 내부적으로 시그널 번호가 매핑되어 있다.

SIGKILL
프로세스를 죽여라
(슈퍼관리자가 사용하는 시그널로, 프로세스가 어떤 경우든 죽음)

SIGALARM
알람을 발생한다.

SIGSTP
프로세스를 멈춰라 (=Ctrl + z)

SIGCONT
멈춰진 프로세스를 실행해라 (Continue)

SIGINT
프로세스에 인터럽트를 보내서 프로세스를 죽여라 (=Ctrl + c)

SIGSEGV
프로세스가 다른 메모리영역을 침범했다.

kill -l 

kill -l 을 입력하면 Signal 들의 목록을 볼 수 있다.


* SIGUSR1, 2 이런 것들은 프로그램 내에서 어떤 동작을 할지 정의를 사용자가 정할 수 있는 SIG




#시그널 동작 종류

프로그램에서 특정 시그널의 기본 동작(Defalut) 대신 다른 동작을 하도록 구현이 가능하다.
각 프로세스에서 시그널 처리에 대해 다음과 같은 동작 설정이 가능하다.

 
1. 시그널 무시
2. 시그널 블록 (블록을 푸는 순간, 해당 프로세스에서 시그널 처리)
3. 프로그램 안에 등록된 시그널 핸들러로 재정의한 특정 동작 수행
4. 등록된 시그널 핸들러가 없다면, 커널에서 기본 동작 수행
등등




#시그널 보내기
해당 프로세스에 특정 시그널을 보낼 때 사용하는 시스템 콜

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

int kill(pid_t pid, int sig);

// pid : 프로세스의 PID
// sig : 시그널 번호

 

예제
./loop & : &는 Background 프로세스로 실행시키겠단 의미 
./sigkill 1806 2
ps  : 프로세스의 상태 확인 가능 명령어 (PID)




#받는 시그널에 따른 동작 정의

#include <signal.h>
void (*signal(int signum, void (*handler)(int)))(int);

 

// 예 1

signal(SIGINT, SIG_IGN);                                      // SIGINT를 받으면 SIG_IGN 하라는 설정을 받는 시그널
                      // void (*handler)(int) : SIG_IGN - 시그널 무시, SIG_DEL - 디폴트 동작하라고 만들 수도 있음

 


// 예 2

signal(SIGINT, (void*)signal_handler);
                                // SIGINT 시그널 수신 시, 그 프로그램 안에서 정의한 signal_handler 함수를 호출해라. 
                                    라고 재정의한 동작을 설정할 수 있음

 

 



#시그널 관련 코드 예제

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

static void signal_handler (int signo) {                 // signal_handler의 함수를 정의(이름은 맘대로)
         printf("Catch SIGINT!, but no stop\n");
}
int main (void) {                                                         // signal 를 사용하도록 함.
         if (signal (SIGINT, signal_handler) == SIG_ERR) {
                printf("Can't catch SIGINT!\n");
                exit (1);
         }
         for (;;)
                      pause();
         return 0;
}

이 프로그램은 종료가 안된다. (무한 루프)
종료가 안되기 때문에 background 프로세스로 실행 시켜야 다른 명령을 쉘에서 실행 시킬 수 있다. 

(실행 파일 뒤에 & 표시 사용)

SIGINT는 프로세스 종료가 Defalut 동작인데
우리가 재정의하여 Catch SIGINT!, but no stop 라는 문구가 나오게했다.




#시그널과 프로세스(리눅스)
시그널이 내부적으로 어떻게 작동하는지 좀 더 깊게 알아보자

프로세스는 프로세스 상태를 나타내는 PCB가 있다.
PCB에는 시그널과 관련된 몇 가지 자료 구조가 들어가 있다.
PCB에서 해당 프로세스가 블록 또는 처리해야하는 시그널 관련 정보를 관리할 수 있다.
결과적으로 커널 모드에서 사용자 모드 전환시 시그널 정보 확인해서 해당 내용에 대한 처리를 한다. 



1. pending
해당 프로세스에 전달이 되면 받은 시그널의 순서를 가지고 있게 된다. 
가장 먼저 받은 시그널을 받아서 시그널 동작을 한다.

2. sigpending
block 된 시그널이 무엇인지 나타낸다.

3. blocked
시그널의 개수는 64개
64 bit의 간단한 데이터 구조를 가지고 있다.
bit가 1로 바뀐다는 것은 해당 시그널이 block 되어 있다는 상태를 나타낼 수 있다.

4. sig
각각의 받은 시그널에 대해서 어떤 동작을 처리해야하느냐
Defalut 동작이냐, 프로그램에서 재정의된 동작이냐를 관리하는 자료 구조이다.

 

 


어떤 프로세스 안에 시그널이 들어오면
시그널 관련 자료 구조에 해당 시그널을 넣어준다.


하나의 프로세스는 시스템 콜을 처리하거나 인터럽트를 받거나 
시스템 리소스 처리를 하거나 스케줄링을 하는 등등의 이유로 
수시로 커널 모드로 바뀌게 된다. 


사용자 모드로 전환하는 시점에 마지막에 하는 일 중에 하나가

PCB에 있는 시그널 자료 구조를 확인한 다음
커널에 시그널에 대한 동작(Defalut나 재정의된 동작)이 구현이 되어 있으니
그 동작을 한다.