0. 시작하기전에
- 운영체제 파트에서 시그널을 IPC기법으로 사용할 수 있다는 것을 배웠다.
- 프로세스에서 발생한 이벤트를 조건으로 인터럽트를 발생시켜 다른 프로세스가 알게 하는 방식이었다.
https://devraphy.tistory.com/175
- 하지만, 이 시그널을 단순히 IPC기법 중 하나로 설명하기에는 그 역할이 너무 방대하다.
- 그러므로 시그널에 대해서 알아보자.
1. 시그널(signal)이란?
- UNIX에서 30년 이상 사용된 전통적인 기법
- 커널 또는 프로세스에서 다른 프로세스에게 어떤 이벤트가 발생되었는지 알려주는 기법
ex) 터미널에서 Ctrl + C를 눌러 프로세스를 종료시키는 것 = 시그널 사용
a) 시그널의 기본 동작
- OS에는 다양한 시그널이 default로 정의되어 있다.
- 간단한 예를 통해서 시그널의 동작과정을 이해해보자.
- 터미널에서 Ctrl + C를 누르면, OS에서 이를 시그널로 인식한다.
- OS는 시그널에 해당하는 기능을 찾고, 시그널이 발생된 프로세스에서 해당 기능을 실행한다.
- 즉, 프로세스는 종료된다.
b) kill -l
- 운영체제에는 기본적으로 정의된 시그널이 존재한다.
- 이를 확인하기 위해서는 다음의 명령어 kill -l을 통해서 확인할 수 있다.
2. 시그널 재정의
- 프로그램에서 특정 시그널의 기본 동작 대신 다른 동작을 구현할 수 있는데, 이를 재정의라고 한다.
- 각 프로세스에서 다음과 같은 동작을 구현할 수 있다.
▶ 시그널 무시
▶ 시그널 블락(블락을 푸는 순간, 해당 프로세스에서 시그널을 수행한다.)
▶ 프로그램 내부에 등록된 시그널 핸들러를 통해 재정의한 동작 수행
▶ 커널에서 기본(default) 동작 수행
a) 시그널 동작 정의
- 아래의 예시는 시그널 무시와 시그널 핸들러를 호출하는 방식으로 시그널 동작을 처리한 것이다.
- 다음의 예시를 살펴보자.
#include <signal.h>
void (*signal(int signum, void (*handler)(int)))(int);
// 예시 1
// void (*handler)(int): SIG_IGN - 시그널 무시 / SIG_DFL - 시그널 디폴트
signal(SIGINT, SIG_IGN); // 특정한 시그널 번호가 발동하면 무시한다.
// 예시 2
// SIGINT 시그널 수신 시, signal_handler 함수를 호출
signal(SIGINT, (void *)signal_handler); // 특정 시그널 번호가 발동하면 시그널 핸들러 호출
b) 시그널 예시
1. sigloop.c
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
static void signal_hander(int signo) { // 시그널 핸들러 정의
printf("Catch SIGINT!, but no stop\n");
}
int main(void) {
if (signal(SIGINT, signal_hander) == SIG_ERR) { // 핸들러 에러처리
printf("Can't catch SIGINT! \n");
exit(1);
}
for (;;) // 무한루프
pause();
return 0;
}
- 위의 프로그램은 SIGINT를 재정의한 프로그램이다.
- SIGINT는 터미널에서 Ctrl + C를 눌렀을 때, 동작하는 시그널로 프로세스를 종료하는 시그널이다.
- SIGINT가 발동되면, 기본동작을 수행하지 않고 signal_handler라는 함수가 동작하도록 재정의 하였다.
- 무한루프를 도는 프로그램이므로, 백그라운드에서 실행하도록 한다.
- 백그라운드에서 잘 실행되고 있는 것을 확인할 수 있다.
- sigloop의 프로세스 번호가 14219번 이라는 것을 확인하였다.
2. sigkill.c
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int pid, result;
int sig_num;
if (argc != 3) {
printf("usage %s [pid] [signum]\n", argv[0]);
exit(1);
}
pid = atoi(argv[1]);
sig_num = atoi(argv[2]);
result = kill(pid, sig_num);
if (result < 0) {
perror("To send Signal is failed\n");
exit(1);
}
return 0;
}
- 위의 프로그램은 pid와 signum을 인자로 받아서 kill 시그널에게 넘기도록 하는 프로그램이다.
- 만약 kill 시그널이 제대로 작동하지 않는다면, 위에서 정의한 에러메세지가 출력될 것이다.
- sigloop를 백그라운드에서 실행시켜놓은 상태에서 다음과 같이 터미널에 입력해보자.
- 앞쪽 숫자는 PID이고 뒤에 2는 SIGINT의 signal 번호를 의미한다.
- 올바른 PID와 시그널 번호가 sigkill 프로그램에게 전달되면, 이는 kill 메소드를 호출한다.
- kill 메소드는 프로세스를 중단시키기 위해 SIGINT 시그널을 호출하게 된다.
- SIGINT 시그널을 호출하게 되면, 위에서 재정의한 동작이 시그널 핸들러가 작동한다.
- 전체적인 과정은 다음과 같다.
- 이처럼 시그널을 재정의하여, 시그널이 default 동작이 아닌 재정의된 동작을 수행하도록 할 수 있다.
3. 시그널과 프로세스
- 다음 그림을 통해 시그널이 프로세스 내부적으로 어떻게 동작하는지 알아보자.
- 프로세스 내부에 프로세스의 상태를 나타내는 PCB에는 시그널을 처리하기위한 몇가지 자료구조가 있다.
* PCB(Process Control Block)
▶ pending
- 수신된 시그널을 순서대로 대기시켜놓는 자료구조 (FIFO 순서)
▶ sigpending
- blocked와 함께 관리되는 자료구조
▶ blocked
- 64 비트로 이루어진 자료구조
- 운영체제의 기본 64개의 시그널을 표시한다.
- blocked에 표시된 시그널은 시그널 블락에 상태가 된다.
▶ sig
- 각 시그널에 대해 어떤 동작을 처리해야하는지를 관리한다.
- 시그널의 default 동작인지, 재정의 동작인지를 처리한다.
a) 결론
- 이처럼 프로세스는 시그널 뿐만 아니라 시스템콜, 인터럽트, 스케줄링, 시스템 자원관리 등의 이유로
- 수시로 사용자 모드에서 커널 모드로 변경된다.
- 그렇다면 언제 PCB에 존재하는 시스템 콜을 확인하는 걸까?
- 운영체제가 커널모드에서 사용자모드로 바뀌는 그 시점에, 사용자 모드로 바뀌기 전 마지막 작업으로 PCB의 시그널을 확인한다.
- 기본 동작을 실행하는 시그널인 경우, 운영체제의 커널 내부에 이미 정의가 되어 있기에 바로 실행이 가능하다.
- 재정의된 동작을 실행하는 시그널의 경우, 프로세스 내부적으로 PC(Program Counter)가 재정의된 동작을 다음 동작으로 가리키도록 바꿔놓는다.
'컴퓨터공학기초 개념 > 시스템 프로그래밍' 카테고리의 다른 글
30. 쉘 스크립트 - 조건문 (0) | 2021.09.24 |
---|---|
29. 쉘 스크립트 - 이해와 변수 (0) | 2021.09.23 |
27. 프로세스 - IPC 기법(shared memory) (0) | 2021.09.21 |
26. 프로세스 - IPC 기법(pipe, message queue) (0) | 2021.09.17 |
25. 프로세스 - 우선순위 스케줄링 시스템 콜 (0) | 2021.09.17 |
댓글