디시인사이드 갤러리

갤러리 이슈박스, 최근방문 갤러리

갤러리 본문 영역

[C언어] 안전한 시그널 처리: Self-Pipe Trick

나르시갤로그로 이동합니다. 2025.07.23 23:20:55
조회 68 추천 0 댓글 1


https://nimfsoft.art/ko/blog/2025/07/23/c-safe-signal-handling-self-pipe/

 



C/C++로 리눅스/유닉스 환경에서 서버나 데몬을 개발하다 보면 반드시 마주치는 난제가 있습니다. 바로 '시그널(Signal)'입니다. Ctrl + C (SIGINT)로 프로그램을 종료하거나 kill 명령으로 특정 동작을 지시하는 등, 시그널은 프로세스 외부에서 비동기적으로 발생하는 강력한 이벤트입니다.

문제는 이 강력함에 비해 안전하게 다루기가 매우 까다롭다는 점입니다. 혹시 시그널 핸들러 안에서 printf malloc을 사용했다가 프로그램이 설명할 수 없는 이유로 멈추거나 죽어버린 경험이 있으신가요? 이는 async-signal-safe라는 개념을 간과했기 때문입니다.

이번 포스트에서는 시그널 핸들러가 왜 위험한지 알아보고, 이 문제를 해결하는 정석적인 기법인 'Self-Pipe Trick'을 활용하여 안정적이고 재사용 가능한 시그널 처리 모듈을 C언어로 구현해 보겠습니다.

무엇이 문제인가? Async-Signal-Safety

시그널 핸들러 함수는 프로그램의 정상적인 흐름을 언제든지 중단시키고 실행됩니다. 만약 main 함수가 malloc을 호출하여 힙 메모리의 잠금(lock)을 획득한 바로 그 순간 시그널이 발생하면 어떻게 될까요?

  1. main 함수가 malloc 내부에서 힙 메모리 잠금을 획득합니다.
  2. 시그널이 발생하여 운영체제는 main의 실행을 중단시키고 등록된 시그널 핸들러를 호출합니다.
  3. 만약 시그널 핸들러 안에서 로그를 남기기 위해 printf malloc을 호출하면, 이 함수들 역시 내부적으로 힙 메모리 잠금을 다시 획득하려고 시도합니다.
  4. 하지만 잠금은 이미 main 함수가 보유하고 있으므로, 시그널 핸들러는 영원히 대기 상태에 빠집니다. 이를 데드락(deadlock) 이라고 합니다.

이처럼 핸들러가 언제 끼어들지 모르기 때문에, 핸들러 내부에서는 재진입(re-entrant)이 가능하고 실행이 중단되더라도 안전한, 즉 async-signal-safe하다고 보장된 함수만 사용해야 합니다. write, read, close, _exit 등 극소수의 시스템 콜만이 여기에 해당합니다. printf, malloc, free, fopen 등 우리가 흔히 사용하는 대부분의 표준 라이브러리 함수는 안전하지 않습니다.

해결책: Self-Pipe Trick

그렇다면 복잡한 처리가 필요한 시그널은 어떻게 다뤄야 할까요? 해답은 시그널 핸들러에서는 최소한의 작업만 하고, 실제 처리는 안전한 메인 프로그램의 컨텍스트로 넘기는 것입니다. Self-Pipe Trick은 이 아이디어를 구현하는 가장 대표적인 방법입니다.

동작 원리는 다음과 같습니다.

  1. 파이프 생성: 프로그램 시작 시, 자기 자신에게 데이터를 쓸 수 있는 파이프(pipe)를 하나 생성합니다. 파이프는 읽기용(fd[0])과 쓰기용(fd[1]) 파일 디스크립터(fd) 쌍으로 이루어집니다.
  2. 최소 핸들러 등록: 실제 시그널 핸들러는 오직 시그널 번호를 파이프의 쓰기용 fd(fd[1])에 write하는 작업만 수행합니다. write async-signal-safe 함수이므로 안전합니다.
  3. 이벤트 루프에서 감시: 메인 프로그램은 poll, select, epoll 같은 I/O 멀티플렉싱 함수를 이용해 파이프의 읽기용 fd(fd[0])를 감시합니다. 시그널이 발생해 핸들러가 파이프에 데이터를 쓰면, poll은 해당 fd가 읽을 준비가 되었다고 알려줍니다.
  4. 안전한 처리: 메인 루프는 파이프에서 시그널 번호를 read하고, 사전에 정의된 적절한 콜백 함수를 호출하여 시그널을 처리합니다. 이 모든 과정은 시그널 핸들러 컨텍스트가 아닌, 안전한 메인 프로그램의 컨텍스트에서 실행됩니다.

결과적으로 시그널 발생이라는 비동기 이벤트를 파일 디스크립터 I/O라는 동기 이벤트로 변환하여 다른 네트워크 소켓 등과 함께 일관되게 처리할 수 있게 됩니다.

재사용 가능한 C 코드 구현

이제 이 개념을 바탕으로 설계된 재사용 가능한 시그널 처리 모듈을 살펴보겠습니다.

1. 헤더 파일: signal_handler.h

먼저 외부로 노출될 API를 정의합니다. 사용자는 이 헤더 파일만 보고도 라이브러리의 기능을 파악할 수 있어야 합니다.

#ifndef SIGNAL_HANDLER_H
#define SIGNAL_HANDLER_H

#include <signal.h>

// 사용자 정의 시그널 핸들러 함수 포인터 타입
typedef void (*SignalHandler)(int signo);

// 시그널 처리 메커니즘을 초기화합니다.
int signal_init(void);

// 시그널 처리 리소스를 해제합니다.
void signal_fini(void);

// 특정 시그널(signo)에 대한 행동(handler)을 설정합니다.
int signal_set_action(int signo, SignalHandler handler);

// 시그널 이벤트를 수신하는 파이프의 읽기 fd를 반환합니다.
int signal_get_fd(void);

// 파이프로부터 시그널을 읽어 등록된 핸들러를 호출합니다.
int signal_dispatch(void);

#endif // SIGNAL_HANDLER_H

2. 구현 파일: signal_handler.c

Self-Pipe Trick의 핵심 로직이 담긴 구현부입니다.

#define _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include "signal_handler.h"

static SignalHandler signal_handlers[NSIG];
static int pipe_fds[2] = {-1, -1};

// [핵심] 오직 시그널 번호를 파이프에 쓰는 역할만 하는 마스터 핸들러
static void master_signal_handler(int signo, siginfo_t *info, void *context) {
    (void)info;
    (void)context;
    // async-signal-safe 함수인 write()만 사용
    ssize_t written;
    do {
        written = write(pipe_fds[1], &signo, sizeof(signo));
    } while (written == -1 && errno == EINTR);
}

int signal_init(void) {
    if (pipe2(pipe_fds, O_CLOEXEC | O_NONBLOCK) == -1) {
        perror("pipe2");
        return -1;
    }
    for (int i = 0; i < NSIG; ++i) {
        signal_handlers[i] = NULL;
    }
    return 0;
}

void signal_fini(void) {
    if (pipe_fds[0] != -1) close(pipe_fds[0]);
    if (pipe_fds[1] != -1) close(pipe_fds[1]);
}

int signal_set_action(int signo, SignalHandler handler) {
    if (signo < 1 || signo >= NSIG) return -1;

    signal_handlers[signo] = handler;
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sigemptyset(&sa.sa_mask);

    if (handler == SIG_DFL || handler == SIG_IGN) {
        sa.sa_handler = handler;
        sa.sa_flags = 0;
    } else {
        sa.sa_sigaction = master_signal_handler;
        sa.sa_flags = SA_SIGINFO | SA_RESTART;
    }

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

int signal_get_fd(void) {
    return pipe_fds[0];
}

int signal_dispatch(void) {
    int signo;
    ssize_t bytes_read;
    int dispatched_count = 0;

    while ((bytes_read = read(pipe_fds[0], &signo, sizeof(signo))) > 0) {
        if (bytes_read == sizeof(signo)) {
            SignalHandler handler = signal_handlers[signo];
            if (handler && handler != SIG_DFL && handler != SIG_IGN) {
                handler(signo);
                dispatched_count++;
            }
        }
    }
    return dispatched_count;
}

3. 사용 예제: main.c

이제 우리가 만든 모듈을 사용하여 실제 메인 애플리케이션을 작성해 봅시다. poll을 이용해 시그널 이벤트를 기다리는 모습을 확인할 수 있습니다.

#include <stdio.h>
#include <unistd.h>
#include <poll.h>
#include <stdlib.h>
#include "signal_handler.h"

volatile sig_atomic_t running = 1;

// SIGINT (Ctrl+C)에 대한 사용자 정의 핸들러
void handle_sigint(int signo) {
    printf("\nCaught signal %d (SIGINT). Shutting down...\n", signo);
    running = 0;
}

// SIGTERM에 대한 사용자 정의 핸들러
void handle_sigterm(int signo) {
    printf("\nCaught signal %d (SIGTERM). Shutting down gracefully...\n", signo);
    running = 0;
}

int main(void) {
    printf("Starting... PID: %d\n", getpid());

    if (signal_init() != 0) {
        return EXIT_FAILURE;
    }

    signal_set_action(SIGINT, handle_sigint);
    signal_set_action(SIGTERM, handle_sigterm);
    signal_set_action(SIGPIPE, SIG_IGN); // SIGPIPE는 무시

    struct pollfd pfd;
    pfd.fd = signal_get_fd();
    pfd.events = POLLIN;

    while (running) {
        printf("Waiting for a signal or other events...\n");
        int ret = poll(&pfd, 1, -1); // 무한 대기

        if (ret > 0 && (pfd.revents & POLLIN)) {
            printf("Signal event detected. Dispatching...\n");
            signal_dispatch();
        }
    }

    printf("Cleaning up and exiting.\n");
    signal_fini();
    return EXIT_SUCCESS;
}

마무리하며

Self-Pipe Trick은 시그널을 안전하게 처리하는 매우 강력하고 검증된 방법입니다. 이 기법을 통해 우리는 다음의 이점을 얻을 수 있습니다.

  • 안전성: async-signal-safe 하지 않은 함수 호출로 인한 데드락과 경쟁 상태를 원천적으로 방지합니다.
  • 통합성: 시그널 이벤트를 파일 I/O 이벤트처럼 취급하여, 복잡한 애플리케이션의 이벤트 루프에 깔끔하게 통합할 수 있습니다.
  • 단순성: 시그널 핸들러의 역할을 극도로 단순화시켜 코드의 예측 가능성을 높입니다.

물론 최신 리눅스 커널에서는 이 기법을 커널 수준에서 구현한 signalfd()라는 더 간편한 대안도 제공합니다. 하지만 Self-Pipe Trick의 원리를 이해하는 것은 모든 POSIX 시스템에서 통용되는 견고한 소프트웨어를 설계하는 데 매우 중요한 밑거름이 될 것입니다.

안전한 시그널 처리로 여러분의 다음 프로젝트가 한 단계 더 견고해지기를 바랍니다.

추천 비추천

0

고정닉 0

0

댓글 영역

전체 댓글 0
본문 보기

하단 갤러리 리스트 영역

왼쪽 컨텐츠 영역

갤러리 리스트 영역

갤러리 리스트
번호 제목 글쓴이 작성일 조회 추천
설문 모태 솔로도 구제해 줄 것 같은 연애 고수 스타는? 운영자 25/07/21 - -
공지 프로그래밍 갤러리 이용 안내 [92] 운영자 20.09.28 45806 65
2875504 '1 9 ) 윤공주 벗 방한대 ㄱㄱ 프갤러(175.195) 22:28 0 0
2875500 집요한 사람을 대하는 대처법- [1] 프갤러(121.172) 22:22 7 1
2875495 [대한민국] 나는 그냥 헌법에 따라 행동하고 말할 뿐- 프갤러(121.172) 22:14 19 1
2875492 흠.. 낼 어칼깡.. ♥불사신냥덩♥갤로그로 이동합니다. 22:11 5 0
2875483 애널 프갤에 암두 없는듯? ♥불사신냥덩♥갤로그로 이동합니다. 21:56 5 0
2875478 핵전쟁 대비 시나리오 ♥불사신냥덩♥갤로그로 이동합니다. 21:47 17 0
2875476 갑질좌파 무단결강 의혹 특검해야 ♥불사신냥덩♥갤로그로 이동합니다. 21:44 8 0
2875474 나님은 천박한 한국개돼지배급문화를 소비하지 않음 ♥불사신냥덩♥갤로그로 이동합니다. 21:42 7 0
2875468 [대한민국] -> 트럼프 측근, 여야 국회의원 13명 앞에서 '尹 부당한 프갤러(121.172) 21:34 13 2
2875467 11인치 맥 다시 쓰구싶 ♥불사신냥덩♥갤로그로 이동합니다. 21:34 10 0
2875464 나님.. 인간을 너무 과대평가.. ♥불사신냥덩♥갤로그로 이동합니다. 21:29 10 0
2875461 전라도는 통수 피해의식이 패씨브 ♥불사신냥덩♥갤로그로 이동합니다. 21:25 9 0
2875454 츄릅.. ♥불사신냥덩♥갤로그로 이동합니다. 21:10 9 0
2875452 ❤✨☀⭐⚡☘⛩나님 시작합니당⛩☘⚡⭐☀✨❤ [1] ♥불사신냥덩♥갤로그로 이동합니다. 21:07 17 0
2875449 에어콘 춥다 발명도둑잡기(118.216) 21:04 8 0
2875447 나님 담달부터 금연함 ♥팬티스타킹냥덩♥갤로그로 이동합니다. 21:02 11 0
2875442 리준섹 장군 발명도둑잡기(118.216) 20:54 9 0
2875441 흠.. ♥팬티스타킹냥덩♥갤로그로 이동합니다. 20:53 10 0
2875439 내가 관심있는 여성들 주말에 잘 쉬었나 발명도둑잡기(118.216) 20:51 7 0
2875434 이게 서울쥐포새끼들이 만든 문화 현실임 ㅋㅋㅋㅋㅋㅋ 프갤러(221.142) 20:37 19 0
2875431 냥덩이 요새 왜 발정 남? [1] 발명도둑잡기(118.216) 20:33 21 0
2875427 피궁해서 누엇더니 말똥 ♥팬티스타킹냥덩♥갤로그로 이동합니다. 20:25 13 0
2875424 군대리아 드셔보신 분 계십니까 배구공(119.202) 20:19 14 0
2875423 나는조현병이야 나는내향적이야 손발이시립디다갤로그로 이동합니다. 20:18 11 0
2875422 모바일류는 무조건 가벼워야함 ♥팬티스타킹냥덩♥갤로그로 이동합니다. 20:18 11 0
2875414 나님 위스콘신으로 예정이긴한데.. 흠.. ♥팬티스타킹냥덩♥갤로그로 이동합니다. 19:55 20 0
2875406 지진이 없으면 이미 지구는 인간이 살곳이 못된다는뜻 ♥팬티스타킹냥덩♥갤로그로 이동합니다. 19:41 14 0
2875403 모로링이 본 미래.. ♥팬티스타킹냥덩♥갤로그로 이동합니다. 19:38 19 0
2875401 시간날땐 쓰고 싶은 글이 없구 흠.. ♥팬티스타킹냥덩♥갤로그로 이동합니다. 19:36 9 0
2875400 칼럼 허벌나게 쓰고 싶을땐 허벌나게 바쁘고 ♥팬티스타킹냥덩♥갤로그로 이동합니다. 19:35 8 0
2875399 뭔가 칼럼 하나 쓰고 싶은데 눈꺼풀이 무겁당.. ♥팬티스타킹냥덩♥갤로그로 이동합니다. 19:35 9 0
2875397 역시 진짜 여름은 7월 부터당 ♥팬티스타킹냥덩♥갤로그로 이동합니다. 19:33 13 0
2875395 언어란 안 쓰면 무뎌지는 칼이당 ♥팬티스타킹냥덩♥갤로그로 이동합니다. 19:31 8 0
2875394 요즘 나님 일본어 복습중 영어는 이미 비즈니스 수준까지 가능하니 ♥팬티스타킹냥덩♥갤로그로 이동합니다. 19:31 11 0
2875392 원래 계약시간 끝나고 저녁 이후에도 달렸는데 요즘은 그냥 퍼져버림 ♥팬티스타킹냥덩♥갤로그로 이동합니다. 19:29 13 0
2875390 나님 애널 주무시기전에 칼럼 하나 쓰고 잘깡.. 흠.. ♥팬티스타킹냥덩♥갤로그로 이동합니다. 19:28 12 0
2875389 좆도그 담당아 게임 만들거면 한국 사이트를 보지마라.. [2] ㅆㅇㅆ(124.216) 19:28 48 0
2875388 와 씨발 코드랑 세팅 방법까지 그대로 다 올려주시네 [1] 루도그담당(58.239) 19:27 41 0
2875387 트럼프가 한국 좌빨들이 윤석열 대통령 건드리면 2찢명 박살낸다고 한듯? [1] ♥팬티스타킹냥덩♥갤로그로 이동합니다. 19:27 22 0
2875386 와.. ㅅㅂ 나님이 방금 뭘 본거지? 바퀴벌레 본것 같애.. ♥팬티스타킹냥덩♥갤로그로 이동합니다. 19:24 12 0
2875384 데스크탑 책상 아래 두시는분?? 질문왕(211.248) 19:21 11 0
2875383 중국애들이 존나 공유를 잘함. 한국애들은 공유 절대 안함. [2] ㅆㅇㅆ(124.216) 19:20 57 0
2875381 냥덩이 목소리 최초 공개⭐+ ♥팬티스타킹냥덩♥갤로그로 이동합니다. 19:19 16 0
2875380 주6일 햐서 5일 갈깡 주 5일에서 6일 갈깡 ♥팬티스타킹냥덩♥갤로그로 이동합니다. 19:18 10 0
2875379 주말까지 달리니 ♥팬티스타킹냥덩♥갤로그로 이동합니다. 19:17 12 0
2875378 나님 주7일 달리다 퍼져 회복루틴 ♥팬티스타킹냥덩♥갤로그로 이동합니다. 19:16 9 0
2875375 한국에서 겜회사 안드가면 AAA 게임 비디오 게임 테크닉 안가르쳐주잖아 [6] ㅆㅇㅆ(124.216) 19:13 54 0
2875374 llm 믿지 마라. 꼴통이다 나르시갤로그로 이동합니다. 19:13 16 0
2875372 언리얼이 매 달마다 FAB으로 공짜로 풀고 그다음에 작년까지만해도 [3] ㅆㅇㅆ(124.216) 19:10 35 0
뉴스 '좀비딸' 조정석 "6세 딸 위해 목숨도 걸 수 있어요"[인터뷰] 디시트렌드 07.25
갤러리 내부 검색
제목+내용게시물 정렬 옵션

오른쪽 컨텐츠 영역

실시간 베스트

1/8

뉴스

디시미디어

디시이슈

1/2