スポンサーリンク

TIMEOUT(9) FreeBSD カーネル開発者マニュアル TIMEOUT(9)

名称

timeout, untimeout, callout_handle_init, callout_init, callout_stop, callout_drain, callout_reset, callout_pending, callout_active, callout_deactivate − 明示された時間長の後の関数の実行

書式

#include <sys/types.h>
#include <sys/systm.h>

typedef void timeout_t (void *);

struct callout_handle

timeout(timeout_t *func, void *arg, int ticks);

void

callout_handle_init(struct callout_handle *handle);

struct callout_handle handle = CALLOUT_HANDLE_INITIALIZER(&handle)

void

untimeout(timeout_t *func, void *arg, struct callout_handle handle);

void

callout_init(struct callout *c, int mpsafe);

int

callout_stop(struct callout *c);

int

callout_drain(struct callout *c);

void

callout_reset(struct callout *c, int ticks, timeout_t *func, void *arg);

int

callout_pending(struct callout *c);

int

callout_active(struct callout *c);

callout_deactivate(struct callout *c);

解説

関数 timeout() は ticks/hz 秒後に実行されるための引数 func によって与えら れる関数を呼び出すためのスケジュールを行います。正ではない値の ticks は暗 黙のうちに値 ‘1’ に変換されます。 funcvoid * の引数を取る関数へのポイ ンタであるべきです。実行時には、 funcarg をその唯一の引数として受け取 ります。 timeout() からの戻り値は、スケジュールされた timeout を取り消す 要求のための untimeout() 関数との接続に使用されることが可能な struct callout_handle です。 timeout() の呼び出しは古いスタイルで、新しいコード は callout_*() 関数を使用するべきです。

関数 callout_handle_init() はハンドルを初期化するために使用し、untimeout と共に使用されても副作用無しに戻るようにします。

コールアウトハンドルに CALLOUT_HANDLE_INITIALIZER() の値を割当てること は、 callout_handle_init() と同様の機能を実行し、静的な宣言またはグローバ ルなコールアウトハンドルで使用するために提供されています。

関数 untimeout() は、そのハンドルの正当性を確認するために func および arg 引数を使用して、 handle に関連付けられた timeout を取り消します。そのハン ドルが引数 arg を取る関数 func を持つ timeout と一致しない場合には、何も 行いません。 handleuntimeout() に渡される前に以前の timeout(), callout_handle_init() の呼び出し、または CALLOUT_HANDLE_INITIALIZER(&handle) の値の割当てによって初期化されなけれ ばなりません。以前に初期化されたハンドルを伴なわない untimeout の呼び出し の振る舞いは未定義です。 untimeout() の呼び出しは古いスタイルで、新しい コードは callout_*() 関数を使用するべきです。

ハンドルがシステムによって再利用されるので、両方の呼び出しが同じ関数のポ インタおよび引数を使用し、2 番目の呼び出しの前に最初の timeout が終了する か取り消された場合には、1 つの timeout() の実行からのハンドルが別の timeout() の実行のハンドルが一致することができることが (意外ではあるが) 可能です。 timeout の設備は timeout() および untimeout() のための O(1) 実 行時間を提供します。 timeout (訳注: および untimeout) は Giant ロックが保 持されている状態で、 softclock() から実行されます。従って、再入から保護さ れます。

関数 callout_init(), callout_stop(), callout_drain() および callout_reset() は固有のコールアウト構造を割当てることを希望するクライア ントのための、低レベルのルーチンです。

関数 callout_init() はコールアウトを初期化し、そのためそのコールアウトは 何の副作用もなしに callout_stop(), callout_drain() または callout_reset() に渡されることができます。 mpsafe 引数が 0 の場合には、callout 構造体は ‘‘マルチプロセッサセーフ’’ であるとはみなされません。すなわち、ジャイアン トロックがコールアウト関数の呼出し前に獲得され、コールアウト関数が戻ると きに解放されるようにします。

関数 callout_stop() は、そのコールアウトが現在保留中の場合には、コールア ウトを取り消します。コールアウトが保留中の場合には、 callout_stop() は 0 でない値を返します。コールアウトが設定されていないか既に実行されているか 現在実行中の場合には、 0 が返されます。この関数が呼び出されるとき、コール アウトが Giant ミューテックス (mutex) によって保護されている場合には、 Giant を保持していなければなりません。

関数 callout_drain() は、コールアウトが既に進行中の場合にはその完了をウェ イトすることを除いて、 callout_stop() と同一です。この関数は、そのコール アウトがブロックするかもしれないあらゆるロックを保持している間は、決して 呼び出されてはなりません。さもないと結果としてデッドロックします。コール アウトサブシステムが既にこのコールアウトを処理し始めたならコールアウト関 数が callout_drain() の実行の間に呼び出されるかもしれないことに注意してく ださい。しかしながら、コールアウトサブシステムは、 callout_drain() が返る 前にコールアウトが完全に停止されることを保証します。

関数 callout_reset() は最初にそのコールアウトを廃止するために callout_stop() と同様のことを実行し、それから新しいコールアウトを timeout() と同じ流儀で確立します。この関数が呼び出されるとき、コールアウ トが Giant ミューテックスによって保護される場合には、 Giant は保持されな ければなりません。

マクロ callout_pending(), callout_active() および callout_deactivate() は コールアウトの現在の状態へのアクセスを提供します。これらのマクロを慎重に 使用すれば、非同期タイマ機構に特有の多くの競合条件を避けることができま す。さらなる詳細については下記の 競合条件を回避するを参照してださい。 callout_pending() マクロは、コールアウトが 保留中であるかどうかチェックし ます。コールアウトはタイムアウトが設定されているが時間がまだ到着していな い時、 保留中であると見なされます。いったんタイムアウト時間が来て、コール アウトサブシステムがこのコールアウトを処理し始めれば、 callout_pending() はたとえコールアウト関数が実行を終了して (または、始めて) いなくても FALSE を返すことに注意してください。 callout_active() マクロはコールアウ トが アクティブとしてマークされているかどうかチェックし、 callout_deactivate() マクロはコールアウトの アクティブフラグをクリアしま す。コールアウトサブシステムは、タイムアウトが設定されているコールアウト を アクティブに設定し、 callout_stop() と callout_drain() では アクティブ をクリアしますが、コールアウト関数の実行を通して通常どおりコールアウトの 期限が切れた場合には、クリア しません。

競合条件を回避する

コールアウトサブシステムはそれ自体のタイマコンテキストからコールアウト関 数を呼び出します。ある種の同期なしでは、コールアウト関数は他のスレッドか らコールアウトの停止やリセットを試みながら並行して起動される可能性があり ます。特に、コールアウト関数は典型的には最初の動作としてミューテックスを 獲得するので、コールアウト関数は既に呼び出されたとしても、別のスレッドが コールアウトをリセットするかまたは停止しようとするまでそのミューテックス を待ってブロックされてしまいます。

コールアウトサブシステムはこれらの同期関係に対処するために多くのメカニズ ムを提供します。

             1. mpsafe を FALSE に設定して callout_init() を使用することで指定される) Giant ミューテックスによってコールアウトが保護される場合は、このミューテックスは競合条件を避けるために使用できます。callout_stop() か callout_reset() を呼び出す前に、 Giantミューテックスは呼び出し側によって獲得されなければなりませんが、コールアウトが想定したとおりに正しく停止されるかまたはリセットされることが保証されます。コールアウトかその関連のミューテックスを破壊する前に callout_drain() を使用する必要がまだあることに注意してください。

2. callout_stop() からのリターン値は、コールアウトが削除されたか どうかを示します。コールアウトが設定され、コールアウト関数がま だ実行されていないことがわかっている場合は、 FALSE の値はコー ルアウト関数がまさに呼び出されようとしていることを示します。例 えば:

if (sc->sc_flags & SCFLG_CALLOUT_RUNNING) {

if (callout_stop(&sc->sc_callout)) {

sc->sc_flags &= ~SCFLG_CALLOUT_RUNNING;

/* successfully stopped */

} else {

/*

* callout has expired and callout

* function is about to be executed

*/

}

}

callout_reset() がコールアウトを停止したかどうか決定するための 同等なメカニズムがなにもないことに注意してください。

             3. callout_pending(), callout_active() およびcallout_deactivate() マクロは競合条件を回避するために連携して使用できます。コールアウトのタイムアウトが設定されるとき、コールアウトサブシステムはともに アクティブと 保留中の両方でコールアウトをマークします。タイムアウト時間に達するとき、コールアウトサブシステムは 保留中のフラグを最初にクリアすることによってコールアウトを処理し始めます。次に、 アクティブフラグを変えないでコールアウト関数を呼び出して、コールアウト関数が戻った後でさえ アクティブフラグをクリアしません。ここで説明されたメカニズムでは、コールアウト関数自体が callout_deactivate() マクロを使用して アクティブフラグをクリアすることを要求します。callout_stop() と callout_drain() 関数は戻る前に、常に アクティブと 保留中フラグの両方をクリアします。
callout_pending() が TRUE を返す場合、コールアウト関数は、最初に 保留中フラグをチェックして動作なしで戻るべきです。これは、コールアウトがコールアウト関数が呼び出される直前にcallout_reset() を使用して再スケジュールされたことを示します。callout_active() が FALSE を返す場合、コールアウト関数は同様に動作なしで返るべきです。これは、コールアウトが停止されたことを示します。最後に、コールアウト関数は、 アクティブフラグをクリアするために callout_deactivate() を呼び出すべきです。例えば:

mtx_lock(&sc->sc_mtx);
if (callout_pending(&sc->sc_callout)) {

/* callout was reset */

mtx_unlock(&sc->sc_mtx);

return;

}
if (!callout_active(&sc->sc_callout)) {

/* callout was stopped */

mtx_unlock(&sc->sc_mtx);

return;

}
callout_deactivate(&sc->sc_callout);
/* rest of callout function */

上記で使用したミューテックスのような適切な同期をともに使うこと により、このアプローチは callout_stop() と callout_reset() 関 数がいつも競合なしで使用できます。例えば:

      mtx_lock(&sc->sc_mtx);
      callout_stop(&sc->sc_callout);
      /* The callout is effectively stopped now. */

コールアウトがまだ保留中である場合、これらの関数は通常どおり動 作しますが、コールアウトの処理が既に始まっている場合、コールア ウト関数におけるテストにおいて、これらの関数はさらなる動作なし で戻ることになります。コールアウト関数と他のコードの間の同期 は、コールアウト関数が callout_deactivate() 呼び出しを終えるま でコールアウトの停止やリセットが行われないことを確実にします。

さらに、上記のテクニックは、 アクティブフラグが実際にコールア ウトが有効か無効かを反映することを確実にします。 callout_active() が FALSE を返した場合、たとえコールアウトサブ システムが実際にコールアウト関数を開始しようとしていたとして も、コールアウト関数は動作なしに終了してしまうので、実質的に無 効化されています。

最後に、コールアウトを停止しようとしているときに考慮しなければならない最 後の競合条件が 1 つあります。この場合、既に破壊されるかまたは再利用された データオブジェクトにアクセスする必要があるかもしれないので、コールアウト 関数自体に停止されたコールアウトを検出するさせるために安全でないかもしれ ません。コールアウトが完全に終了したことを保証するためには、 callout_drain() 呼び出しを使用しなければなりません。

戻り値

timeout() 関数は untimeout() に渡すことが可能な struct callout_handle を 返します。 callout_stop() および callout_drain() 関数は呼び出された時に コールアウトが未だ保留中の場合には 0 以外を、そうでない場合には 0 を返し ます。

歴史

現在の timeout および untimeout ルーチンは Adam M. Costello および George Varghese の Redesigning the BSD Callout and Timer Facilities と名付けられ た技術レポートで発表された作業に基づいています。また、 FreeBSD への導入の ために Justin T. Gibbs によって少し修正されています。この実装で使用されて いるデータ構造の元の作業は、 G. Varghese および A. Lauck によって Proceedings of the 11th ACM Annual Symposium on Operating Systems PrinciplesHashed and Hierarchical Timing Wheels: Data Structures for the Efficient Implementation of a Timer Facility で発表されました。現在の 実装は、長らく存在していた、挿入および削除の O(n) 実行時間を提供するが untimeout 操作のためのハンドルを生成または要求しなかった BSD リンクリスト のコールアウト機構に、取って代りました。

FreeBSD 10.0 February 6, 2005 FreeBSD 10.0

スポンサーリンク