4. シグナルの実装(送信)
4.8 送信
今までのシグナルは、ターミナル・OS・プログラム等から対象プロセスに対し通知していました。しかし時にはシグナルをプログラムの中から任意のプロセスに対し発行させたい時もあります。プロセスからプロセスへ通知する事こそプロセス間通信の特徴を捉えているといってもよいでしょう。
今までは主に受信するための方法や、受信してからどのように振舞うべきかを中心に述べてきました。この章では送信するための方法を述べていきます。プログラムから任意のプロセスに送信するには主に2つの方法があります。kill(2)もしくはsigqueue(2)です。
共に送信できる条件は一緒です。送信するプロセスが特権(例えばroot権限を持つプロセス)を持っていれば全プロセスに送信でき、特権を持っていなければ送信プロセスの実UIDもしくは実効UIDが、受信プロセス側のUIDと一緒でないといけません。一般的にプロセスは、それを起動したユーザーの権限で動作しますが、ファイルの属性をsetuid/setgidする事で、起動したユーザーと無関係にファイルの所有権を持つユーザーの権限で動作させる事もできます。
4.8.1 kill
任意のプロセスもしくはプロセスグループにシグナルを送れます。簡単に実装できるうえ、送信相手の指定にバリエーションがあるため、割とよく使われていると思います。以下、簡単にまとめました。詳細はman 2 killを参照してください。
| pid | 動作内容 |
| pid > 0 | pidを持ったプロセスに送信 |
| pid == 0 | 送信側のプロセスと同じプロセスグループの全プロセスに送信。つまり送信側も受信する |
| pid = -1 | 送信側のプロセスが送る事のできる全プロセスに送信 |
| pid < -1 | pidのプロセスグループの全プロセスに送信 |
対象プロセスのpidがわかっている時、かつ対象プロセスだけにシグナルを発行したい時は、そのpidを指定すればよいでしょう。プロセスグループに属するすべてのプロセスに対し一斉送信する方法については次回紹介します。
問題はpid = -1の時で、これはinitプロセスを除く全プロセスにシグナルを送信します(送信できる条件を満たしているプロセスだけが対象)。もし、root権限のプロセスが実行すると全プロセスを殺すことになってしまいます。
また、第2引数であるsigにはシグナルの値を設定しますが、「0」をセットすると送信対象プロセスが存在するかチェックしてくれます。
kill(2)は、引数からも判るようにプロセスに対しシグナルを発行するだけなので、受信プロセスのハンドラはsa_handlerとなり、sa_sigactionで詳細な情報を取得する事はできません。
下記プログラムは内部で子プロセスを生成して引数で与えた時間だけsleepし、過ぎたら終了します。親プロセスは子プロセスへsigを0にしてkillを定期的に送ります。子プロセスの終了前と終了後で出力結果が異なる事が確認できます。
/*
gcc signal_test_kill.c -o signal_test_kill -W -Wall -g
*/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <errno.h> #include <unistd.h> #include <sys/wait.h> void signal_handler( int num ); int main( int argc, char ** argv ) { if( argc < 3 ) { exit( 1 ); } int max_cnt = atoi( argv[1] ); int sleep_cnt = atoi( argv[2] ); struct sigaction sa; sa.sa_handler = signal_handler; sa.sa_flags = SA_RESTART; sigaction( SIGCHLD, &sa, 0 ); pid_t pid = fork( ); if( pid == 0 ) { fprintf( stdout, "child process[%d] start!! : [%d]\n", getpid( ), sleep_cnt ); sleep( sleep_cnt ); fprintf( stdout, "child process[%d] end!! : [%d]\n", getpid( ), sleep_cnt ); exit( 0 ); } else { fprintf( stdout, "parent process[%d] doing\n", getpid( )); } int rtn; int i; for( i = 0; i < max_cnt; i ++ ) { sleep( 1 ); rtn = kill( pid, 0 ); fprintf( stdout, "kill( %d, 0 ):[%d] err:[%d][%s]\n", pid, rtn, errno, strerror( errno )); } return 0; } void signal_handler( int num ) { ( void )num; int status = 0; while( waitpid( -1, &status, WNOHANG ) > 0 ); }
なお、自プロセスへシグナルを送る場合、raise(3)関数も使用できます。簡易版kill(2)と呼べるもので、raise( sig ) == kill( getpud( ), sig )です。また、kill(2)およびraise(3)では、SIGFPE、SIGILL、SIGSEGVについては送る事ができません。
4.8.2 sigqueue
基本的にはkill(2)と同様の動作が可能ですが、違うのは、killと比べて詳細な情報を送信できる点と、pidが0以上しか認められない点です。詳細情報を送信できるので受信側はsa_handlerでもsa_sigactionでもOKです。sa_sigactionで受信すれば、sigqueueの第3引数に対して、sa_sigactionの第2引数から情報を取得できます。
- union sigval value.sival_int( 型はint ) にセットした値を、siginfo_t * info -> si_int
- union sigval value.sival_ptr( 型はvoid * ) にセットした値を、siginfo_t * info -> si_ptr
で確認できます。
sival_ptrはポインタ変数ですが、受信プロセスが送信プロセスと異なる場合、ポインタ(つまりアドレス)の値が同じでも、その場所にある情報は当然違うので、他プロセスへ送信する際の使用はできません。つまり受信プロセスと送信プロセスが等しい時にしか、sival_ptrは使えません。
下記プログラムは自プロセスへkill(2)とsigqueue(2)を交互に発行しています。sigqueueの時は文字列を送信していますが、正しく受け取れる事が確認できます。
/*
gcc signal_test_sigqueue.c -o signal_test_sigqueue -W -Wall -g
*/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <unistd.h> void signal_handler( int num, siginfo_t * info, void * ctx ); int main( int argc, char ** argv ) { struct sigaction sa; union sigval val; int i; if( argc < 3 ) { exit( 1 ); } int max_cnt = atoi( argv[1] ); int sleep_cnt = atoi( argv[2] ); sa.sa_sigaction = signal_handler; sa.sa_flags = SA_RESTART | SA_SIGINFO; sigemptyset( &sa.sa_mask ); sigaction( SIGINT, &sa, 0 ); val.sival_ptr = malloc( 256 ); for( i = 0; i < max_cnt; i ++ ) { sleep( sleep_cnt ); if( i % 2 == 0 ) { kill( getpid( ), SIGINT ); } else { sprintf( val.sival_ptr, "this signal message : [%d]", i ); sigqueue( getpid( ), SIGINT, val ); } } return 0; } void signal_handler( int num, siginfo_t * info, void * ctx ) { ( void )num; ( void )ctx; if( info -> si_code == 0 ) { fprintf( stdout, "now get signal!! [%d] si_code : [%d] si_value : [%d]\n", getpid( ), info -> si_code, info -> si_int ); } else { fprintf( stdout, "now get signal!! [%d] si_code : [%d] si_value : [%s]\n", getpid( ), info -> si_code, ( char * )info -> si_ptr ); } }
また、同一プロセスという条件であるなら、スレッドに対しても送信できるはずです。スレッドを使ったシグナルの扱いは後篇で解説します。
/*
gcc signal_test_sigwaitinfo.c -o signal_test_sigwaitinfo -W -Wall -g -lpthread
*/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> #include <signal.h> #include <errno.h> void * worker_thread ( void * arg ); void * signal_handler( void * arg ); int main( int argc, char ** argv ) { sigset_t ss; pthread_t pt1, pt2; int max_cnt; if( argc < 2 ) { return 0; } max_cnt = atoi( argv[1] ); sigemptyset( &ss ); sigaddset( &ss, SIGHUP ); sigaddset( &ss, SIGINT ); sigprocmask( SIG_BLOCK, &ss, 0 ); pthread_create( &pt1, 0, &signal_handler, 0 ); pthread_detach( pt1 ); pthread_create( &pt2, 0, &worker_thread, &max_cnt ); pthread_join( pt2, 0 ); pthread_cancel( pt1 ); return 0; } void * worker_thread( void * arg ) { int count; int max_cnt = *( int * )arg; union sigval val; val.sival_ptr = malloc( 256 ); for( count = 0; count < max_cnt; count ++ ) { sleep( 1 ) ; printf( "timer - [%d] [%d]\n", max_cnt, count ); sprintf( val.sival_ptr, "message from \'worker_thread\' count:[%d]", count ); sigqueue( getpid( ), SIGHUP, val ); } return 0; } void * signal_handler( void * arg ) { sigset_t ss; siginfo_t info; int sig; ( void )arg; sigemptyset( &ss ); sigaddset( &ss, SIGHUP ); sigaddset( &ss, SIGINT ); while( 1 ) { sig = sigwaitinfo( &ss, &info ); if( sig < 0 ) { printf( "error sigwaitinfo:[%d][%s]\n", errno, strerror( errno )); continue; } switch( sig ) { case SIGHUP: printf( "signal_handler 'SIGHUP' Path!! [%d] message:[%s]\n", sig, ( char * )info.si_ptr ); break; case SIGINT: printf( "signal_handler 'SIGINT' Path!! [%d]\n", sig ); break; default: break; } } printf( "sigwait END!!\n" ); return 0; }
