cs

[크래프톤 정글] 시그널(CSAP 8.5장)

하루이2222 2024. 10. 12. 12:47

8.5 시그널(Signals) - 상세 설명

시그널은 소프트웨어 기반의 예외적 제어 흐름의 한 형태로, 프로세스에 특정 이벤트가 발생했음을 알리는 간단한 메시지이다. 시그널은 운영체제가 외부 이벤트나 오류를 프로세스에 통지하는 방법으로 사용되며, 프로세스는 시그널을 받으면 이에 대한 적절한 처리를 수행할 수 있다.

시그널의 기본 개념

  1. 시그널 전송: 시그널은 커널이나 다른 프로세스에 의해 생성되어, 대상 프로세스에 전송된다.
  2. 시그널 대기 및 처리: 시그널이 프로세스에 도착하면, 커널은 해당 프로세스에 시그널을 전달하고, 해당 시그널이 처리되기를 기다린다.
  3. 시그널 처리기: 시그널을 처리하기 위해, 프로세스는 시그널을 무시하거나, 기본 동작(프로세스 종료 등)을 수행하거나, 또는 시그널 핸들러를 통해 시그널을 처리할 수 있다.

시그널의 주요 개념

  • 시그널 전달: 시그널이 프로세스에 전달되면, 커널은 프로세스의 상태를 변경하여 해당 시그널이 대기 중임을 표시한다. 이는 프로세스가 차단된 상태라면 그 즉시 처리되지 않으며, 실행 가능할 때 처리된다.
  • 시그널 핸들링: 시그널은 다양한 방식으로 처리할 수 있다. 프로세스는 시그널을 무시하거나 기본적으로 정의된 동작을 수행하거나, 직접 작성한 시그널 핸들러로 처리할 수 있다.

주요 시그널과 그 기능

리눅스는 다양한 시그널을 지원하며, 각 시그널은 특정 이벤트에 대응한다. 주요 시그널은 다음과 같다:

  • SIGINT (2): 사용자 인터럽트(Ctrl+C)로 프로그램을 중단하는 시그널.
  • SIGKILL (9): 프로세스를 강제로 종료하는 시그널. 이 시그널은 잡거나 무시할 수 없다.
  • SIGSEGV (11): 잘못된 메모리 접근 시 발생하는 시그널(세그멘테이션 폴트).
  • SIGALRM (14): alarm 함수로 지정된 시간이 경과했을 때 발생하는 타이머 시그널.
  • SIGTERM (15): 소프트웨어 종료 요청 시 전송되는 시그널로, SIGKILL과 달리 처리기를 통해 잡을 수 있으며, 프로세스는 이를 이용해 안전하게 종료할 수 있다.

시그널 처리 흐름

  1. 시그널 발생: 커널 또는 다른 프로세스에서 시그널이 발생하면, 해당 시그널은 지정된 프로세스에 전달된다.
  2. 시그널 대기: 시그널이 전달되면, 프로세스는 이 시그널을 대기 상태로 두고 처리할 준비가 된다. 시그널이 차단된 경우에는 차단 해제 시까지 대기 상태에 머문다.
  3. 시그널 처리: 차단 해제 후 시그널 핸들러를 통해 시그널이 처리된다. 시그널 처리 중 새로운 시그널이 발생할 수 있으며, 이 경우 커널은 새로운 시그널을 관리한다.

시그널 처리 시 주의 사항

  1. 비동기적 특성: 시그널은 비동기적으로 발생하므로, 프로그램의 흐름과 상관없이 도착할 수 있다. 따라서 시그널이 도착할 때마다 안전하게 처리하기 위해선 시그널 핸들러의 동작을 신중히 설계해야 한다.

  2. 시그널 블로킹: 시그널을 처리하는 도중에는 특정 시그널을 일시적으로 차단할 수 있다. 이를 통해 중요한 코드 영역에서 시그널에 의해 흐름이 방해되지 않도록 할 수 있다.

  3. 시그널 동기화 문제: 시그널을 처리하는 중에 공유된 전역 변수에 접근할 때 동기화 문제가 발생할 수 있다. 이러한 문제를 방지하기 위해 시그널 블로킹비동기 신호 안전 함수(async-signal-safe functions)를 사용하는 것이 중요하다.

시그널 핸들러 작성

시그널 핸들러는 시그널을 받았을 때 실행되는 함수이다. 핸들러는 특정 시그널에 대해 커스터마이징할 수 있으며, C 언어에서는 signal() 함수를 이용해 시그널 핸들러를 설정할 수 있다.

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

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

int main() {
    // SIGINT에 대한 시그널 핸들러 설정
    signal(SIGINT, handler);

    // 무한 루프
    while (1) {
        printf("Running...\n");
        sleep(1);
    }

    return 0;
}

위 코드에서 SIGINT(Ctrl+C) 시그널이 발생하면 handler 함수가 호출되어 시그널 번호를 출력한다. 시그널 핸들러는 매우 간단한 동작을 하지만, 비동기적 실행으로 인한 동기화 문제를 처리할 때는 보다 신중한 설계가 필요하다.

동기화 문제와 해결

프로그램이 시그널을 처리할 때 중요한 문제 중 하나는 동기화이다. 시그널 핸들러는 프로그램의 다른 부분과 동시에 실행될 수 있으므로, 공유 자원을 보호해야 한다. 이를 위해 시그널 블로킹과 같은 기법을 사용하며, 공유 자원에 접근할 때는 시그널이 처리되지 않도록 해야 한다.

시그널 처리 예시

  1. SIGINT 처리: Ctrl+C로 사용자 인터럽트를 받아 프로그램을 안전하게 종료하거나 중단할 수 있다.
  2. SIGALRM 처리: 타이머 기반의 이벤트를 처리하기 위해 alarm() 함수를 이용해 설정된 시간이 지나면 핸들러를 실행할 수 있다.
  3. SIGCHLD 처리: 부모 프로세스가 자식 프로세스의 종료를 감지하고 그에 따라 적절한 자식 프로세스의 자원을 회수하는 데 사용된다.

이와 같이 시그널은 운영체제와 프로그램 간의 소통을 위한 중요한 도구이며, 프로세스가 외부 이벤트나 오류에 적절하게 대응할 수 있도록 도와준다.