3.POSIXシグナル
前項で説明した混乱状況や現状のシグナルの拡張性の無さを解消するため、UNIXの互換性を保証する機関であるPOSIXでは新しいシグナルシステムコールを定めました。新しいシステムコールでは関数名も変えてしまい、オプション次第でSysVもBSDもどちらの動作も模倣できるようにしました。
signal(2)の代わりに使うシステムコールはsigaction(2)です。
#include <signal.h> int sigaction( int signum, /* シグナル番号(イベントコード) */ struct sigaction * act, /* 変更後の情報 */ struct sigaction * oact ); /* 変更前の情報 */
引数act、oactはNULLを許容します。変更前の設定情報が必要ない場合は、oactにNULLをセットします。
#include <signal.h> struct sigaction { union { /* シグナルハンドラ */ void ( *_sa_handler )( int ); /* シグナルハンドラ。3引数に対応 */ void ( *_sa_sigaction)( int, siginfo_t *, void * ); } _u; /* シグナルマスク */ sigset_t sa_mask; /* 動作の詳細を設定するフラグ */ unsigned long sa_flags; /* 廃止されたため使用してはならない。 */ void ( *sa_restorer )( void ); } #define sa_handler _u._sa_handler #define sa_sigaction _u._sa_sigaction
sa_handlerとsa_sigactionは、unionで定義されているため、どちらか一方しか設定できません。sa_flagsは、シグナル・ハンドラの動作を変更するためのフラグの論理和をとった値を指定します。わりとよく知られているフラグは下記の通りです。
| SA_RESETHAND | シグナルハンドラが1回呼ばれたら、シグナルの動作をデフォルトに戻します。 |
| SA_NODEFER | シグナルハンドラ内にいる時に同一シグナルを受けると、そのままハンドラが叩かれます。 |
| SA_RESTART | システムコールはシグナルハンドラの処理を待ち、処理が終わればシステムコールの処理を継続します。 |
| SA_ONSTACK | アプリケーションで用意したシグナル・スタックでハンドラを呼び出す事を宣言します。中篇4.6参照。 |
| SA_SIGINFO | sa_sigactionの使用を宣言します。当オプションを設定しない時はsa_handlerを使用します。次ページ3.2参照。 |
| SA_NOCLDWAIT | signam(イベントコード)がSIGCHLDの時、子プロセスが終了した時に子プロセスをゾンビにさせません。中篇4.5参照。 |
普通はSA_RESTARTフラグはデフォルトでセットします。これをしないとシステムコールを使っている所はすべてループで囲み、errno==EINTREの時にcontinueする処理を加えなくてはいけません。
以下はCtrl+Cを1回押下すると、「read error,Interrupted system call」が出力されてプログラムが終了するサンプルプログラムです。
/*
gcc -g -W -Wall signal_test_restart.c -o signal_test_restart
*/ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <errno.h> #include <string.h> void signal_handler( int no ); int main( ) { char buf[256]; int ret; struct sigaction sa; sa.sa_handler = signal_handler; // sa.sa_flags = SA_RESTART; if( sigaction( SIGINT, &sa, NULL ) != 0 ) { /* SIGINTをキャッチ */ fprintf( stderr, "sigaction(2) error!\n" ); exit( 1 ); } while( 1 ) { memset( buf, '\0', sizeof( buf )); printf( "read wait..\n" ); ret = read( 0, buf, sizeof(buf) ); if( ret <= 0 ) { fprintf( stderr, "read error! [%d][%s]\n", errno, strerror( errno )); exit( 1 ); } write( 1, buf, ret ); } return 0; } void signal_handler( int no ) { ( void )no; char *mes1 = "signal get\n"; char *mes2 = "signal end\n"; write( 1, mes1, strlen( mes1 )); sleep( 2 ); write( 1, mes2, strlen( mes2 )); }
SA_RESETHANDフラグはハンドラが叩かれるのを1回のみ許可し、2回目以降は叩かれないようにする場合に使用します。
次のサンプルプログラムは、Ctrl+Cを1回押下するとハンドラが起動しますが、2回目はプログラムが終了します。これは、Ctrl+Cによって発生するシグナル(SIGINT)のデフォルトの動作が「プログラムの終了」だからです。
/*
gcc -g -W -Wall signal_test_resethand.c -o signal_test_resethand
*/ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <errno.h> #include <string.h> void signal_handler( int no ); int main( ) { char buf[256]; int ret; struct sigaction sa; sa.sa_handler = signal_handler; sa.sa_flags = SA_RESTART; sa.sa_flags |= SA_RESETHAND; if( sigaction( SIGINT, &sa, 0 ) != 0 ) { /* SIGINTをキャッチ */ fprintf( stderr, "sigaction(2) error!\n" ); exit( 1 ); } while( 1 ) { memset( buf, '\0', sizeof( buf )); printf( "read wait..\n" ); ret = read( 0, buf, sizeof(buf) ); if( ret <= 0 ) { fprintf( stderr, "read error! [%d][%s]\n", errno, strerror( errno )); exit( 1 ); } write( 1, buf, ret ); } return 0; } void signal_handler( int no ) { ( void )no; char *mes1 = "signal get\n"; char *mes2 = "signal end\n"; write( 1, mes1, strlen( mes1 )); sleep( 2 ); write( 1, mes2, strlen( mes2 )); }
SA_NODEFERフラグはハンドラ内の処理中でも同一シグナルを受け付けるので、実際にシグナルが発生した回数を記録できます(それくらいしか利用価値はないように思います……)。
以下のサンプルプログラムは、ハンドラ内でCtrl+Cを押下すると「signal get」が出力され、2秒後に「signal end」が出力しますが、その2秒間にCtrl+Cを押下すると押下した分だけ「signal get」が出力されます。
/*
gcc -g -W -Wall signal_test_nodefer.c -o signal_test_nodefer
*/ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <errno.h> #include <string.h> void signal_handler( int no ); int main( ) { char buf[256]; int ret; struct sigaction sa; sa.sa_handler = signal_handler; sa.sa_flags = SA_RESTART; sa.sa_flags |= SA_NODEFER; if( sigaction( SIGINT, &sa, 0 ) != 0 ) { fprintf( stderr, "sigaction(2) error!\n" ); exit( 1 ); } while( 1 ) { memset( buf, '\0', sizeof( buf )); printf( "read wait..\n" ); ret = read( 0, buf, sizeof(buf) ); if( ret <= 0 ) { fprintf( stderr, "read error! [%d][%s]\n", errno, strerror( errno )); exit( 1 ); } write( 1, buf, ret ); } return 0; } void signal_handler( int no ) { ( void )no; char *mes1 = "signal get\n"; char *mes2 = "signal end\n"; write( 1, mes1, strlen( mes1 )); sleep( 2 ); write( 1, mes2, strlen( mes2 )); }
SA_RESTARTフラグは旧シグナルで言うところのBSD系OSを、また、SA_RESETHANDフラグとSA_NODEFERフラグはSysVシグナルを模倣できます。個人的な見解ですが、今となってはSysVシグナルを模倣する理由がほとんど無いので、使う理由も無いのかな、と思っています。
sa_sigactionの説明は後で行います。まずはsa_handlerにて一般的なシグナルハンドラ処理を行ってみます。
