TIMEOUT(9)

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

書式
<sys/types.h> <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’ に変換されます。 func は void * の引数を取る関数へのポインタであるべきです。 実行時には、 func は arg をその唯一の引数として受け取ります。 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 と一致しない場合には、何も行いません。 handle は untimeout() に渡される前に以前の 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 の と名付けられた技術レポートで発表された作業に基づいています。 また、 FreeBSD への導入のために Justin T. Gibbs によって少し修正されています。 この実装で使用されているデータ構造の元の作業は、 G. Varghese および A. Lauck によって の で発表されました。 現在の実装は、長らく存在していた、挿入および削除の O(n) 実行時間を 提供するが untimeout 操作のためのハンドルを生成または要求しなかった BSD リンクリストのコールアウト機構に、取って代りました。