C 시그널 처리

C 시그널 처리는 C 프로그래밍 언어에서 실행 중인 프로그램이 운영 체제 혹은 다른 프로세스로부터 발생하는 비동기 이벤트(시그널)를 감지하고, 해당 시그널에 대응하는 함수를 실행하도록 하는 메커니즘이다. 시그널은 주로 프로세스 종료, 중단, 알람, I/O 오류 등과 같은 시스템 레벨 사건을 알리기 위해 사용되며, POSIX 표준을 기반으로 한 대부분의 UNIX‑like 운영 체제에서 지원된다.


1. 개념 및 역사

  • 시그널(signal)은 프로세스에게 특정 사건이 발생했음을 알리는 인터럽트와 유사한 메커니즘이다.
  • 초기 C 표준(C89)에서는 signal() 함수만을 정의했으며, 이후 POSIX.1‑2001 표준에 따라 보다 정교한 sigaction() 인터페이스가 추가되었다.
  • C 표준 라이브러리(<signal.h>)는 시그널 정의와 기본적인 처리를 제공하지만, 실제 시그널 동작은 운영 체제에 의존한다.

2. 주요 헤더와 타입

#include <signal.h>
  • typedef void (*sighandler_t)(int); // 시그널 핸들러 함수 포인터 타입 (POSIX)
  • typedef sig_atomic_t // 시그널 핸들러와 메인 코드 간에 안전하게 공유 가능한 정수형

3. 핵심 함수

함수 설명 비고
void (*signal(int sig, void (*func)(int)))(int); 지정된 시그널 sig에 대해 핸들러 func를 등록한다. 기존 핸들러 포인터를 반환한다. 표준 C, 구현에 따라 동작이 제한적
int raise(int sig); 현재 프로세스 내에서 시그널 sig를 발생시킨다. 테스트 용도 또는 프로그램 자체에서 시그널을 트리거
int kill(pid_t pid, int sig); 지정된 프로세스(pid)에 시그널 sig를 전송한다. <unistd.h>에 정의, POSIX
int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact); 시그널 핸들러를 보다 상세히 정의한다. sigaction 구조체를 통해 블록 마스크, 플래그, 대체 핸들러 등을 지정 가능. POSIX 권장 방식
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); 현재 프로세스의 시그널 마스크를 변경한다. 시그널 차단/해제에 사용
int sigpending(sigset_t *set); 현재 프로세스에 pending 상태인 시그널 집합을 반환한다.
int sigsuspend(const sigset_t *mask); 지정된 마스크로 일시적으로 차단을 해제하고, 시그널이 도착하면 반환한다.

4. 시그널 핸들러 작성 시 주의사항

  1. 비동기 안전성(Async‑safety)
    시그널 핸들러 내부에서는 printf(), malloc() 등 비동기 안전이 보장되지 않은 함수는 사용하면 안 된다. POSIX는 async-signal-safe 함수 목록을 정의한다(예: write(), _exit(), signal(), sig_atomic_t 변수 접근 등).

  2. sig_atomic_t 사용
    핸들러와 메인 코드 사이에 데이터를 공유할 경우 volatile sig_atomic_t 타입을 사용해 원자성을 확보한다.

  3. 재진입 방지
    동일 시그널이 다시 발생했을 때 핸들러가 중첩 실행되지 않도록 sigactionSA_RESTART 혹은 SA_RESETHAND 플래그를 적절히 설정한다.

  4. 시그널 마스크
    특정 구간에서 시그널을 차단하고 싶을 때 sigprocmask() 혹은 pthread_sigmask()(멀티스레드 환경) 를 사용한다.

5. 예제 코드

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

volatile sig_atomic_t quit = 0;

void handle_sigint(int sig)
{
    (void)sig;               // 사용하지 않음 표시
    quit = 1;                // 메인 루프에 종료 신호 전달
}

int main(void)
{
    struct sigaction sa;
    sa.sa_handler = handle_sigint;
    sigemptyset(&sa.sa_mask);   // 시그널 마스크 비우기
    sa.sa_flags = 0;            // 기본 플래그

    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }

    printf("Ctrl‑C를 눌러 종료합니다...
");
    while (!quit) {
        pause();                // 시그널 대기
    }

    printf("프로그램을 종료합니다.
");
    return 0;
}

설명

  • SIGINT(Ctrl‑C) 발생 시 handle_sigint가 호출되어 quit 플래그를 설정한다.
  • while 루프에서는 pause()를 사용해 시그널이 올 때까지 대기한다.
  • sigaction을 사용했으므로 시그널 핸들러는 비동기 안전하게 동작한다.

6. 플랫폼별 차이

플랫폼 지원 시그널 비고
Linux (glibc) POSIX 전부 + 실시간 시그널(SIGRTMIN~SIGRTMAX) sigaction 권장
macOS POSIX 시그널 (실시간 시그널 미지원) signal도 동작하지만 sigaction이 안전
Windows 제한적인 C 시그널(SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM) 실제 시그널 메커니즘이 없으며, C 런타임이 에뮬레이션
RTOS (예: VxWorks) 구현에 따라 다름 실시간 시그널 지원 여부가 주요 차이점

7. 관련 주제

  • 실시간 시그널(Real‑time Signals) – POSIX에서 우선순위와 큐잉을 제공하는 확장 시그널.
  • 멀티스레드와 시그널pthread_sigmask, sigwait, sigwaitinfo 등으로 스레드별 시그널 처리.
  • 프로세스 제어fork(), exec(), waitpid()와 시그널 연계.
  • 시그널 안전 함수 – POSIX가 정의한 async-signal-safe 함수 목록.

8. 참고 문헌

  1. ISO/IEC 9899:1999 (C99) – <signal.h> 규격.
  2. POSIX.1‑2008 – 신호(Signal) 인터페이스 정의.
  3. Stevens, W. R., & Rago, S. (2013). Advanced Programming in the UNIX Environment (3rd ed.). Addison‑Wesley.
  4. The Open Group Base Specifications Issue 7 – sigaction, sigprocmask 등.

위 내용은 C 언어에서 시그널을 처리하기 위한 핵심 개념·함수·사용법을 포괄적으로 정리한 백과사전 수준의 설명이다.

둘러보기

더 찾아볼 만한 주제