signal handler library
C/C++ 에는 signal.h 를 사용한 signal 함수가 존재한다. 시스템 인터럽트 외 여러 상황에 사용할 수 있기 때문에 필수로 시그널 처리를 하게 된다.
가장 쉬운 예를 들면 ctrl + c를 통해 SIGINT를 발생시켜 프로그램을 종료할 때 메모리 반환을 하여 메모리 누수를 막거나 소켓 또는 IO 핸들러들의 상태를 닫아주어 graceful 한 종료를 해야 할 때 사용된다.
수많은 프로그램을 작성하면서 번번이 signal에 대한 정의와 시그널 처리에 따른 콜백 함수 등을 작성하기 귀찮아 라이브러리로 작성하였고 이를 공유한다.
https://github.com/muabow/home/tree/main/library/cpp/lib_signal
소스코드 내용
// api_signal.cpp
#include <stdio.h>
#include "api_signal.h"
int g_num_sig_handle = 0;
bool g_is_sig_term = false;
void (*p_user_signal_handler)(int) = NULL;
SignalHandler::SignalHandler(void) {
this->num_sig_handle = g_num_sig_handle;
g_num_sig_handle++;
if( this->num_sig_handle > 0 ) {
this->print_debug_info("SignalHandler() instance can not be created.\n");
return ;
}
this->print_debug_info("SignalHandler() create instance\n");
// init global var/function
g_is_sig_term = false;
p_user_signal_handler = NULL;
this->is_debug_print = false;
return ;
}
SignalHandler::~SignalHandler(void) {
this->print_debug_info("SignalHandler() instance destructed\n");
return ;
}
void SignalHandler::print_debug_info(const char *_format, ...) {
if( !this->is_debug_print ) return ;
fprintf(stdout, "SignalHandler::");
va_list arg;
va_start(arg, _format);
vprintf(_format, arg);
va_end(arg);
return ;
}
void SignalHandler::set_debug_print(void) {
this->is_debug_print = true;
this->print_debug_info("set_debug_print() set debug print\n");
return ;
}
void SignalHandler::signal_handler(int _sig_num) {
if( g_is_sig_term ) {
return ;
}
g_is_sig_term = true;
if( p_user_signal_handler != NULL ) {
p_user_signal_handler(_sig_num);
}
return ;
}
void SignalHandler::set_signal(int _sig_num) {
if( this->num_sig_handle > 0 ) {
this->print_debug_info("SignalHandler() instance can not be created.\n");
return ;
}
this->print_debug_info("set_signal() bind signal event [%s]\n", strsignal(_sig_num));
signal(_sig_num, signal_handler);
return ;
}
void SignalHandler::set_ignore(int _sig_num) {
if( this->num_sig_handle > 0 ) {
this->print_debug_info("SignalHandler() instance can not be created.\n");
return ;
}
this->print_debug_info("set_ignore() bind ignore event [%s]\n", strsignal(_sig_num));
signal(_sig_num, SIG_IGN);
return ;
}
void SignalHandler::set_signal_handler(void (*_func)(int)) {
if( this->num_sig_handle > 0 ) {
this->print_debug_info("SignalHandler() instance can not be created.\n");
return ;
}
this->print_debug_info("set_signal_handler() bind user term function\n");
p_user_signal_handler = _func;
return ;
}
bool SignalHandler::is_term(void) {
return g_is_sig_term;
}
헤더 내용
// api_signal.h
#ifndef __API_SIGNAL_H__
#define __API_SIGNAL_H__
#include <iostream>
#include <stdarg.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
using namespace std;
class SignalHandler {
private :
static void signal_handler(int _sig_num);
void print_debug_info(const char *_format, ...);
int num_sig_handle;
bool is_debug_print;
public :
SignalHandler(void);
~SignalHandler(void);
void set_debug_print(void);
void set_signal(int _sig_num);
void set_ignore(int _sig_num);
void set_signal_handler(void (*_func)(int));
bool is_term(void);
};
#endif
makefile 내용
- 깔끔한 구성의 makefile
// makefile
CC=g++
SRCS=$(shell find ./src/ -type f -name '*.cpp')
OBJS=$(patsubst %.cpp, %.o, $(SRCS))
TARGET=libapi_signal.so
$(TARGET): $(OBJS)
@echo
@echo "# Create library"
$(CC) -shared -o $@ $(OBJS)
%.o: %.cpp
$(CC) $(FLAGS) -fPIC -I./include -c $< -o $@
makefile 실행 및 결과
- makefile 실행 결과 libapi_signal.so 가 생성된 걸 확인할 수 있다.
muabow@muabow:~/dev/library/api_signal$ make clean ; make
rm -Rf ./src/api_signal.o libapi_signal.so
rm -Rf
g++ -fPIC -I./include -c src/api_signal.cpp -o src/api_signal.o
# Create library
g++ -shared -o libapi_signal.so ./src/api_signal.o
muabow@muabow:~/dev/library/api_signal$ ls
include libapi_signal.so makefile src
muabow@muabow:~/dev/library/api_signal$
sample 파일 내용
- main()과 2개의 thread가 1초씩 슬립 하는 멀티 스레딩 환경을 구성을 한다.
// sample.cpp
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <iostream>
#include <string>
#include <thread>
#include "api_signal.h"
using namespace std;
bool g_sig_term = false;
// signal event 처리 함수
void signal_event_handler(int _sig_num) {
printf("signal_event_handler() event : [%d] %s\n", _sig_num, strsignal(_sig_num));
g_sig_term = true;
return ;
}
void counter(int _id) {
int idx = 0;
while( !g_sig_term ) {
printf("counter[%d] : %d\n", _id, idx++);
sleep(1);
}
printf("counter[%d] termed\n", _id);
return ;
}
int main(void) {
// signal handler instance 생성
// * signal handler는 하나의 process 에서 한개의 instance 만 생성 가능, 즉 signal handler 복수 생성 불가.
// * signal event의 중복 처리는 원칙적으로 불가하기 때문.
SignalHandler signal_handler;
// debug print 출력
signal_handler.set_debug_print();
// SIGNAL case 설정, 각각의 SIGNAL은 errno.h 를 참조
// args(<SIGNAL>)
signal_handler.set_signal(SIGINT); // set_signal 은 해당 signal 발생 시 동작
signal_handler.set_signal(SIGKILL);
signal_handler.set_signal(SIGTERM);
signal_handler.set_signal(SIGPIPE);
signal_handler.set_ignore(SIGALRM); // set_ignore 는 해당 signal 발생 시 무시
// signal event의 추가 처리를 위한 event handler 등록
// args(<function ptr>)
// function(int _sig_num)
signal_handler.set_signal_handler(&signal_event_handler);
thread thread_func_1(counter, 1);
thread thread_func_2(counter, 2);
thread_func_1.join();
thread_func_2.join();
// signal handler가 signal event를 통해 종료가 되었는지 확인
// return - true: term, false: running
while( !signal_handler.is_term() ) {
sleep(1);
}
printf("main() termed\n");
return 0;
}
sample 컴파일 및 파일 생성 확인
- sample 파일은 signal library와 동일한 경로에 생성한 후 커맨드 라인을 이용하여 컴파일한다.
sample 내용 중에 thread를 사용하니 -l 옵션으로 추가하는 것을 잊지 말자.
muabow@muabow:~/dev/library/api_signal$ g++ -o sample sample.cpp -I./include -L./ -lapi_signal -std=c++11 -lpthread
muabow@muabow:~/dev/library/api_signal$ ls
include libapi_signal.so makefile sample sample.cpp src
sample 실행 및 SIGINT 결과
- sample 파일 실행을 하려면 library가 위치한 경로를 알아야 한다. LD_LIBRARY_PATH에 현재 library 경로를 export 하여 실행할 수 있는 환경을 조성한다.
muabow@muabow:~/dev/library/api_signal$ cat README.md
# sample file 컴파일 옵션
g++ -o sample sample.cpp -I./include -L./ -lapi_signal -std=c++11 -lpthread
# sample file 실행하기 위한 LD_LIBRARY_PATH 설정
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)
muabow@muabow:~/dev/library/api_signal$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)
muabow@muabow:~/dev/library/api_signal$
muabow@muabow:~/dev/library/api_signal$ ./sample
SignalHandler::set_debug_print() set debug print
SignalHandler::set_signal() bind signal event [Interrupt]
SignalHandler::set_signal() bind signal event [Killed]
SignalHandler::set_signal() bind signal event [Terminated]
SignalHandler::set_signal() bind signal event [Broken pipe]
SignalHandler::set_signal_handler() bind user term function
counter[2] : 0
counter[1] : 0
counter[2] : 1
counter[1] : 1
counter[2] : 2
counter[1] : 2
counter[2] : 3
counter[1] : 3
counter[2] : 4
counter[1] : 4
^Csignal_event_handler() event : [2] Interrupt
counter[2] termed
counter[1] termed
main() termed
SignalHandler::SignalHandler() instance destructed
muabow@muabow:~/dev/library/api_signal$
스레드가 종료된 후 main()이 종료되고 마지막에 SignalHandler()가 호출되었단 걸 확인할 수 있다.
SignalHandler() instance destructed를 출력하는 함수 내에 메모리, IO 등 종료 코드를 넣어 마치 main()의 destructor처럼 사용하면 프로그램 종료 뒤처리에 무척 편리하다.
# signal의 코드 및 동작 방식은 아래 man page에서 확인한다.
https://man7.org/linux/man-pages/man7/signal.7.html
'IT > programming' 카테고리의 다른 글
[C/C++] parse mp3 header/C언어 MP3 헤더 분석 함수 공유 (0) | 2022.01.07 |
---|---|
[C/C++] ALSA PCM capture(recoding) 소스 코드 공유 (0) | 2021.12.28 |
[C/C++] C언어 main 함수 전/후에 함수를 실행 하는 방법 (0) | 2021.12.21 |
[C/C++] pthread condition 설명 (0) | 2020.03.30 |
[C/C++] pthread_mutex_lock 설명 (0) | 2020.03.30 |
댓글