類似の機能について
前回でも紹介している方法ですが、signalの受信をファイルディスクリプタを経由して知る方法として、現状ではpipe(2)を組み合わせる方法がお勧めできます。
シグナルハンドラ内でpipe(2)をwrite(2)し、メインプロセスでは多重I/O等でpipe(2)のread(2)を待ちます。シグナルが発生するとハンドラが呼ばれ、ハンドラ内でpipe(2)をwrite(2)することでメインプロセスが検知し、しかるべき処理を行う、という流れです。signalfd(2)とほぼ同じ機能を実装できます。
サンプルでは、SIGTERM、SIGINT、SIGQUITについて汎用的なハンドラを用意し、ハンドラ内ではpipeを使ってシグナルイベントを書き込みます。本来のイベントハンドラでは、実際のシグナルハンドラでは使用できないメモリアロケートやprintfが使用できます。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
void make_sigaction( int sig, void ( *signal_handle )( int sig ));
void signal_handler ( int sig );
void signal_term_handler( int sig );
void signal_int_handler ( int sig );
void signal_quit_handler( int sig );
int pipefd[2];
struct {
int sigid;
void ( *sig_handle )( int sig );
} siginfo[] = {
{ SIGTERM, signal_term_handler, },
{ SIGINT, signal_int_handler, },
{ SIGQUIT, signal_quit_handler, },
};
#define array( a ) ( sizeof( a )/sizeof( a[0] ))
int main( ) {
fd_set rset, org_rset;
unsigned int i;
int sig = 0;
for( i = 0; i < array( siginfo ); i ++ ) {
make_sigaction( siginfo[i].sigid, signal_handler );
}
pipe( pipefd );
FD_ZERO( &org_rset );
FD_SET( pipefd[0], &org_rset );
while( 1 ) {
rset = org_rset;
int rtn = select( FD_SETSIZE, &rset, 0, 0, 0 );
if( rtn < 0 && errno == EINTR ) {
continue;
}
read( pipefd[0], &sig, sizeof( int ));
for( i = 0; i < array( siginfo ); i ++ ) {
if( siginfo[i].sigid == sig ) {
siginfo[i].sig_handle( siginfo[i].sigid );
break;
}
}
}
return 0;
}
void make_sigaction( int sig, void ( *signal_handle )( int sig )) {
struct sigaction sa;
sa.sa_handler = signal_handle;
sa.sa_flags = SA_RESTART;
sigemptyset( &sa.sa_mask );
sigaction( sig, &sa, 0 );
return;
}
void signal_term_handler( int sig ) {
static int cnt;
cnt ++;
printf( "inside %s Path ... [%d] [%d]\n", __FUNCTION__, sig, cnt );
}
void signal_int_handler( int sig ) {
static int cnt;
cnt ++;
printf( "inside %s Path ... [%d] [%d]\n", __FUNCTION__, sig, cnt );
}
void signal_quit_handler( int sig ) {
static int cnt;
cnt ++;
printf( "inside %s Path ... [%d] [%d]\n", __FUNCTION__, sig, cnt );
}
void signal_handler( int sig ) {
write( pipefd[1], &sig, sizeof( int ));
}
ちなみにこのpipe(2)を使う方法はpthreadの管理にも使えます。現在、メインプロセスでpthreadが停止したことを知るのはpthread_join(3)とpthread_kill(3)になります。どちらも一定の間隔で呼んで、その時の戻り値などで判定することになり、同期的に行うことはできません。よって他のファイルディスクリプタと同等に管理できません。この方法を使用することで、多重I/O等で一元管理化できます。
signalfdのメリットとデメリット
今までのシグナルの実装は、シグナルハンドラという関数内で処理を行う方法が、ほぼ必須でした。その場合、シグナルのリエントラント問題など、使用できる関数に厳しい制限がありました。また、あまり一般的ではないようですが、シグナルハンドラではなくスレッドでシグナルを対処する方法もあります。スレッド内でシグナルを処理するのでハンドラと比較すると利点が大きいですが、それでもスレッドを使う場合はスレッドアンセーフな関数の存在を意識する必要があります。
signalfd(2)を使用すると、多重I/Oを使用してシグナルの到着を待てるため、リエントラント問題は回避され、使用できる関数に制限がありません。これはとても大きなメリットだと思います。
反面、デメリットですが・・・すいません、私の瑣末な知識では思い浮かばないです。ファイルディスクリプタを複数管理する場合、多重I/Oで管理するのが一般的ですが、多重I/Oならではの気をつけなくてはいけない点はあります。しかしそれはsignalfd(2)独自の問題ではなく、ファイルディスクリプタと多重I/Oにおける問題です。
前述していますが、RedHat等のEnterprize向けディストリビューションでは、現時点ではsignalfd(2)が使えるほど新しいカーネルおよびglibがバンドルされていませんので、商用環境で使用することはまだまだできませんが、サポートされた暁は使用をお勧めします。
まとめ
以上で、3つの新しいファイルディスクリプタ「eventfd」「timerfd」「signalfd」の紹介については終了となります。以降の連載では、複数のディスクリプタの管理方法について言及していきたいと思います。
