C 시그널 처리
1. 개요
C 시그널 처리는 C 표준 라이브러리에서 제공하는 기능으로, 프로그램이 예외적인 상황이나 외부적인 요청에 대응할 수 있도록 한다. `signal.h` 헤더 파일에 정의된 6개의 표준 시그널(`SIGABRT`, `SIGFPE`, `SIGILL`, `SIGINT`, `SIGSEGV`, `SIGTERM`)을 포함하여, 구현에 따라 추가적인 시그널도 정의될 수 있다. 시그널은 `raise()` 함수로 발생시키거나 `kill()` 함수로 특정 프로세스에 전송할 수 있으며, 시그널이 발생했을 때 실행될 함수인 시그널 처리기를 설정하여 처리할 수 있다. 시그널 처리기는 `signal()` 또는 `sigaction()` 함수를 통해 설정하며, `SIGKILL`과 `SIGSTOP` 시그널은 잡거나 무시할 수 없다.
| 유형 | C 표준 라이브러리 |
|---|---|
| 헤더 | 시그널.h (signal.h) |
| 도입 | ANSI C (C89) |
| 시그널 | signal raise |
|---|---|
| 설명 | C 언어에서 시그널을 처리하기 위한 함수들을 제공 |
2. 표준 시그널
C 표준에서는 `signal.h` 헤더 파일(`C++`에서는 `csignal`)에 정의된 6개의 표준 시그널을 제공한다. 구현에 따라서는 `signal.h` 헤더에 추가적인 시그널이 정의될 수 있다. 예를 들어 유닉스 계열 운영 체제는 15개 이상의 추가적인 시그널들을 정의한다.
2.1. 종류
C 표준은 6개의 시그널만을 정의하며, 이들은 모두 `signal.h` 헤더 (`csignal` - C++에서)에 정의되어 있다.
* `SIGABRT` - "abort", 비정상적 종료.
* `SIGFPE` - 부동소수점.
* `SIGILL` - "illegal", 유효하지 않은 명령어.
* `SIGINT` - "interrupt", 프로그램에 보내진 상호적인 요청.
* `SIGSEGV` - "세그멘테이션 오류", 유효하지 않은 메모리 접근.
* `SIGTERM` - "terminate", 프로그램에 보내진 종료 요청.
유닉스 계열 운영 체제와 같이 구현에 따라서 `signal.h` 헤더에 추가적인 시그널들이 정의되어 있을 수 있다. 예를 들어 유닉스 계열 운영체제는 15개 이상의 추가적인 시그널들을 정의한다.
2.2. 추가 시그널
유닉스 계열 운영 체제(예: 리눅스)는 15개 이상의 추가적인 시그널을 정의한다. 디버깅 목적으로 `SIGTRAP` 시그널을 사용할 수 있다. (플랫폼에 따라 다름)
3. 시그널 처리
시그널은 `raise()` 또는 `kill()` 시스템 호출을 통해 생성될 수 있다. `raise()`는 현재 프로세스에, `kill()`은 특정 프로세스에 시그널을 전송한다.
SIGKILL과 SIGSTOP을 제외한 모든 시그널에 대해 사용자 정의 시그널 처리기(핸들러)를 설정할 수 있다. 시그널 처리기는 해당 시그널이 발생했을 때 호출되는 함수이다. 시그널 처리기는 프로그램 실행을 일시 중단시키고, 처리기가 반환되거나 `longjmp()`를 호출할 때까지 대기한다. 최대한의 호환성을 위해 비동기적 신호 처리기는 다음 작업만 수행해야 한다.
* `signal()` 함수를 성공적으로 호출한다.
* `sig_atomic_t` volatile 변수 타입 객체에 값을 할당한다.
* 호출자에게 제어를 반환한다.
프로그램 내부 오류로 시그널이 발생한 경우, 시그널 처리기는 `abort()`, `exit()`, `longjmp()`를 호출하여 종료할 수 있다.
3.1. 시그널 핸들러
시그널 핸들러는 특정 시그널이 발생했을 때 호출되는 함수이다. 시그널 핸들러는 프로그램의 실행을 일시 중단시키고, 핸들러가 반환되거나 `longjmp()`를 호출할 때까지 대기한다.
시그널 핸들러는 `signal()` 또는 `sigaction()` 함수를 사용하여 설정할 수 있다. 다만, `signal()` 함수는 역사적으로 여러 번 변경되어 사용이 권장되지 않으며, 이식성을 위해서는 SIG_DFL 또는 SIG_IGN으로 시그널 처분을 설정하는 경우에만 사용해야 한다. SIGKILL과 SIGSTOP 두 가지 시그널을 제외한 모든 시그널에 대해 핸들러를 지정할 수 있다. (이 두 시그널은 잡거나, 차단하거나, 무시할 수 없다.)
프로그램 내부 오류로 인해 시그널이 발생한 경우, 시그널 핸들러는 `abort()`, `exit()`, `longjmp()` 함수를 호출하여 프로그램을 종료할 수 있다.
4. 관련 함수
| 함수 | 설명 |
|---|---|
| raise | 시그널을 인위적으로 발생시킨다. |
| signal | 프로그램이 특정한 시그널을 받았을 때의 행위를 설정한다. |
| kill | 지정된 프로세스에 신호를 인위적으로 보낸다. |
5. 사용 예시
c
#include
#include
#include
static void catch_function(int signo) {
puts("대화형 주의 신호가 잡혔습니다.");
}
int main(void) {
if (signal(SIGINT, catch_function) == SIG_ERR) {
fputs("신호 처리기를 설정하는 동안 오류가 발생했습니다.\n", stderr);
return EXIT_FAILURE;
}
puts("대화형 주의 신호를 발생시킵니다.");
if (raise(SIGINT) != 0) {
fputs("신호를 발생시키는 동안 오류가 발생했습니다.\n", stderr);
return EXIT_FAILURE;
}
puts("종료합니다.");
return EXIT_SUCCESS;
// 신호를 발생시킨 후 종료
}
```
```c
#include
#include
#include
volatile sig_atomic_t status = 0;
static void catch_function(int signo) {
status = signo;
}
int main(void) {
// 위의 함수를 SIGINT 신호의 신호 처리기로 설정합니다.
if (signal(SIGINT, catch_function) == SIG_ERR) {
fputs("신호 처리기를 설정하는 동안 오류가 발생했습니다.\n", stderr);
return EXIT_FAILURE;
}
puts("대화형 주의 신호를 발생시킵니다.");
if (raise(SIGINT)) {
fputs("신호를 발생시키는 동안 오류가 발생했습니다.\n", stderr);
return EXIT_FAILURE;
}
if (status == SIGINT) puts("대화형 주의 신호가 잡혔습니다.");
puts("종료합니다.");
return EXIT_SUCCESS;
// 신호를 발생시킨 후 종료
}