Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

signalについて(中篇)

シグナルの実装

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2007/10/10 14:00

前篇に引き続きシグナルについて解説します。今回は実際にシグナルを使った実装を紹介し、さまざまな場面に応じた使用法と注意点を説明します。また、シグナルを元に、Linux/Unixシステムコールプログラミングにおける特徴や注意点にも述べていきます。

目次

はじめに

 前回に引き続きシグナルについてまとめてみたいと思います。今回はシグナルの実装方法についてです。

過去の記事

4. シグナルの実装(割り込み禁止、アラーム)

4.1 割り込み禁止

 シグナルは、あるハンドラの実行中に別シグナルによる割り込みを防ぐことができます。割り込み防止対象はハンドラだけではなく、ユーザー独自の関数に対しても割り込まれない事を保証することが可能です。

 例えば、SIGINT(Ctrl+C)シグナルが発生しハンドラ内で時間がかかる処理を行っている時にSIGTSTP(Ctrl+Z)が来た場合には、ハンドラ内の処理が終了してからSIGTSTPを処理(バックグラウンド動作)します。

signal_test_interrupt.c
/*
gcc -g -W -Wall signal_test_interrupt.c -o signal_test_interrupt
*/
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <signal.h> #include <errno.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; sigemptyset( &sa.sa_mask ); sigaddset( &sa.sa_mask, SIGTSTP ); /* シグナルマスクの設定 */ sigaction( SIGINT, &sa, 0 ); while( 1 ) { printf( "read wait..\n" ); ret = read( 0, buf, sizeof( buf )); write( 1, buf, ret ); } return 0; } void signal_handler( int no ) { ( void )no; char *mes1 = "signal_handler start!!\n"; char *mes2 = "signal_handler end...\n"; write( 1, mes1, strlen( mes1 )); sleep( 5 ); /* 時間のかかる処理 */ write( 1, mes2, strlen( mes2 )); }

 次の例はハンドラ内とユーザー独自の関数で同じデータを見ている場合の対処です。

 ハンドラ内で参照するデータはグローバル変数である事が多く、ユーザー独自の関数でも同時に参照される可能性があります。その際、ハンドラに邪魔される事無くグローバルデータを編集する機能が必要です。

 下記プログラムはSIGINTシグナルが発生(Ctrl+Cを押下)した回数をハンドラで記録し、Enterキーが押下された時にSIGINTシグナル発生回数を出力します。このときsleep(3)を入れて処理を遅くしていますが、その間にSIGINTシグナルが発生しても記録は変更されません。

signal_test_procmask.c
/*
gcc -g -W -Wall signal_test_procmask.c -o signal_test_procmask
*/
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <errno.h> #include <string.h> int key_num; void signal_handler( int no ); int main( ) { sigset_t block, oblock; /* シグナルマスク */ struct sigaction sa; sigemptyset( &block ); sigemptyset( &oblock ); sigaddset( &block, SIGINT ); sa.sa_handler = signal_handler; sa.sa_flags = SA_RESTART; sa.sa_mask = block; sigaction( SIGINT, &sa, 0 ); while( 1 ) { getchar( ); sigprocmask( SIG_BLOCK, &block, &oblock ); sleep( 4 ); printf( "inner show_number, key_num : [%d]\n", key_num ); sigprocmask( SIG_SETMASK, &oblock, 0 ); } return 0; } void signal_handler( int no ) { ( void )no; char *mes1 = "signal_handler end...["; char *mes2 = "]\n"; char buf[1]; key_num ++; if( key_num > 9 ) key_num = 0; buf[0] = key_num + '0'; write( 1, mes1, strlen( mes1 )); write( 1, buf, 1 ); write( 1, mes2, strlen( mes2 )); }

 前述したとおり、プロセスは特定のシグナルをブロックしたりブロックを解除したりすることができます。シグナルをトリガにして時間のかかる処理をしている時に同じシグナルが複数回届いた場合、ブロックが解除されると通常は1回だけシグナルが送信されます。この事を利用してブロック中に特定のシグナルが送信されたかどうか、調査することができます。

 一般的に、プロセスがブロックしているシグナルの集合を「シグナルマスク」と呼びます。このシグナルマスクを操作するのがsigprocmaskで、調査するのがsigpendingです。以下のプログラムは、SIGINTおよびSIGTERMをブロックし、ブロック中にSIGINTとSIGTERMが発生したかどうかを調べます。

signal_test_sigpending.c
/*
gcc signal_test_sigpending.c -o signal_test_sigpending -W -Wall -g
*/
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <signal.h> #include <errno.h> int key_num; void signal_handler( int no ); int main( ) { sigset_t block, oblock, sigset; /* シグナルマスク */ struct sigaction sa; sigemptyset( &block ); sigaddset( &block, SIGINT ); sigaddset( &block, SIGTERM ); sa.sa_handler = signal_handler; sa.sa_flags |= SA_RESTART; sigemptyset( &sa.sa_mask ); sigaction( SIGINT, &sa, 0 ); sigaction( SIGTERM, &sa, 0 ); while( 1 ) { getchar( ); sigprocmask( SIG_BLOCK, &block, &oblock ); sleep( 2 ); /* 時間のかかる処理 */ if( sigpending( &sigset ) == 0 ) { if( sigismember( &sigset, SIGINT )) { printf( "catch SIGINT!!\n" ); } if( sigismember( &sigset, SIGTERM )) { printf( "catch SIGTERM!!\n" ); } } sigprocmask( SIG_SETMASK, &oblock, 0 ); } return 0; } void signal_handler( int no ) { ( void )no; char *mes1 = "signal_handler end...["; char *mes2 = "]\n"; char buf[1]; key_num ++; if( key_num > 9 ) key_num = 0; buf[0] = key_num + '0'; write( 1, mes1, strlen( mes1 )); write( 1, buf, 1 ); write( 1, mes2, strlen( mes2 )); }

4.2 アラーム

 シグナルと意識しないでシグナルを使用している場面があります。例えばsleep(3)やalarm(2)がそうです。これらは内部でシグナルを使用していますが、使っている側はシグナルを意識しないで利用できます。

 sleep(3)はいろいろな場面で見受けられる、どちらかと言うと汎用的な関数ですが、alarm(2)は使われる場面が比較的少ないと思います。

 そこでalarm(2)の使用例を下記に示します。alarm(2)関数は、指定された値を秒数として参照し、その秒数が経過したらSIGALRMを呼び出します。プロセス内でSIGALRMを再定義していない場合、つまりデフォルト動作ではプロセスを終了させます。

signal_test_alarm.c
/*
gcc -g -W -Wall signal_test_alarm.c -o signal_test_alarm -lm
*/
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <unistd.h> #include <math.h> #include <errno.h> #include <sys/wait.h> #include <sys/time.h> void pressure_cpu ( unsigned int cpu_cnt, unsigned int timeout ); void pressure_io ( unsigned int cpu_cnt, unsigned int timeout ); void wait_children( unsigned int cpu_cnt ); int main( int argc, char ** argv ) { if( 4 > argc ) { return 1; } pressure_cpu(( unsigned int )atoi( argv[1] ), ( unsigned int )atoi( argv[3] )); pressure_io (( unsigned int )atoi( argv[2] ), ( unsigned int )atoi( argv[3] )); wait_children(( unsigned int )( atoi( argv[1] ) + atoi( argv[2] ))); return 0; } void pressure_cpu( unsigned int cpu_cnt, unsigned int timeout ) { unsigned int i = 0; pid_t pid = 0; for( i = 0; i < cpu_cnt; i ++ ) { pid = fork( ); if( pid == 0 ) { alarm( timeout ); while( 1 ) { sqrt( rand( )); } exit( 0 ); } else if( pid > 0 ) { fprintf( stderr, "pressure_cpu %u [%i] go!!\n", i, pid ); } else { fprintf( stderr, "pressure_cpu %u [%i] error ... [%d][%s]\n", i, pid, errno, strerror( errno )); } } return; } void pressure_io( unsigned int cpu_cnt, unsigned int timeout ) { unsigned int i = 0; pid_t pid = 0; for( i = 0; i < cpu_cnt; i ++ ) { pid = fork( ); if( pid == 0 ) { alarm( timeout ); while( 1 ) { sync( ); } exit( 0 ); } else if( pid > 0 ) { fprintf( stderr, "pressure_io %u [%i] go!!\n", i, pid ); } else { fprintf( stderr, "pressure_io %u [%i] error ... [%d][%s]\n", i, pid, errno, strerror( errno )); } } return; } void wait_children( unsigned int cpu_cnt ) { pid_t pid = 0; int status = 0; while( cpu_cnt ) { pid = wait( &status ); cpu_cnt --; if( WIFEXITED( status )) { fprintf( stdout, "worker %i normally end...\n", pid ); } else { fprintf( stdout, "worker %i abnormally end...\n", pid ); } } return; }

 上記プログラムは3つの引数(すべて数値)を取ります。1番目の数値は、CPUのユーザーモードを100%にするプロセス数を指定します。2番目の数値は、CPUのシステムモードを100%にするプロセス数を指定します。3番目の数値は、上記処理を何秒間行うか(つまり何秒後に終了するか)を指定します。

 負荷対象CPU数を指定できるようにしてあり、CPUを複数個実装している環境でも、全CPUに対し負荷が掛けることができます。

 3番目の引数の数値をalarmに渡しているので、0を設定するといつまでも負荷を掛ける状態になります。

 環境によってはCtrl+C等でも止まらない場合がありますので、使用は個人の責任の範疇で行ってください。


  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

修正履歴

  • 2007/11/17 00:46 signal_test_new_sleep.c if( errno == EINTR ) -> if( ret != 0 && errno == EINTR ) signal_test_nano_sleep.c if( errno == EINTR ) -> if( ret != 0 && errno == EINTR )

著者プロフィール

  • 赤松 エイト(エイト)

    (株)DTSに勤てます。 WebアプリやJavaやLL等の上位アプリ環境を密かに憧れつつも、ず~っとLinuxとかHP-UXばかり、ここ数年はカーネル以上アプリ未満のあたりを行ったり来たりしています。 mixiもやってまして、こちらは子育てとか日々の日記メインです。

バックナンバー

連載:signalについて
All contents copyright © 2005-2019 Shoeisha Co., Ltd. All rights reserved. ver.1.5