(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.
えーと前回のタイマの実装で書いたのだけど,現状のKOZOSのタイマ処理にはバグがあって,タイマがすでに動作中の状態で kz_timer() を呼び出すと,ualarm()しなおすために現在動作中のタイマも再起動されてしまう.
これを修正するためには,前回のタイマ起動時からの経過時間を測定してその差分を考慮する必要があるのだが,まああまり難しい修正ではないのでちょっと直してみた.以下は前回からの差分.まず,kz_timer() の処理用関数である thread_timer() に以下の修正が入っている.
gettimeofday()により現在時刻を取得し,前回 ualarm() が行われた時刻(現在カウント中のタイマが設定された時刻)からの差分を計算し,現在カウント中のタイマ資源の時刻を修正するというものだ.従来は例えば100msのタイマが70ms経過したところで150msのタイマを設定すると,
修正後の処理では前回設定時刻からの経過時間が差分として計算に入れられるため,上のような問題は無い.さらにタイマが先頭に挿入されたときのみ,ualarm()によるタイマ設定を行うようになっている(タイマが動作中でタイマキューの後ろのほうにタイマが挿入されるならば,現在動作中のタイマを上書きする意味は無いので).
SIGALRM発生時の処理は以下のようになる.
ualarm()によるタイマ設定時には,後々の経過時間測定用に,その時点の時刻を保存しておくようにしてある.
サンプルプログラムによって動作確認する.前回のサンプルプログラムに対して,スレッドを3つにして,100ms,150ms,151msの間隔でタイマをかけて文字列表示を行うようになっている.
実行結果は以下のようになる.
まあそれっぽく表示されているようだ.
えーと前回のタイマの実装で書いたのだけど,現状のKOZOSのタイマ処理にはバグがあって,タイマがすでに動作中の状態で kz_timer() を呼び出すと,ualarm()しなおすために現在動作中のタイマも再起動されてしまう.
これを修正するためには,前回のタイマ起動時からの経過時間を測定してその差分を考慮する必要があるのだが,まああまり難しい修正ではないのでちょっと直してみた.以下は前回からの差分.まず,kz_timer() の処理用関数である thread_timer() に以下の修正が入っている.
+static struct timeval alarm_tm;
+
static int thread_timer(int msec)
{
kz_timebuf **tmpp;
kz_timebuf *tmp;
+ struct timeval tm;
+ int diffmsec;
tmp = malloc(sizeof(*tmp));
tmp->next = NULL;
tmp->thp = current;
+ gettimeofday(&tm, NULL);
+ if (timers) {
+ diffmsec = (tm.tv_sec - alarm_tm.tv_sec) * 1000 +
+ (tm.tv_usec - alarm_tm.tv_usec) / 1000;
+ if (timers->msec > diffmsec)
+ timers->msec -= diffmsec;
+ else
+ timers->msec = 0;
+ }
+
for (tmpp = &timers; *tmpp; tmpp = &((*tmpp)->next)) {
if (msec < (*tmpp)->msec) {
(*tmpp)->msec -= msec;
break;
}
msec -= (*tmpp)->msec;
}
if (msec == 0) msec++;
tmp->msec = msec;
tmp->next = *tmpp;
*tmpp = tmp;
- ualarm(timers->msec * 1000, 0);
+ alarm_tm = tm;
+ if (tmpp == &timers)
+ ualarm(timers->msec * 1000, 0);
putcurrent();
return 0;
}
gettimeofday()により現在時刻を取得し,前回 ualarm() が行われた時刻(現在カウント中のタイマが設定された時刻)からの差分を計算し,現在カウント中のタイマ資源の時刻を修正するというものだ.従来は例えば100msのタイマが70ms経過したところで150msのタイマを設定すると,
- 現在のタイマがまた ualarm() により再設定されるため,ゼロからカウントされなおす.このため最終的には 100ms でなく 170ms の時間でタイマ満了されることになる.
- 150msのタイマ設定時に現在起動しているタイマの時間を減算するが,150msから100msをそのまま減算しているため,100msのタイマ満了時(実際には上の理由により170ms後)のさらに50ms後にタイマ満了する.実際には100msのタイマが70ms経過した時点(残り30ms)で150msのタイマをかけているので,100msタイマ満了後の120ms後にタイマ満了するのが正しい.
修正後の処理では前回設定時刻からの経過時間が差分として計算に入れられるため,上のような問題は無い.さらにタイマが先頭に挿入されたときのみ,ualarm()によるタイマ設定を行うようになっている(タイマが動作中でタイマキューの後ろのほうにタイマが挿入されるならば,現在動作中のタイマを上書きする意味は無いので).
SIGALRM発生時の処理は以下のようになる.
void alarm_handler()
{
kz_timebuf *tmp;
sendmsg(timers->thp, 0, 0, NULL);
tmp = timers;
timers = timers->next;
free(tmp);
if (timers) {
+ gettimeofday(&alarm_tm, NULL);
ualarm(timers->msec * 1000, 0);
}
}
ualarm()によるタイマ設定時には,後々の経過時間測定用に,その時点の時刻を保存しておくようにしてある.
サンプルプログラムによって動作確認する.前回のサンプルプログラムに対して,スレッドを3つにして,100ms,150ms,151msの間隔でタイマをかけて文字列表示を行うようになっている.
実行結果は以下のようになる.
% ./koz
func1
func2
func3
func1
func2
func3
func1
func2
func1
func3
func1
func2
func3
func1
^C
%
まあそれっぽく表示されているようだ.