C 언어에서 잘못된 시그널 핸들링 예제와 해결법

C 언어는 강력한 저수준 프로그래밍 언어로, 시스템 프로그래밍 및 임베디드 소프트웨어에서 널리 사용됩니다. 그러나 시그널 핸들링을 잘못 구현하면 예기치 않은 동작이나 프로그램 충돌로 이어질 수 있습니다. 본 기사에서는 시그널 핸들링의 개념부터 일반적인 실수와 이를 해결하기 위한 방법까지 단계별로 살펴보겠습니다. 이를 통해 C 언어로 작성된 애플리케이션의 안정성과 신뢰성을 높이는 방법을 배울 수 있습니다.

목차

시그널 핸들링의 개념과 중요성


시그널은 운영 체제가 프로세스에 특정 이벤트를 알리기 위해 사용하는 메커니즘입니다. 예를 들어, SIGINT는 사용자가 키보드에서 Ctrl+C를 눌렀을 때 발생하며, SIGSEGV는 잘못된 메모리 접근 시 발생합니다.

시그널 핸들링의 정의


시그널 핸들링은 이러한 시그널을 받아서 적절히 처리하는 과정을 의미합니다. 이는 프로그램이 비정상적으로 종료되지 않도록 하거나 특정 작업을 수행하도록 설정할 수 있게 합니다.

시그널 핸들링의 중요성


시그널을 올바르게 처리하는 것은 다음과 같은 이유로 중요합니다:

  • 프로그램 안정성: 비정상 종료를 방지하고 프로그램이 예측 가능한 동작을 하도록 보장합니다.
  • 자원 관리: 파일, 메모리 등 자원을 해제하거나 저장 작업을 수행할 기회를 제공합니다.
  • 디버깅 및 로깅: 시그널 발생 원인을 추적하고 로그를 남길 수 있어 문제 해결이 용이해집니다.

시그널 핸들링의 기본 예시


다음은 SIGINT 시그널을 처리하는 간단한 예제입니다:

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

void handle_signal(int sig) {
    printf("Caught signal %d\n", sig);
}

int main() {
    signal(SIGINT, handle_signal);
    while (1) {
        printf("Running...\n");
        sleep(1);
    }
    return 0;
}

이 예제는 프로그램이 SIGINT를 받을 때 사용자 정의 함수 handle_signal을 호출하여 메시지를 출력하고 종료를 방지합니다.

시그널 핸들링은 프로그램의 안정성과 유지보수성을 높이는 데 중요한 역할을 합니다. 다음 섹션에서는 잘못된 구현 사례를 살펴보겠습니다.

잘못된 시그널 핸들링의 사례


시그널 핸들링을 잘못 구현하면 프로그램의 안정성을 해칠 수 있습니다. 일반적인 실수와 그로 인해 발생할 수 있는 문제를 이해하는 것이 중요합니다.

잘못된 시그널 핸들링 예제


아래 코드는 시그널 핸들링의 일반적인 오류를 보여줍니다:

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

void handle_signal(int sig) {
    printf("Signal %d caught\n", sig);
    sleep(5);  // 잘못된 구현: 시그널 핸들러 내에서 비재진입 함수 호출
}

int main() {
    signal(SIGINT, handle_signal);
    while (1) {
        printf("Program running...\n");
        sleep(1);
    }
    return 0;
}

이 코드는 SIGINT 시그널이 발생하면 handle_signal 함수가 호출되어 메시지를 출력합니다. 그러나 sleep 함수는 비재진입(non-reentrant) 함수로, 시그널 핸들러 내에서 호출하면 동작이 비정상적일 수 있습니다.

문제가 되는 동작

  1. 재진입 문제: 비재진입 함수는 동일한 함수가 다른 컨텍스트에서 동시에 호출되면 상태가 손상될 수 있습니다.
  2. 프로세스 중단: 시그널 핸들러에서 실행이 멈추면 다른 중요한 작업이 중단될 수 있습니다.
  3. 데드락: 멀티스레드 환경에서 특정 자원이 잠기면 교착 상태가 발생할 수 있습니다.

실제 문제 시나리오

  • 프로그램이 SIGINT를 받는 동안 핸들러가 처리되지 않거나, 예상하지 못한 상태로 인해 크래시가 발생합니다.
  • 핸들러가 긴 시간 동안 실행되면서 다른 작업이 처리되지 않아 성능이 저하됩니다.

다음 섹션에서는 이러한 문제를 해결하기 위한 올바른 시그널 핸들러 구현 방법을 소개합니다.

시그널 핸들러의 올바른 구현 방법


시그널 핸들러를 올바르게 구현하면 프로그램의 안정성과 신뢰성을 크게 향상시킬 수 있습니다. 이를 위해 반드시 지켜야 할 원칙과 유의사항을 소개합니다.

핵심 원칙

  1. 재진입 가능 함수만 사용
    시그널 핸들러 내에서는 재진입 가능한 함수만 호출해야 합니다. 예를 들어, write는 재진입 가능하지만, printf와 같은 표준 입출력 함수는 사용할 수 없습니다.
  2. 짧고 단순하게 작성
    핸들러는 최소한의 작업만 수행해야 하며, 복잡한 로직이나 긴 작업은 핸들러 외부로 분리해야 합니다.
  3. 시그널 재설정 피하기
    일부 시스템에서는 signal 함수로 시그널을 처리하면 기본 동작으로 복원됩니다. 이를 방지하기 위해 sigaction 함수를 사용하는 것이 좋습니다.

올바른 시그널 핸들러 예제


다음은 SIGINT를 처리하는 올바른 예제입니다:

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

volatile sig_atomic_t signal_received = 0;

void handle_signal(int sig) {
    signal_received = sig;  // 안전하게 플래그 설정
}

int main() {
    struct sigaction sa;
    sa.sa_handler = handle_signal;
    sa.sa_flags = 0;  // 추가 플래그 없음
    sigemptyset(&sa.sa_mask);
    sigaction(SIGINT, &sa, NULL);  // sigaction으로 핸들러 등록

    while (1) {
        if (signal_received) {
            printf("Caught signal %d\n", signal_received);
            signal_received = 0;
        }
        printf("Program running...\n");
        sleep(1);
    }
    return 0;
}

코드 설명

  • volatile sig_atomic_t를 사용하여 시그널 핸들러와 메인 루프 간에 안전한 데이터 교환을 보장합니다.
  • sigaction 함수는 시그널 핸들러를 안전하고 명시적으로 등록하는 데 사용됩니다.
  • 핸들러는 단순히 플래그를 설정하는 역할만 하고, 실제 작업은 메인 루프에서 처리합니다.

유의사항

  1. 글로벌 변수 사용 최소화: 플래그처럼 간단한 상태 정보만 전달합니다.
  2. 긴 작업 분리: 긴 작업은 메인 루프나 별도 함수로 위임합니다.
  3. 시그널 마스크 활용: 복수 시그널이 처리 중 겹치는 것을 방지하려면 마스크를 적절히 설정합니다.

이러한 원칙을 따르면 프로그램이 예상치 못한 상황에서도 안정적으로 동작할 수 있습니다. 다음 섹션에서는 블록킹 시그널 및 다중 스레드 환경에서의 문제를 다루겠습니다.

블록킹 시그널과 다중 스레드 환경에서의 문제


멀티스레드 환경에서 시그널 처리는 복잡한 문제를 동반할 수 있습니다. 특히 블록킹 시그널(blocking signal)과 시그널 전달 방식은 프로그램의 안정성에 중요한 영향을 미칩니다. 이 섹션에서는 다중 스레드 환경에서의 주요 문제와 해결 방법을 살펴봅니다.

시그널과 스레드 간의 관계


운영 체제는 다음과 같은 규칙에 따라 시그널을 처리합니다:

  • 프로세스 전체에 영향을 미치는 시그널(SIGINT, SIGHUP 등)은 모든 스레드에 적용됩니다.
  • 특정 스레드에 전달될 수 있는 시그널은 해당 스레드의 컨텍스트에서 처리됩니다.
  • 다중 스레드 환경에서는 한 스레드가 시그널을 처리 중일 때 다른 스레드의 작업이 영향을 받을 수 있습니다.

블록킹 시그널 문제


멀티스레드 프로그램에서는 일부 스레드가 특정 시그널을 처리하는 동안 다른 스레드가 동일한 시그널을 처리하려 할 수 있습니다. 이러한 상황은 다음과 같은 문제를 초래할 수 있습니다:

  1. 경쟁 상태(race condition): 두 스레드가 동일한 시그널을 처리하려고 시도하면 충돌이 발생할 수 있습니다.
  2. 중복 처리: 동일한 시그널이 여러 번 처리되면서 예기치 않은 결과를 초래할 수 있습니다.
  3. 데드락: 시그널 처리 중 자원 접근이 교착 상태에 빠질 위험이 있습니다.

문제 해결 방법


다중 스레드 환경에서 안정적인 시그널 처리를 위해 다음과 같은 전략을 사용할 수 있습니다:

1. 시그널 마스크 사용


pthread_sigmask를 사용하여 특정 스레드에서 처리할 시그널을 명시적으로 지정할 수 있습니다.

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

void* thread_function(void* arg) {
    sigset_t set;
    int sig;

    sigemptyset(&set);
    sigaddset(&set, SIGINT);
    pthread_sigmask(SIG_BLOCK, &set, NULL);

    while (1) {
        sigwait(&set, &sig);
        printf("Thread %ld caught signal %d\n", pthread_self(), sig);
    }
    return NULL;
}

int main() {
    pthread_t thread;
    pthread_create(&thread, NULL, thread_function, NULL);

    while (1) {
        printf("Main thread running...\n");
        sleep(1);
    }
    return 0;
}

2. 전용 시그널 처리 스레드


하나의 스레드만 시그널을 처리하도록 지정하여 경쟁 상태를 방지합니다. 위의 예제는 이 방법을 구현한 코드입니다.

3. 동기화 객체 활용


시그널 핸들링과 다른 스레드 간의 데이터 교환 시 mutex와 같은 동기화 객체를 사용해 안전성을 보장합니다.

유의사항

  • 멀티스레드 환경에서는 반드시 pthread_sigmask를 사용하여 각 스레드의 시그널 마스크를 명확히 설정합니다.
  • 시그널 처리를 위해 설계되지 않은 스레드에서 시그널이 처리되지 않도록 합니다.
  • 전역적으로 공유되는 자원에 대한 접근은 최소화하거나 동기화를 철저히 합니다.

이러한 방법을 통해 멀티스레드 환경에서 발생할 수 있는 시그널 핸들링 문제를 예방하고 프로그램의 안정성을 높일 수 있습니다. 다음 섹션에서는 sigactionsignal 함수의 차이점을 살펴보겠습니다.

sigaction과 signal 함수의 차이


C 언어에서는 시그널 핸들러를 설정하기 위해 signal 함수와 sigaction 함수를 사용할 수 있습니다. 두 함수는 유사한 목적을 가지지만, 기능과 사용 방법에서 중요한 차이가 있습니다.

signal 함수


signal 함수는 간단한 시그널 핸들링 설정을 제공합니다.

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

void handle_signal(int sig) {
    printf("Caught signal %d\n", sig);
}

int main() {
    signal(SIGINT, handle_signal);  // 시그널 핸들러 설정
    while (1) {
        printf("Running...\n");
        sleep(1);
    }
    return 0;
}

특징

  1. 간단한 사용: signal은 기본적인 시그널 핸들링을 설정하기 위해 간단한 인터페이스를 제공합니다.
  2. 비표준 동작 가능성: 시스템에 따라 signal 함수의 동작이 일관되지 않을 수 있습니다. 예를 들어, 일부 구현에서는 시그널 처리 후 핸들러가 기본 상태로 복원됩니다.
  3. 제한된 기능: 시그널 마스크 설정과 같은 고급 기능은 지원하지 않습니다.

sigaction 함수


sigaction 함수는 더 강력하고 유연한 시그널 핸들링을 제공합니다.

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

void handle_signal(int sig) {
    printf("Caught signal %d\n", sig);
}

int main() {
    struct sigaction sa;
    sa.sa_handler = handle_signal;
    sa.sa_flags = 0;  // 추가 옵션 없음
    sigemptyset(&sa.sa_mask);

    sigaction(SIGINT, &sa, NULL);  // 시그널 핸들러 설정
    while (1) {
        printf("Running...\n");
        sleep(1);
    }
    return 0;
}

특징

  1. 표준화된 동작: sigaction은 POSIX 표준에 따라 구현되어 동작이 일관적입니다.
  2. 고급 기능 지원: 시그널 마스크 설정, 핸들러 동작 방식 세부 조정 등이 가능합니다.
  3. 재설정 방지: 기본적으로 핸들러가 시그널 처리 후에도 유지됩니다.

차이점 비교

특징signalsigaction
사용 편의성간단함약간 복잡함
표준 준수비표준 동작 가능POSIX 표준 준수
핸들러 유지 여부시스템 종속적기본적으로 유지됨
고급 기능 지원제한적시그널 마스크, 플래그 설정 가능

사용 추천

  • 간단한 시그널 처리만 필요하면 signal을 사용할 수 있지만, 동작이 시스템에 따라 다를 수 있다는 점에 유의해야 합니다.
  • 복잡한 프로그램이나 멀티스레드 환경에서는 sigaction을 사용하는 것이 더 안전하고 권장됩니다.

다음 섹션에서는 재진입 가능 함수와 비재진입 가능 함수에 대해 자세히 살펴보겠습니다.

재진입 가능 함수와 비재진입 가능 함수


시그널 핸들러에서 호출되는 함수는 반드시 재진입 가능(reentrant)해야 합니다. 그렇지 않으면 경쟁 상태(race condition)나 예기치 않은 동작이 발생할 수 있습니다. 이 섹션에서는 재진입 가능 함수와 비재진입 가능 함수의 차이를 설명하고, 안전한 함수 사용 방법을 다룹니다.

재진입 가능 함수란?


재진입 가능 함수는 여러 실행 컨텍스트에서 동시에 호출되더라도 안전하게 작동할 수 있는 함수입니다. 이러한 함수는 다음과 같은 특징을 가집니다:

  1. 글로벌 또는 정적 변수 사용 금지: 함수 내부에서 상태를 저장하지 않으므로 경쟁 상태가 발생하지 않습니다.
  2. 스레드 안전성: 멀티스레드 환경에서도 동시 실행이 가능합니다.
  3. 단순성과 독립성: 호출 시 외부 환경에 의존하지 않습니다.

예: write, _exit, signal

비재진입 가능 함수란?


비재진입 가능 함수는 한 컨텍스트에서 호출 중일 때 다른 컨텍스트에서 호출되면 문제가 발생할 수 있는 함수입니다.

  • 예: malloc, free, printf, strtok

이 함수들은 내부적으로 정적 변수나 글로벌 변수를 사용하거나, 스레드 안전하지 않은 동작을 포함합니다.

잘못된 시그널 핸들러의 예


다음은 비재진입 가능 함수를 사용한 잘못된 예입니다:

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

void handle_signal(int sig) {
    printf("Signal %d caught\n", sig);  // 비재진입 가능 함수 사용
}

int main() {
    signal(SIGINT, handle_signal);
    while (1) {
        printf("Running...\n");
        sleep(1);
    }
    return 0;
}

이 코드에서 printf 함수는 내부적으로 버퍼를 사용하므로, 시그널 핸들러에서 호출되면 경쟁 상태가 발생할 수 있습니다.

올바른 시그널 핸들러 작성


시그널 핸들러는 다음과 같이 재진입 가능 함수만 호출해야 합니다:

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

void handle_signal(int sig) {
    const char msg[] = "Signal caught\n";
    write(STDOUT_FILENO, msg, sizeof(msg) - 1);  // 재진입 가능 함수 사용
}

int main() {
    signal(SIGINT, handle_signal);
    while (1) {
        pause();  // 시그널 대기
    }
    return 0;
}

재진입 가능 함수의 확인


POSIX 표준은 재진입 가능 함수 목록을 제공합니다. 시그널 핸들러에서 호출 가능한 함수는 POSIX.1 표준의 안전 함수 목록에 포함되어야 합니다. 주요 재진입 가능 함수는 다음과 같습니다:

  • _exit
  • write
  • read
  • sigaction

핸들러 내에서 긴 작업 처리 방법


핸들러는 짧고 단순하게 유지되어야 합니다. 긴 작업은 핸들러 내에서 플래그만 설정하고, 메인 루프에서 처리하는 방식으로 구현해야 합니다.

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

volatile sig_atomic_t signal_received = 0;

void handle_signal(int sig) {
    signal_received = 1;  // 플래그 설정
}

int main() {
    signal(SIGINT, handle_signal);
    while (1) {
        if (signal_received) {
            printf("Signal processed in main loop\n");
            signal_received = 0;
        }
        sleep(1);
    }
    return 0;
}

요약

  • 시그널 핸들러에서는 반드시 재진입 가능 함수만 호출해야 합니다.
  • 비재진입 가능 함수는 핸들러 내에서 사용하지 말고, 작업을 메인 루프로 위임하십시오.
  • POSIX 표준에 명시된 안전 함수 목록을 참고하여 올바른 함수를 사용하세요.

다음 섹션에서는 시그널 핸들링 문제를 디버깅하는 방법을 다루겠습니다.

시그널 핸들링 문제의 디버깅 방법


시그널 핸들링 문제는 디버깅이 까다로울 수 있습니다. 잘못된 시그널 핸들러 구현은 프로그램이 비정상 종료하거나, 예기치 않은 동작을 하게 만듭니다. 이 섹션에서는 시그널 핸들링 문제를 효과적으로 진단하고 해결하는 디버깅 기법을 살펴봅니다.

gdb를 사용한 시그널 디버깅


GNU Debugger(gdb)는 시그널 핸들링 문제를 디버깅하는 데 강력한 도구입니다.

1. 시그널 처리 중단


gdb는 기본적으로 시그널을 가로채거나 무시합니다. 특정 시그널을 디버깅하려면 다음 명령을 사용합니다:

(gdb) handle SIGINT stop  # SIGINT를 중단점으로 설정
(gdb) run

이 명령은 프로그램이 SIGINT를 받았을 때 중단시켜, 문제가 발생한 지점을 분석할 수 있습니다.

2. 시그널 전달 무시


필요에 따라 특정 시그널을 무시할 수도 있습니다:

(gdb) handle SIGPIPE nostop noprint pass  # SIGPIPE 시그널 무시

이 설정은 디버깅 중 시그널이 프로그램의 흐름을 방해하지 않도록 합니다.

3. 시그널 발생 위치 추적


시그널이 발생한 코드 위치를 추적하려면 다음 명령을 사용합니다:

(gdb) backtrace

이 명령은 호출 스택을 출력하여 시그널 발생의 원인을 분석하는 데 도움을 줍니다.

로깅을 활용한 디버깅


프로그램에 로깅을 추가하여 시그널 발생 시 상태를 기록할 수 있습니다.

로깅 코드 예제

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

void handle_signal(int sig) {
    FILE *log = fopen("signal.log", "a");
    if (log) {
        fprintf(log, "Caught signal %d\n", sig);
        fclose(log);
    }
}

int main() {
    signal(SIGINT, handle_signal);
    while (1) {
        printf("Running...\n");
        sleep(1);
    }
    return 0;
}

이 코드는 시그널 발생 시 로그 파일에 기록하므로, 디버깅 과정에서 문제의 원인을 분석하는 데 유용합니다.

시그널과 스택 덤프


시그널 처리 중 프로그램이 충돌하는 경우 스택 덤프를 생성하여 디버깅에 활용할 수 있습니다.

스택 덤프를 생성하는 코드

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

void handle_signal(int sig) {
    void *array[10];
    size_t size;

    size = backtrace(array, 10);
    fprintf(stderr, "Error: signal %d:\n", sig);
    backtrace_symbols_fd(array, size, STDERR_FILENO);
    exit(1);
}

int main() {
    signal(SIGSEGV, handle_signal);
    *(int *)0 = 0;  // 강제로 SIGSEGV 발생
    return 0;
}

이 코드는 SIGSEGV 시 스택 덤프를 생성하여 문제의 원인을 분석할 수 있게 합니다.

디버깅 시 유의사항

  1. 핸들러 최소화: 핸들러 내에서 복잡한 작업은 피하고, 가능한 한 단순하게 유지합니다.
  2. 디버깅 도구 조합 사용: gdb, 로깅, 스택 덤프 등을 함께 활용하여 문제를 종합적으로 분석합니다.
  3. 멀티스레드 환경 고려: 다중 스레드 프로그램에서는 특정 스레드에서만 시그널이 처리되도록 설계해야 합니다.

시그널 핸들링 문제를 효과적으로 디버깅하면 프로그램의 안정성과 신뢰성을 높일 수 있습니다. 다음 섹션에서는 코드 예제와 연습 문제를 제공하겠습니다.

코드 예제와 연습 문제


시그널 핸들링을 더 깊이 이해하기 위해 잘못된 코드와 이를 수정하는 연습 문제를 살펴보겠습니다.

연습 문제: 잘못된 시그널 핸들링 수정

문제 설명
아래 코드는 시그널 핸들링에서 자주 발생하는 오류를 포함하고 있습니다. 코드를 분석하고 올바르게 수정하세요.

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

void handle_signal(int sig) {
    printf("Signal %d caught\n", sig);  // 비재진입 가능 함수 사용
    sleep(2);  // 시그널 핸들러 내에서 비재진입 함수 호출
}

int main() {
    signal(SIGINT, handle_signal);  // signal 함수 사용
    while (1) {
        printf("Program running...\n");
        sleep(1);
    }
    return 0;
}

문제점

  1. 시그널 핸들러 내에서 printfsleep 같은 비재진입 가능 함수를 사용했습니다.
  2. signal 함수를 사용하여 핸들러를 등록했으므로, 시스템에 따라 기본 동작으로 복원될 가능성이 있습니다.

수정된 코드 예제

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

volatile sig_atomic_t signal_received = 0;

void handle_signal(int sig) {
    signal_received = sig;  // 안전하게 플래그 설정
}

int main() {
    struct sigaction sa;
    sa.sa_handler = handle_signal;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGINT, &sa, NULL);  // sigaction 함수 사용

    while (1) {
        if (signal_received) {
            write(STDOUT_FILENO, "Signal caught\n", 14);  // 재진입 가능 함수 사용
            signal_received = 0;
        }
        printf("Program running...\n");
        sleep(1);
    }
    return 0;
}

연습 문제: 시그널과 스레드

문제 설명
멀티스레드 환경에서 특정 스레드만 시그널을 처리하도록 설계하려면 어떻게 해야 할까요? 다음 코드를 수정하여 올바른 시그널 처리 방식을 구현하세요.

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

void* thread_function(void* arg) {
    while (1) {
        printf("Thread running...\n");
        sleep(1);
    }
    return NULL;
}

void handle_signal(int sig) {
    printf("Signal %d caught in main thread\n", sig);
}

int main() {
    pthread_t thread;
    pthread_create(&thread, NULL, thread_function, NULL);

    signal(SIGINT, handle_signal);

    while (1) {
        pause();  // 시그널 대기
    }
    return 0;
}

문제점

  • 모든 스레드가 SIGINT를 받을 가능성이 있습니다.
  • 특정 스레드만 시그널을 처리하도록 설계되지 않았습니다.

수정된 코드 예제

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

void* thread_function(void* arg) {
    while (1) {
        printf("Thread running...\n");
        sleep(1);
    }
    return NULL;
}

void handle_signal(int sig) {
    printf("Signal %d caught in dedicated thread\n", sig);
}

void* signal_handler_thread(void* arg) {
    sigset_t set;
    int sig;

    sigemptyset(&set);
    sigaddset(&set, SIGINT);
    pthread_sigmask(SIG_BLOCK, &set, NULL);

    while (1) {
        sigwait(&set, &sig);
        handle_signal(sig);
    }
    return NULL;
}

int main() {
    pthread_t worker_thread, signal_thread;
    pthread_create(&worker_thread, NULL, thread_function, NULL);
    pthread_create(&signal_thread, NULL, signal_handler_thread, NULL);

    pthread_join(worker_thread, NULL);
    pthread_join(signal_thread, NULL);

    return 0;
}

연습 문제 요약


이 두 가지 연습 문제는 다음을 다룹니다:

  1. 비재진입 가능 함수 사용을 피하고, 올바른 핸들러 구조를 설계합니다.
  2. 멀티스레드 환경에서 특정 스레드에 시그널 처리를 제한하는 방법을 학습합니다.

다음 섹션에서는 전체 내용을 간략히 요약하겠습니다.

요약


본 기사에서는 C 언어에서의 시그널 핸들링 개념, 잘못된 구현 사례, 올바른 구현 방법, 멀티스레드 환경에서의 문제 해결, 그리고 디버깅 기법을 다뤘습니다. 특히, sigaction과 같은 안전한 함수 사용과 재진입 가능 함수의 중요성을 강조하며, 실용적인 코드 예제와 연습 문제를 통해 학습을 심화할 수 있도록 구성했습니다. 올바른 시그널 핸들링 구현은 프로그램의 안정성과 신뢰성을 보장하는 핵심 요소입니다.

목차