POSIX 신호
POSIX 신호 개요
- POSIX 신호는 또 다른 형태의 프로세스 간 통신입니다.
- 프로그램 내에서 동시성을 생성하는 방법이기도 합니다.
- 이러한 이유로 POSIX 신호는 복잡하고 미묘합니다.
신호의 메시지로서의 역할
비동기 메시지
- POSIX 신호는 비동기 메시지입니다.
- 비동기란 신호 수신이 언제든지 발생할 수 있음을 의미합니다.
- 각 신호는 작은 정수로 된 번호를 가지고 있습니다.
- POSIX 신호는 그 외의 데이터를 포함하지 않습니다.
신호 유형
두 가지 기본 유형
실시간 신호는 더 복잡하며 데이터를 전달할 수 있습니다. 이번 강의에서는 신뢰할 수 있는 신호만 다룹니다.
비동기 수신
애플리케이션 관점
- 신호는 차단(block)되거나 무시(ignored)될 수 있습니다.
- 활성화된 신호는 두 프로세서 명령 사이에서 수신될 수 있습니다.
- 수신된 신호는 사용자 정의 함수인 신호 핸들러를 실행할 수 있습니다.
- 이로 인해 활성화된 신호와 프로그램 코드는 공유 또는 전역 데이터를 매우 신중하게 조작해야 합니다.
신호 목록
POSIX 정의 신호
- SIGHUP, 1 (터미널이 연결이 끊어졌을 때)
- SIGINT, 2 (Ctrl-C를 눌렀을 때)
- SIGKILL, 9 (캐치할 수 없으며, 프로세스를 종료)
- SIGSEGV, 11 (잘못된 메모리 접근 시)
- SIGCHLD, 17 (자식 프로세스가 종료될 때)
신호 핸들러
신호 핸들러 설치
- 프로세스는 신호 핸들러를 설치하여 신호를 수신하고자 하는 의사를 표시합니다.
- 각 신호에는 기본 핸들러가 있습니다:
- 신호 무시
- 프로세스 중지
- 프로세스 계속
- 프로세스 종료
- 프로세스 종료 및 코어 덤프
신호 핸들러 함수
사용자 정의 신호 핸들러
- 프로세스는 SIGKILL 및 SIGSTOP을 제외한 모든 신호에 대해 신호 핸들러를 설치할 수 있습니다.
- 신호 핸들러는 함수로, 신호가 수신될 때 호출됩니다.
- 신호 핸들러는 sighandler_t 타입입니다:
typedef void (*sighandler_t)(int);
핸들러 설치
신호 함수 사용
sighandler_t signal(int signum, sighandler_t handler);
- signal 함수는 신호 번호와 핸들러 함수를 받아, 해당 함수를 신호에 바인딩합니다.
- 이후 신호를 수신하면 바인딩된 함수가 호출됩니다.
- 이 함수는 이전 신호 핸들러를 반환합니다.
특수 핸들러
특수 신호 핸들러
- SIG_IGN: 신호 무시
- SIG_DFL: 기본 동작 복원
signal(SIGCHLD, SIG_DFL);
신호의 이식성 문제
signal() 함수의 이식성 문제
- 일부 시스템에서는 신호 수신 시 핸들러를 SIG_DFL로 재설정합니다.
- 일부 시스템에서는 신호가 signal()로 설정된 핸들러 동안 도착할 수 있습니다.
sigaction() 함수 사용
- 이러한 이유로, POSIX는 더 명확한 동작을 가진 sigaction() 함수를 제공합니다.
신호 수신
커널의 신호 전달
- 커널은 언제든지 신호를 전달할 수 있습니다.
- 신호를 수신하면 프로세스는:
- 현재 프로그램 카운터를 스택에 푸시
- 신호 핸들러로 점프
- 신호 핸들러 실행
- 저장된 PC를 팝하고 반환
신호 차단
신호 차단 방법
- 프로그램은 신호를 차단할 수 있습니다.
- 차단된 신호는 차단 해제 시 전달됩니다.
- 신호는 다음과 같이 차단될 수 있습니다:
- 핸들러가 현재 실행 중인 경우 암묵적으로 차단
- 프로그래머가 sigprocmask()를 사용하여 명시적으로 차단
sigprocmask() 함수 사용
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
int sigemptyset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
- sigprocmask()는 신호 집합으로 주어진 신호를 차단하거나 차단 해제합니다.
sigprocmask() 예시
sigset_t mask, oldmask;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
sigprocmask(SIG_BLOCK, &mask, &oldmask);
sigprocmask(SIG_SETMASK, &oldmask, NULL);
공유 데이터
신호와 공유 데이터
- 신호는 프로그램에 동시성을 도입합니다.
- 신호 핸들러는 예측할 수 없는 시점에 실행되므로, 공유 데이터를 액세스하는 것은 위험합니다.
- 데이터가 불완전한 상태에서 핸들러가 액세스하면 손상 또는 프로그램 오류가 발생할 수 있습니다.
신호의 동시성 문제
동시성 문제 예시
- 신호 핸들러가 실행되는 동안 데이터가 변경되면 데이터 손상 가능성이 있습니다.
신호 전송
신호 전송 방법
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
- 신호는 kill() 함수를 사용하여 프로세스에 전송됩니다.
- kill() 함수는 수신 프로세스를 실제로 종료시키지 않을 수도 있습니다.
- 프로세스는 일반적으로 자신 또는 동일한 사용자가 소유한 다른 프로세스만 종료할 수 있습니다.
요약
- 신호는 프로세스 간 통신입니다.
- 각 신호는 메시지입니다.
- 신호는 함수에 의해 처리됩니다.
- 신호 핸들러는 동시성을 도입합니다.
- 신호를 사용할 때는 공유 데이터를 신중하게 조작해야 합니다.