コンピューター:C言語講座:スリープについて(シグナルの例)
概要
シグナルはUNIXでのCプログラミングにおいてかなり難解な分野だと思いますが、割り込み処理などを行なう為には使わざるを得ない技術です。今回はたまたま仕事でsleep()「指定された秒数休む」の秒指定をより細かく行なうものを作成することがありましたので、それを例として取り上げ、シグナルの参考になればと思います。
サンプル
sleep()はUNIX初期の実装ではシステムコールとして実装されていたようです。つまり、OSがハードと連携しながら直接提供する機能だったのですが、その後、シグナルが充実して来た際に関数へと格下げ(?)となりました。我々にとって関数になることのメリットはもちろん、自分で似たような機能を組めるということでしょう。
今回のサンプルでは、MicroSleep()という、「指定されたマイクロ秒時間休む」というものを取り上げたいと思います。
サンプルソース
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
static int ringring; /* 待ちフラグ */
int MicroSleep(
long sec,
long usec)
{
struct itimerval val,oval;
static void sleephandler();
/* 待つ必要がない */
if(sec==0&&usec==0){
return(0);
}
/* 実時間タイマの現在値を得、クリア */
timerclear(&val.it_interval);
timerclear(&val.it_value);
if(setitimer(ITIMER_REAL,&val,&oval)<0){
return(sec);
}
/* 実時間タイマの現在値と指定値を比較し残り時間を計算しておく */
val.it_value.tv_sec=sec;
val.it_value.tv_usec=usec;
if(timerisset(&oval.it_value)){
if(timercmp(&oval.it_value,&val.it_value,>)){
oval.it_value.tv_sec-=val.it_value.tv_sec;
oval.it_value.tv_usec-=val.it_value.tv_usec;
}
else{
val.it_value=oval.it_value;
oval.it_value.tv_sec=1;
oval.it_value.tv_usec=0;
}
}
/* シグナルハンドラ登録 */
signal(SIGALRM,sleephandler);
ringring=0;
/* 実時間タイマに指定時間をセット */
setitimer(ITIMER_REAL,&val,(struct itimerval *)0);
/* 待ち */
while(!ringring){
sigpause(SIGALRM);
}
/* 元のタイマの残り時間をセット */
setitimer(ITIMER_REAL,&oval,(struct itimerval *)0);
return(0);
}
static void sleephandler(
int sig)
{
if(sig==SIGALRM){
ringring=1;
}
}
MicroSleep()には秒とマイクロ秒を引数で指示することにします。ソース中にコメントを記述したので理解できるかと思います。setitimer()を使用しますが、システムは各プロセスに実時間・仮想時間・仮想+実行時間の3つのインターバルタイマを提供してくれますので、今回は実時間のインターバルタイマを使用します。すでに使用中の場合の対策の為に初めに現在値との比較を行なっています。処理終了時に元のタイマをセットする為です。
signal()でシグナルハンドラを登録します。setitimer()は実時間の場合、SIGALRMが発生するようになっているので、SIGALRMに対してハンドラを設定します。その後、setitimer()でタイマをセットし、ハンドラ関数でringringフラグがクリアされるまで待ちます。待つ場合にただループをまわしてしまうとCPUを食ってしまいますので、sigpause()を使用して待ちます。ringringフラグがクリアされたら元のタイマに設定を戻し、終了します。
実はこのソースはFreeBSDのsleep()の実装を参考に作ってあります。ただ、FreeBSDの実装ではシグナル関連がBSD特有のsigvec(),sigblock(),sigsetmask()などで記述されており、そのままではシステムV系のOSでは使えません。今回のサンプルで使用したsignal(),sigpause()を使用しておけばBSD,システムV両方でそのまま動きます。
このように、シグナルは割り込まれるので直列的なプログラミングが出来ませんが、割り込みが必要な処理では不可欠ですので重要な技術だと思います。単純にkillなどでシグナルが送られた場合の対処だけでなく、割り込みプログラミングにも是非活用してみてください。