프로세스 사이의 통신


  • 운영체제는 같은 호스트상에서 실행중인 프로세스 사이의 통신을 가능하게 하는 여러가지 방법을 제공하며, 소켓 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