setitimer

目次

シグナル

指定した時間だけプログラムの実行を止めて, その後に時間を計るということを, これまで行ってきました. この手法の欠点は, 正確なインターバルを取ることができないという点にあります. 今は gettimeofday を使って時間を調べるだけなので, 処理に大きな時間はかかりません. しかし, 終了するのに 10 ミリ秒かかる処理を 100 ミリ秒毎に行いたいとき, これまでの方法では 10 + 100 = 110 秒毎に処理が行われる計算になります.

だったら 100 ミリ秒を 90 ミリ秒にすればいいんじゃない? そう思うかもしれませんが, そうは問屋が卸しません. 必ず処理に 10 ミリ秒かかるのなら問題はありませんが, 5 ミリ秒だったり 15 ミリ秒だったりするとダメですよね.

これを解決するためには, 時間の計測と処理とを別にして, 一方はひたすら時間を計り, 100 ミリ秒毎に目覚しを鳴らす. 目覚しを聞いたプログラムは, とにかく処理をする. こういうふうにすると, 何とかなりそうです.

目覚しには, setitimer(2) を使います. これは, どのように時間を計るのかを指定する intwhich, 新しく目覚しをセットする時間の長さを指定する struct itimervalvalue, これまでセットされていた時間を保存する struct itimervalovalue という変数に対して, setitimer(which, &value, &ovalue) というように使います. which には, 今回は実時間で計るための変数 ITIMER_REAL を指定. struct itimerval には, gettimeofday で利用した struct timeval な 2 つの要素 it_intervalit_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 sigactionsa_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 が無視されるかなぁと思い, 一応 countvolatile にしておきます. 本当は tvtzvolatile にしたいなぁと思ったんですが, 意味ないよ とコンパイラに言われたので外しました.

計測が終わったら, 変更した設定を 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


[うさぎ]

m@sa.to