신호(Signal)는 운영체제가 프로세스에 전달하는 일종의 인터럽트(interrupt)로, 프로그램을 비정상적으로 종료시킬 수 있습니다. UNIX, LINUX, Mac OS X 또는 Windows 환경에서 Ctrl+C를 누르면 이러한 인터럽트를 직접 발생시킬 수 있습니다.
모든 신호가 프로그램에서 잡아낼(catch) 수 있는 것은 아니지만, 아래 표에 정리된 신호들은 프로그램 내에서 감지하여 상황에 맞는 적절한 조치를 취할 수 있습니다. 이러한 신호들은 C++의 <csignal> 헤더 파일에 정의되어 있습니다.
주요 신호 종류
| 신호 | 설명 |
|---|---|
| SIGABRT | 프로그램의 비정상 종료. abort() 함수 호출 시 발생합니다. |
| SIGFPE | 잘못된 산술 연산. 0으로 나누기나 오버플로우(overflow)를 초래하는 연산 등이 해당됩니다. |
| SIGILL | 잘못된 명령어(illegal instruction)의 감지. |
| SIGINT | 대화형 인터럽트 신호 수신. 주로 Ctrl+C 입력으로 발생합니다. |
| SIGSEGV | 허용되지 않은 메모리 영역에 대한 잘못된 접근(segmentation fault). |
| SIGTERM | 프로그램에 전송된 종료(termination) 요청. |
signal() 함수
C++의 신호 처리 라이브러리는 예상치 못한 이벤트를 포착(trap)하기 위해 signal() 함수를 제공합니다. 함수의 원형은 다음과 같습니다.
void (*signal (int sig, void (*func)(int)))(int);
간단히 설명하면, 이 함수는 두 개의 인자를 받습니다.
- 첫 번째 인자: 신호 번호를 나타내는 정수 값
- 두 번째 인자: 신호 처리(signal-handling) 함수에 대한 포인터
즉, 프로그램에서 특정 신호를 처리하고 싶다면 signal() 함수를 사용해 해당 신호를 미리 등록하고, 그 신호와 연결될 핸들러(handler) 함수를 지정해야 합니다.
signal() 함수 예제
다음은 signal() 함수를 사용해 SIGINT 신호를 포착하는 간단한 C++ 프로그램입니다.
#include <iostream>
#include <csignal>
using namespace std;
void signalHandler( int signum ) {
cout << "Interrupt signal (" << signum << ") received.\n";
// 여기서 리소스 정리 및 마무리 작업을 수행합니다.
// 프로그램 종료
exit(signum);
}
int main () {
// SIGINT 신호와 신호 핸들러 등록
signal(SIGINT, signalHandler);
while(1) {
cout << "Going to sleep...." << endl;
sleep(1);
}
return 0;
}실행 결과
Going to sleep.... Going to sleep.... Going to sleep....
실행 중 Ctrl + C를 누르면 다음과 같이 출력됩니다.
Ctrl + C 입력 후 결과
Going to sleep.... Going to sleep.... Going to sleep.... Interrupt signal (2) received.
Ctrl+C가 입력되면 운영체제가 SIGINT 신호(번호 2)를 프로세스에 전달하고, 미리 등록해 둔 signalHandler() 함수가 호출되어 메시지를 출력한 뒤 프로그램이 종료되는 것을 확인할 수 있습니다.
raise() 함수
운영체제뿐만 아니라 프로그램 내부에서도 신호를 직접 생성할 수 있으며, 이때 raise() 함수를 사용합니다. 이 함수는 정수형 신호 번호를 인자로 받으며, 원형은 다음과 같습니다.
int raise (signal sig);
여기서 sig는 전송할 신호 번호로, SIGINT, SIGABRT, SIGFPE, SIGILL, SIGSEGV, SIGTERM, SIGHUP 중 하나를 지정할 수 있습니다.
raise() 함수 예제
다음은 raise() 함수를 사용해 프로그램 내부에서 직접 신호를 발생시키는 예제입니다.
#include <iostream>
#include <csignal>
using namespace std;
void signalHandler( int signum ) {
cout << "Interrupt signal (" << signum << ") received.\n";
// 여기서 리소스 정리 및 마무리 작업을 수행합니다.
// 프로그램 종료
exit(signum);
}
int main () {
int i = 0;
// SIGINT 신호와 신호 핸들러 등록
signal(SIGINT, signalHandler);
while(++i) {
cout << "Going to sleep...." << endl;
if( i == 3 ) {
raise( SIGINT);
}
sleep(1);
}
return 0;
}실행 결과
Going to sleep.... Going to sleep.... Going to sleep.... Interrupt signal (2) received.
위 예제에서는 반복문이 세 번째 사이클에 도달했을 때 raise(SIGINT)를 호출하여 내부적으로 인터럽트 신호를 발생시킵니다. 그 결과 등록된 신호 핸들러가 실행되어 프로그램이 종료됩니다. 이처럼 signal()과 raise()를 함께 활용하면 외부 인터럽트뿐 아니라 프로그램 내부의 특정 조건에서도 우아하게(gracefully) 종료 처리를 구현할 수 있습니다.