C에서 SIGINT 신호 처리

Jinku Hu 2023년10월12일
  1. signal기능을 사용하여SIGINT신호 핸들러 루틴 등록
  2. sigaction함수를 사용하여SIGINT신호 핸들러 루틴 등록
C에서 SIGINT 신호 처리

이 기사에서는 C에서SIGINT신호를 처리하는 방법에 대한 여러 방법을 보여줍니다.

signal기능을 사용하여SIGINT신호 핸들러 루틴 등록

SIGINT는 터미널 인터럽트 문자 (일반적으로 Ctrl + C)와 관련된 미리 정의 된 신호 중 하나입니다. 셸이 현재 프로세스를 중지하고 기본 루프로 돌아가서 사용자에게 새 명령 프롬프트를 표시하도록합니다. 신호는 커널 내부의 프로세스와 사용자 공간간에 전송되는 작은 알림 일뿐입니다. 일반적으로 프로그램의 정상적인 실행을 중지하고 주어진 신호 유형에 대해 특수 작업을 실행하기 때문에 소프트웨어 인터럽트라고도합니다. 액션은 대부분 시스템에서 기본값으로 정의되지만 사용자는 특수 기능을 구현하고 신호에 대한 새 액션으로 등록 할 수 있습니다.

하지만 일부 신호는 운영 체제에서 할당 된 엄격하게 고정 된 동작을 가지며 커널이 응답하지 않는 프로세스를 종료하는 것과 같은 중요한 작업을 수행하는 데 사용하므로 재정의 할 수 없습니다.

하지만 ‘SIGINT’는 처리 할 수있는 신호의 종류로, 사용자가 프로세스가 신호를 수신 할 때 실행할 사용자 정의 함수를 등록 할 수 있음을 의미합니다. SIGINT 신호의 기본 동작은 프로세스가 종료되는 것입니다. 다음 예제 코드에서는 무한while 루프를 실행하는 프로그램을 구현하여 내부에서fprintf 함수를 계속 호출합니다. 비록 루프가 시작되기 직전에signal 함수를 호출하여SIGINT``sigintHandler 함수를 핸들러로 등록합니다. 핸들러는 함수 호출이 하나 뿐이며stdout에 문자열을 인쇄합니다.

신호 처리기 코드가 후드 아래의 전역 프로그램 데이터를 수정하는 재진입이 아닌 함수를 호출해서는 안되므로printf대신write가 사용됩니다. 예제를 시연하려면 프로그램을 실행 한 다음 다른 터미널에서SIGINT신호를 보내 동작을 관찰해야합니다. 일반적으로while루프 실행을 중지하고"Caught SIGINT!"문자열을 인쇄 한 다음 성공 상태 코드와 함께 종료해야합니다.

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

#define errExit(msg)    \
  do {                  \
    perror(msg);        \
    exit(EXIT_FAILURE); \
  } while (0)

static void sigintHandler(int sig) {
  write(STDERR_FILENO, "Caught SIGINT!\n", 15);
}

int main(int argc, char *argv[]) {
  if (signal(SIGINT, sigintHandler) == SIG_ERR) errExit("signal SIGINT");

  while (1) {
    fprintf(stderr, "%d", 0);
    sleep(3);
  }

  exit(EXIT_SUCCESS);
}

sigaction함수를 사용하여SIGINT신호 핸들러 루틴 등록

UNIX 시스템에서signal함수 호출의 최신 구현이 간단한 사용 사례에 대해 안정적으로 작동하더라도 신호 핸들러 등록을 위해sigaction함수를 사용하는 것이 좋습니다. signal호출에 비해 훨씬 더 많은 옵션을 제공하지만 신호의 심각한 사용 사례에 필요한 핵심 기능도 제공합니다. sigaction은 특수struct sigaction인수를 사용하여 핸들러 함수 포인터 및 기타 인디케이터를 지정합니다. 이 경우, 부모가 기다리는 동안 자식 프로세스가 전역 변수shutdown_flag를 조건 표현식으로 사용하여while루프를 실행하는 시나리오를 구현합니다. shutdown_flag변수는 신호 핸들러 코드에서 전역 적으로 안전하게 수정할 수있는 특수 정수인sig_atomic_t유형입니다. 따라서 사용자가SIGINT신호를 하위 프로세스에 보내면shutdown_flag0값으로 설정하는cleanupRoutine함수가 호출되고 제어는 조건 표현식이 다시 평가되고while루프로 돌아갑니다. 0은 루프에서 끊어지게합니다. 자식은 종료되고 부모는waitpid함수가 반환 될 때 상태를 얻습니다.

#define _POSIX_C_SOURCE 199309
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

#define errExit(msg)    \
  do {                  \
    perror(msg);        \
    exit(EXIT_FAILURE); \
  } while (0)

volatile sig_atomic_t shutdown_flag = 1;

void cleanupRoutine(int signal_number) { shutdown_flag = 0; }

int main(void) {
  int wstatus;

  pid_t c_pid = fork();
  if (c_pid == -1) errExit("fork");

  if (c_pid == 0) {
    printf("printed from child process - %d\n", getpid());

    int count = 0;
    struct sigaction sigterm_action;
    memset(&sigterm_action, 0, sizeof(sigterm_action));
    sigterm_action.sa_handler = &cleanupRoutine;
    sigterm_action.sa_flags = 0;

    // Mask other signals from interrupting SIGINT handler
    if (sigfillset(&sigterm_action.sa_mask) != 0) errExit("sigfillset");

    // Register SIGINT handler
    if (sigaction(SIGINT, &sigterm_action, NULL) != 0) errExit("sigaction");

    while (shutdown_flag) {
      getpid();
    }
    printf("pid: %d exited\n", getpid());

    exit(EXIT_SUCCESS);
  } else {
    printf("printed from parent process - %d\n", getpid());
    int ret;

    if (waitpid(c_pid, &wstatus, WUNTRACED) == -1) errExit("waitpid");
  }

  exit(EXIT_SUCCESS);
}
작가: Jinku Hu
Jinku Hu avatar Jinku Hu avatar

Founder of DelftStack.com. Jinku has worked in the robotics and automotive industries for over 8 years. He sharpened his coding skills when he needed to do the automatic testing, data collection from remote servers and report creation from the endurance test. He is from an electrical/electronics engineering background but has expanded his interest to embedded electronics, embedded programming and front-/back-end programming.

LinkedIn Facebook

관련 문장 - C Signal