42 minitalk 과제를 진행하기 앞서 시그널에 대한 공부가 필요한 거 같아 간단하게 정리해보는 시간을 가지겠다!!
시그널은 유닉스 환경에서 30년 동안 사용된 전통적인 기법 중 하나로 프로세스에 어떤 event가 발생했음을 알리는 간단한 메세지를 비동기적으로 보내는 것이라고 할 수 있다. process는 간단히 말해 실행중인 프로그램(메모리에 적재된)이라 할 수 있는데 실행중인 processor에 특정 이벤트가 발생함을 알릴 수 있는 기법 중 하나다. 그리고 시그널이 발생했을 때 프로세스가 실행되는 동작을 처리? 정의하는 곳을 signal handler라고 한다. 이 시그널 핸들러는 기본적으로 os가 지정된 동작이 정의되어 있는데 특정 시그널을 제외하면 프로세스 내에서 새롭게 핸들러는 정의할 수 있다. 이외에도 시그널의 특징은 다음과 같다.
프로그래머가 임의로 발생하는 시그널 이외에도 os, 커널 측에서 프로세스에게 시그널을 보내는 경우가 있다.
kernel이 보내는 시그널 이외에도 프로그래머(사용자)가 필요에 따라 특정 프로세스에게 시그널을 보내는 방법은 크게 3가지 정도가 있다.
kill 명령어 사용 예
#include <stdio.h>
#include <unistd.h>
int main()
{
int i = 0;
printf("my pid: %d\n", getpid());
while (1)
{
printf("time: %d\n", i++);
sleep(1);
}
}
키보드 입력 예
아래의 그림과 같이 os kernel 자체에서 processor에세 signal을 보내거나 어떤 프로세스가 kernel에 요청을 해 보내는 방법 2가지가 존재한다.
시그널의 동작은 위의 그림이 잘 설명하고 있는데, 1) 프로세스가 시그널을 받으면, 2) 시그널 핸들러로 컨트롤(제어권)을 넘기고 3) 핸들러에 정의된 동작을 수행하고 4) 시그널 핸들러는 다음 명령어를 실행(코드로 치면 다음 줄 명령어?)하는 과정을 거친다.
시그널은 크게 2 종류로 구분할 수 있는데, 1) 리눅스의 표준 시그널과 2) 실시간 시그널 두 가지로 나눌 수 있다. 표준 시그널은 커널이 프로세스에게 이벤트를 알릴 때 사용하는 시그널로 1 ~ 31의 상수값으로 구분된다. 실시간 시그널은 응용 프로그램에 정의된 목적을 위해 사용될 수 있는 시그널로 32 ~ 63까지의 번호가 매겨진 시그널을 사용한다.
각 os에서 시그널 리스틀 확인하기 위해 kill -l 명령어를 사용해 확인할 수 있다.
자주 사용되는 시그널 몇 가지를 정리해보자.
번호 | 시그널 | 설명 |
---|---|---|
1 | SIGHUP | Hangup: 접속을 끊을 때 → 터미널과 연결이 끊어졌을 때 발생 |
2 | SIGINT | Interrupt: 현재 작동 중인 프로그램의 동작을 멈춤 |
3 | SIGQUIT | Quit: 사용자가 터미널에서 종료키를 누를 때 |
6 | SIGABRT | Abort: 비정상적인 함수에 의해 발생 |
9 | SIGKILL | Kill: 실행 중인 프로세스를 강제 종료할 때 |
11 | SIGSEGV | Segmentation Violation: 메모리 액세스가 잘 못 되었을 때 발생 |
13 | SIGPIPE | 종료된 소켓에 쓰기를 시도할 때 |
14 | SIGALAM | 알람 타이머 만료 시에 발생 |
15 | SIGTERM | Terminate: 정상적인 종료 방법 |
17 | SIGCHLD | 자식 프로세스가 종료할 때 사용 |
18 | SIGCONT | 중지된 프로세스 실행할 때 사용 |
19 | SIGSTOP | SIGCONT 시그널 받을 때 사용 |
20 | SIGTSTP | 프로세스 대기로 전환할 때 사용 |
이외에도 프로그래머가 임의로 사용할 수 있는 시그널로 SIGUSR1(10), SIGUSR2(12)도 존재한다.
앞서 말했듯이 이미 커널이 특정 시그널이 발생했을 때 정의된 동작이외 다른 동작을 프로그래머가 구현할 수 있다. 시그널이 발생했을 때 프로세스가 동작하는 부분을 시그널 핸들러라고 하는데 c에서는 system call인 signal, sigaction 두 메소드를 통해 시그널 핸들러를 조작할 수 있다.
함수 원형: sighandler_t signal(int signum, sighandler_t handler)
예를 들어 signal(SIGUSR1, sig_handler); 와 같이 코드를 구현하면 SIGUSR1 시그널이 발생하면 해당 프로세스는 sig_handler 함수를 실행한다는 의미이다. 다음 코드는 c에서 signal handler를 간단하게 구현한 예이다.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
void sigusr1_handler(int signo)
{
printf("SIGNAL: %d\n", signo);
printf("새롭게 정의된 SIGUSR1");
exit(1);
}
int main()
{
int i = 0;
signal(SIGUSR1, sigusr1_handler);
printf("my pid: %d\n", getpid());
while (1)
{
printf("time: %d\n", i++);
sleep(1);
}
}
![]() |
![]() |
SIGUSR1와 같이 사용자 시그널 이외에도 기존에 kernel에 정의된 signal 또한 handler를 정의할 수 있다.
💡 SIGKILL, SIGSTOP 시그널은 프로그래머가 못 건드린다!
![]() |
![]() |
위와 같이 기존 SIGINT 시그널 또한 재정의할 수 있다!
💡 이외에도 c에서 제공하는 signal 관련 라이브러리(sigaction, sigemptyset, …)가 많은데 추후 다음 포스팅에서 다뤄보겠다처음에 시그널을 설명할 때 시그널은 프로세스에게 어떤 이벤트가 발생함을 작은 데이터를 통해 알린다고 설명했다. 여기서 추가적으로 덧붙일 내용은 데이터를 보낼 때 비동기적으로 보낸다는 것이다.
비동기적이라는 개념을 간단히 설명하면 말 그대로 동시에 일어나지 않는다를 의미하는데, 하나의 요청에 따른 다른 응답을 즉시 처리하지 않아도, 그 대기 시간동안 다른 요청을 처리 가능하다는 의미이다.
시그널이 비동기적으로 발생한다는 것은 두 가지 측면으로 해석이 가능하다.
Dest Process 측에서 시그널이 발생하여 핸들러가 실행되는 중에 시그널이 또 발생되면 새로운 시그널은 시그널 큐라는 공간에서 대기하다가 핸들러 동작이 끊난 후에 실행된다.
위와 같이 시그널이 발생했으나 아직 처리하지 못 한, 전달되지 않은 시그널을 pending signal이라고 한다. 여기서 주의해야 할 점은 같은 번호의 시그널이 signal queue에 계속 들어오면 쌓이는 것이 아닌 단 한 개의 signal만 큐에서 대기한다. 달리 말하면 특정 시그널이 pending 상태일때 해당 시그널이 발생하면 큐에 넣지 않고 discard한다!
💡 왜 저런 성질을 가지냐하면 bit mask인가 뭔가 때문인데 이것도 다음 포스팅에서 다뤄보겠다.