setitimer
指定した時間だけプログラムの実行を止めて,
その後に時間を計るということを, これまで行ってきました.
この手法の欠点は,
正確なインターバルを取ることができないという点にあります.
今は gettimeofday
を使って時間を調べるだけなので,
処理に大きな時間はかかりません. しかし,
終了するのに 10 ミリ秒かかる処理を 100 ミリ秒毎に行いたいとき,
これまでの方法では 10 + 100 = 110 秒毎に処理が行われる計算になります.
だったら 100 ミリ秒を 90 ミリ秒にすればいいんじゃない? そう思うかもしれませんが, そうは問屋が卸しません. 必ず処理に 10 ミリ秒かかるのなら問題はありませんが, 5 ミリ秒だったり 15 ミリ秒だったりするとダメですよね.
これを解決するためには, 時間の計測と処理とを別にして, 一方はひたすら時間を計り, 100 ミリ秒毎に目覚しを鳴らす. 目覚しを聞いたプログラムは, とにかく処理をする. こういうふうにすると, 何とかなりそうです.
目覚しには, setitimer
(2) を使います. これは,
どのように時間を計るのかを指定する int
な which
,
新しく目覚しをセットする時間の長さを指定する
struct itimerval
な value
,
これまでセットされていた時間を保存する
struct itimerval
な ovalue
という変数に対して,
setitimer(which, &value, &ovalue)
というように使います.
which
には, 今回は実時間で計るための変数
ITIMER_REAL
を指定. struct itimerval
には, gettimeofday
で利用した struct timeval
な 2 つの要素 it_interval
と it_value
があり,
後者は 1 回目の目覚しまでの時間, 前者はそれ以降の目覚しの間隔を指定します.
ovalue
は, プログラムの最後で現状復帰させるときに使います.
で, 目覚しを聞いてプログラムを実行させるためには,
sigaction
(2) を利用します.
これは, 目覚しの種類を指定する int
な変数 signum
,
目覚しを聞いて実行する関数を指定する struct sigaction
な変数 act
,
そしてこれまでにセットされていた関数を保存する
struct sigaction
な変数 oldact
に対して,
sigaction(signum, &act, &oldact)
とします.
signum
には, setitimer
が鳴らす目覚しの
SIGALRM
を指定します.
struct sigaction
には,
実行する関数を指定する sa_handler
という要素,
余計な目覚しを聞こえなくする sa_mask
という要素,
そしてオプションを設定する sa_flags
という要素があります.
また sa_restorer
という要素もありますが,
これは使っちゃダメみたいです (obsolete
ということは,
昔はあったけど廃止されたということかな?).
ごめんなさい. gettimeofday
(2) です.
タイミングを取ることがメインなので,
どうも計時が疎かになっている気が…
プログラムの例です. だんだん難しくなっていくので, 自分でもわからなくならないようにコメントが入ってます (^^;
main
の 33 行目から, sigaction
の設定をしています. 33 行目は,
目覚しを聞いたときに実行される関数を指定しています.
struct sigaction
の sa_handler
は,
引数として int
を取ることができ,
返値が void
な関数へのポインタです.
ここでは sig_action
という関数を指定しています.
この関数は, gettimeofday
を利用して時間を記録し,
count
をインクリメントしています (count
については後述). 34, 35 行目は, 両方とも 0 (デフォルトの動作) です.
複雑な処理をさせたいときには,
マニュアルとにらめっこして適切な値を指定したほうが良いかもしれません.
41 行目からは, 目覚しを鳴らす間隔を指定しています. 44 行目までで時間を設定し, 45 行目で目覚しをセットします. ここでは 10000 マイクロ秒 = 10 ミリ秒をセットします.
48 行目は, LOOP
回だけ時間が計測されるのを,
while
で待ちます. sig_action
が実行されるたびにインクリメントされる変数 count
をチェックします. main
内で
count
の値は変化しないので,
コンパイラが最適化しちゃうと
while
が無視されるかなぁと思い,
一応 count
は volatile
にしておきます.
本当は tv
や tz
も volatile
にしたいなぁと思ったんですが,
意味ないよ
とコンパイラに言われたので外しました.
計測が終わったら, 変更した設定を 51, 54 行目で元に戻し, 時間を出力して終わります.
1 /* 2 * test_getitimer2.c 3 * getitimer を使った割り込みのテスト 4 * 1998 年 3 月 19 日 5 */ 6 7 #include <stdio.h> 8 #include <sys/time.h> 9 #include <signal.h> 10 #include <unistd.h> 11 12 #define LOOP 10 13 #define DELAY_SEC 0 14 #define DELAY_USEC 10000 15 16 volatile int count = 0; 17 struct timeval tv[LOOP]; 18 struct timezone tz; 19 20 void sig_action() 21 { 22 gettimeofday(tv + count, &tz); 23 count ++; 24 } 25 26 int main() 27 { 28 struct sigaction act, oldact; 29 struct itimerval value, ovalue; 30 int i; 31 32 /* 割り込み関数の設定 */ 33 act.sa_handler = sig_action; 34 act.sa_mask = 0; 35 act.sa_flags = 0; 36 sigaction(SIGALRM, &act, &oldact); 37 38 /* 割り込みの設定 */ 39 /* 最初の割り込みは it_value で指定した値 */ 40 /* 2 度目以降は it_interval で指定した値のようです */ 41 value.it_value.tv_usec = DELAY_USEC; 42 value.it_value.tv_sec = DELAY_SEC; 43 value.it_interval.tv_usec = DELAY_USEC; 44 value.it_interval.tv_sec = DELAY_SEC; 45 setitimer(ITIMER_REAL, &value, &ovalue); 46 47 /* 待ち */ 48 while(count < LOOP); 49 50 /* 割り込みの解除 */ 51 setitimer(ITIMER_REAL, &ovalue, &value); 52 53 /* 割り込み関数の解除 */ 54 sigaction(SIGALRM, &oldact, NULL); 55 56 /* 結果の表示 */ 57 for(i=0; i<LOOP; i++){ 58 printf("%3d : %ld:%06ld\n", i, tv[i].tv_sec, tv[i].tv_usec); 59 } 60 61 /* 終了 */ 62 return 0; 63 }
初めの 2 回が少々乱れているようですが, それ以降はなかなかですね.
0 : 890558678:332133 1 : 890558678:341996 2 : 890558678:351972 3 : 890558678:361973 4 : 890558678:371974 5 : 890558678:381973 6 : 890558678:391973 7 : 890558678:401973 8 : 890558678:411973 9 : 890558678:421972
Copyright (C) 1998, Masahiro SATO