본문 바로가기
IT/programming

[C/C++] signal handler library 활용과 소스코드 공유

by 어느해겨울 2021. 12. 24.

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

 

GitHub - muabow/home

Contribute to muabow/home development by creating an account on GitHub.

github.com

 

소스코드 내용

// 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초씩 슬립 하는 멀티 스레딩 환경을 구성을 한다.

각종 signal 을 등록하고 signal 이 발생하면 signal event handler callback 함수 까지 등록하는 sample 이다.
// 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

 

signal(7) - Linux manual page

signal(7) — Linux manual page SIGNAL(7) Linux Programmer's Manual SIGNAL(7) NAME         top signal - overview of signals DESCRIPTION         top Linux supports both POSIX reliable signals (hereinafter "standard signals") and POSIX real-time sign

man7.org

 

 

댓글