프로세스 사이의 통신
- 운영체제는 같은 호스트상에서 실행중인 프로세스 사이의 통신을 가능하게 하는 여러가지 방법을 제공하며, 소켓 API도 그 중에 하나일 뿐이다.
시그널
-
우리 몸에는 신체 내부 또는 외부의 자극을 받아들여 다른 부위로 전달하고 반응을 일으키는 신체기관인 신경계가 있다. 컴퓨터에서 이와 비슷한 역할을 하는 것이 바로 시그널이다.
-
시그널은 프로세스에게 전달되는 특정 정보를 포함하고 있는 신호이다. 리눅스 운영체제의 경우 여러 가지 사건들에 번호를 부여하여 관리하고 있다.
-
시그널은 운영체제에서 프로세스로 전달할 수 있고 프로세스에서 다른 프로세스로 전달할 수도 있다.
-
시그널을 발생시키는 발생시키는 다양한 사건은 크게 에러 발생, 외부에서 발생한 사건, 명시적인 호출과 같이 세 가지 유형으로 분류할 수 있다.
-
산술적인 에러, 정수 오버플로우, 정수를 0으로 나눈 경우 등과 같이 프로그램에서 발생한 에러를 운영체제에서 검출했을 경우에도 시그널이 발생한다.
-
입력의 도착, 타이머의 경과, 자식 프로세스 종료와 같은 외부 사건이 발생 했을 때도 시그널은 발생한다.
-
마지막으로 명시적으로 시그널을 발생시키는 라이브러리 함수가 호출된 경우에도 시그널이 발생한다.
-
시그널이 프로세스로 배달되면 프로세스는 해당 시그널을 위하여 정의된 핸들러 루틴을 실행하게 된다.
-
시그널은 무시하거나, 시그널이 발생하였을 때 처리할 수 있는 함수를 지정하여 해당 함수를 호출하거나 시그널 종료에 따라서 기본 동작을 수행하는 것이 대표적으로 정의된 루틴이라고 볼 수 있다.
시그널의 종류
-
signal.h
파일에는 시그널이 양의 정수 매크로로 정의되어 있다. 정의된 매크로의 값은kill -l
명령어를 통해서도 확인 가능하다. -
사용빈도가 높은 시그널들을 다음 표에 정리해보았다.
번호 | 매크로이름 | 설명 |
---|---|---|
2 | SIGINT | 사용자가 발생시키는 인터럽트 (CTRL + C) |
3 | SIGQUIT | 프로그램 종료와 코어 파일 작성 |
8 | SIGFPE | 부동소수점에러가 발생했을 때 발생하는 시스널 |
9 | SIGKILL | 즉각적인 프로그램 종료를 위해 사용 |
14 | SIGALRM | 지정된 타이머의 경과 |
15 | SIGTERM | 프로그램 종료를 위한 시그널 (kill 명령어의 DEFAULT 시그널) |
17 | SIGHLD | 자식 프로세스의 종료 시에 발생하는 시그널 |
19 | SIGSTOP | 프로세스를 정지시키는 시그널 |
“만약 우리가 시그널로 정의 되어 있는 특정 사건의 발생 여부에 관심이 있고 그 사건이 발생할 때 마다 무언가를 하고 싶다면 어떻게 해야할까?”
- 다음과 같은 경우에는 시그널이 발생했을 때 처리를 담당하는 핸들러 함수를 정의하고 운영체제에게 해당 사건이 발생 했을 때, 정의한 핸들러 함수를 호출해달라고 부탁하면 된다.
void (*signal(int sig, void (*func) (int)))(int);
- 핸들러 함수를 운영체제에 등록하는 함수는 위와 같다.
예제
- 터미널에서
CTRL + C
입력을 통해서SIGINT
시그널이 발생하였을 때, 이벤트가 발생했음을 화면에 출력하는 예제 프로그램이다.
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int global_count = 10;
void sigint_handler(int sig) {
if (sig == SIGINT) {
printf("received SIGINT... %d lives left \n", global_count--);
}
if (global_count <= 0) {
signal(SIGINT, SIG_DFL);
}
}
int main(int argc, char *argv[]) {
int i = 0;
signal(SIGINT, sigint_handler); // 터미널 창에서 CTRL + C 입력을 받으면 운영체제에게 sigint_handler 함수를 호출해 달라는 요청을 하는 과정이다.
while (1) {
printf("%d: sleep and awake \n", ++i);
sleep(5);
}
return 1;
}
-
CTRL + C
를 누를 때마다, 시그널 핸들러에 등록한 함수가 실행되고, 곧바로 “sleep and awake” 문자열이 출력되는 것을 확인할 수 있다. -
이것은 시그널이 발생하면
sleep()
함수의 호출을 통해서sleep()
상태에 있는 프로세스가 깨어난다는 것을 의미한다.
SIGACTION 함수
- 다음은
signal()
함수보다는 복잡하지만, 추가적인 기능을 제공하는sigaction()
함수에 대해서 이야기를 해보자
int sigaction(int sig, const struct sigaction *act, struct sigaction * oldact);
- sig : 시그널 dml 종류를 지칭하는 시그널 번호
- act: 시그널 발생시 호출되는 시그널 핸들러의 포인터
- oldact: 이전에 등록되었던 시그널 핸들러의 포인터
struct sigaction {
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flags;
}
sigaction
함수는 시그널 핸들러 정보를 관리하기 위해서sigaction
이라는 구조체를 사용한다.
예제
- 터미널에서 CTRL + C 입력을 통해서 SIGINT 시그널을 발생시킨다.
- 1초에 한번 씩 카운트를 1씩 감소시켜 카운트 값이 0이되면 프로그램을 종료한다.
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main(int argc, char *argv[]) {
int i = 5;
struct sigaction new_action, old_action;
new_action.sa_handler = SIG_IGN; // 핸들러로 SIG_IGN 등록
sigemptyset(&new_action.sa_mask);
new_action.sa_flags = 0;
sigaction(SIGINT, &new_action, &old_action);
while (1) {
printf("%d: sleep and awake \n", i--);
sleep(1);
if (i == 0) break;
}
return 1;
}
- CTRL + C를 눌러도 프로그램의 루틴이 모두 실행될 때까지 프로그램은 종료되지 않는다.
SIGINT
시그널을 무시하도록 시그널 핸들러를 설정했기 때문이다.
시그널 발생
- 지금까지는 시그널을 전달받았을 때, 그것을 처리하는 방법에 대해서 다뤘고, 마지막으로 시그널을 발생시키는 함수에 대해서 다뤄볼 것이다.
#include <sys/types.h>
#include <signal.h>
int kill (pid_t pid, int sig);
- pid: 시그널을 전송하려고 하는 목적지 프로세스의 PID
- sig: 시그널의 종류 선택
- 결과값: 성공시 0, 실패시 -1
kill()
함수의 첫번째 인자의 PID는 음수또는 0을 입력 받을 수 있는데 의미는 다음과 같다.
- 음수로 입력한 경우에는 pid의 절대값이 지칭하는 프로세스가 속해있는 프로세스 그룹 전체로 시그널이 전달된다.
- 0을 입력받는 경우
kill
함수를 호출한 프로세스가 속해있는 프로세스 그룹 전체로 시그널이 전달된다.- -1을 입력 받는 경우
kill
함수를 호출한 프로세스가 시그널을 전송할 수 있는 권한을 가지고 있는 모든 프로세스에게 시그널이 전달된다.
참고 문헌
>> Home