イベント オブジェクトの定義と使用

イベント オブジェクトを使用するすべてのドライバーは、イベントを待機、設定、クリア、またはリセットする前に、KeInitializeEventIoCreateNotificationEvent、または IoCreateSynchronizationEvent を呼び出す必要があります。 次の図は、スレッドを持つドライバーが、イベント オブジェクトを使用して同期する方法を示しています。

diagram illustrating waiting for an event object.

上の図に示すように、スレッドを持つドライバーは、イベント オブジェクト用に常駐ストレージを提供する必要があります。 ドライバーは、ドライバーが作成したデバイス オブジェクトのデバイス拡張、コントローラー拡張 (コントローラー オブジェクトを使用する場合)、またはドライバーによって割り当てられた非ページ プールを使用できます。

ドライバーは、KeInitializeEvent の呼び出し時に、ドライバーのイベント オブジェクト用の常駐ストレージへのポインターを渡す必要があります。 さらに、呼び出し元は、イベント オブジェクトの初期状態 (シグナル化または非シグナル化) を指定する必要があります。 呼び出し元はイベントの種類も指定する必要があります。イベントの種類は次のいずれかです。

  • SynchronizationEvent

    同期イベントが Signaled 状態に設定されている場合は、イベントが Not-Signaled にリセットされるのを待機している 1 つのスレッドが実行の対象となり、イベントの状態が自動的に Not-Signaled にリセットされます。

    この種類のイベントは、自動クリア イベントとも呼ばれます。待機時間が経過するたびに Signaled 状態が自動的にリセットされるためです。

  • NotificationEvent

    通知イベントが Signaled 状態に設定された場合、そのイベントが Not-Signaled にリセットされるのを待機していたすべてのスレッドが実行可能になり、通知イベントは Not-Signaled に明示的にリセットされるまで (つまり、指定された Event ポインターを使用して、KeClearEvent または KeResetEvent が呼び出されるまで)、Signaled 状態が維持されます。

デバイス ドライバーや中間ドライバーには、1 つのドライバー専用のスレッドを持つものはほとんどありません。まして複数のスレッドを同期させるために、共有リソースを保護する 1 つのイベントを待つスレッドはほとんど皆無です。

イベント オブジェクトを使用して I/O 操作の完了を待機するほとんどのドライバーは、KeInitializeEvent を呼び出すときに入力の TypeNotificationEvent に設定します。 ドライバーが IoBuildSynchronousFsdRequest または IoBuildDeviceIoControlRequest を使用して作成した IRP 用に設定されているイベント オブジェクトは、呼び出し元が 1 つ以上の下位ドライバーによる要求完了を通知するイベントを待機しているため、ほとんどのイベント オブジェクトが NotificationEvent として初期化されます。

ドライバー自体が初期化された後、そのドライバー専用スレッド (存在する場合) およびその他のルーチンは、イベントに対する操作を同期できます。 たとえば、システム フロッピー コントローラー ドライバーなどの IRP のキューを管理するスレッドを持つドライバーは、前の図に示すように、イベントの IRP 処理を同期することができます。

  1. デバイス上で処理できるように IRP をデキューしたスレッドは、ドライバーが提供するストレージへのポインターを使用し、初期化されたイベント オブジェクトに対して KeWaitForSingleObject を呼び出します。

  2. 他のドライバー ルーチンが、IRP の完了に必要なデバイス I/O 操作を実行します。これらの操作が完了すると、ドライバーの DpcForIsr ルーチンが、イベント オブジェクトへのポインター、スレッド用のドライバー決定優先度ブースト (前の図の Increment)、および FALSE に設定されたブール値 Wait を指定して KeSetEvent を呼び出します。 KeSetEvent を呼び出すと、イベント オブジェクトが Signaled 状態に設定され、待機中のスレッドの状態が準備完了に変更されます。

  3. カーネルは、優先順位の高いスレッドが待機状態になく、より高いIRQLで実行すべきカーネルモードルーチンが存在しない場合にのみ、プロセッサが使用可能になるとすぐに実行のためにスレッドをディスパッチします。

    DpcForIsr が既に IRP で IoCompleteRequest を呼び出していない限り、スレッドは IRP を完了でき、デバイスで処理される別の IRP をデキューできます。

Wait パラメーターを TRUE に設定して KeSetEvent を呼び出すことにより、KeSetEvent から戻ったときに KeWaitForSingleObject または KeWaitForMultipleObjects サポート ルーチンを直ちに呼び出すという呼び出し側の意図が示されます。

Wait パラメーターを KeSetEvent に設定する場合は、次のガイドラインを考慮してください。

IRQL < DISPATCH_LEVEL で実行されるページング可能なスレッドまたはページング可能なドライバー ルーチンは、Wait パラメーターを TRUE に設定して KeSetEvent を呼び出さないでください。 このような呼び出しでは、KeSetEventKeWaitForSingleObject または KeWaitForMultipleObjects の呼び出しの間に呼び出し元がページ アウトした場合に、致命的なページ フォールトが発生します。

IRQL = DISPATCH_LEVEL で実行される標準ドライバー ルーチンは、ディスパッチャー オブジェクトで待機時間を 0 以外に設定した待機した場合、システムがダウンします。 ただし、このようなルーチンは、DISPATCH_LEVEL以下の IRQL で実行中に KeSetEvent を呼び出すことができます。

標準ドライバー ルーチンを実行する IRQL の概要については、「ハードウェアの優先度の管理」を参照してください。

KeResetEvent は、指定された Event の以前の状態 (KeResetEvent の呼び出し時に Signaled に設定されたかどうか) を返します。 KeClearEvent は、指定 された Event の状態を単純に Not-Signaled に設定します。

上記のサポート ルーチンを呼び出すタイミングについては、次のガイドラインを検討してください。

パフォーマンスを向上させるには、呼び出し元が KeResetEvent の返す情報に基づいて次の処理を決めるのでない限り、すべてのドライバーで KeClearEvent を呼び出す必要があります。