自動同期の使用
フレームワーク ベースのドライバーのほぼすべてのコードは、イベント コールバック関数に存在します。 フレームワークは、以下のように、ドライバーのコールバック関数のほとんどを自動的に同期します。
フレームワークは常に、一般的なデバイス オブジェクト、機能デバイス オブジェクト (FDO)、物理デバイス オブジェクト (PDO) イベント コールバック関数を相互に同期して、コールバック関数の 1 つ (EvtDeviceSurpriseRemoval、EvtDeviceQueryRemove、EvtDeviceQueryStop を除く) のみをデバイスごとに一度に呼び出すことができます。 これらのコールバック関数は、プラグ アンド プレイ (PnP) イベントと電源管理イベントをサポートし、IRQL = PASSIVE_LEVEL で呼び出されます。
必要に応じて、フレームワークは、ドライバーの I/O 要求を処理するコールバック関数の実行を同期して、これらのコールバック関数が一度に 1 つずつ実行されるようにすることができます。 具体的には、フレームワークは、要求オブジェクトの EvtRequestCancel コールバック関数と共に、キュー、割り込み、遅延プロシージャ呼び出し (DPC)、タイマー、作業項目、およびファイル オブジェクトのコールバック関数を同期できます。 フレームワークは IRQL = DISPATCH_LEVEL でこれらのコールバック関数のほとんどを呼び出しますが、キューとファイル オブジェクトのコールバック関数を IRQL = PASSIVE_LEVEL で強制的に実行できます。 (作業項目コールバック関数は常に PASSIVE_LEVEL で実行されます。)
フレームワークでは、内部同期ロックのセットを使用して、この自動同期を実装します。 フレームワークでは、2 つ以上のスレッドが同じコールバック関数を同時に呼び出すことができないようにします。各スレッドは、コールバック関数を呼び出す前に同期ロックを取得できるようになるまで待機する必要があるためです。 (必要に応じて、ドライバーはこれらの同期ロックを取得することもできます。詳細については、「フレームワーク ロックの使用」を参照してください。)
ドライバーは、オブジェクト コンテキスト空間にオブジェクト固有のデータを格納する必要があります。 ドライバーがフレームワークで定義されたインターフェイスのみを使用する場合、オブジェクトへのハンドルを受け取るコールバック関数のみがこのデータにアクセスできます。 フレームワークがドライバーのコールバック関数の呼び出しを同期している場合、一度に呼び出されるコールバック関数は 1 つだけであり、オブジェクトのコンテキスト空間には一度に 1 つのコールバック関数にのみアクセスできます。
ドライバーがパッシブ レベルの割り込み処理を実装しない限り、割り込みおよび割り込みデータへのアクセスを処理するコードは、デバイスの IRQL (DIRQL) で実行する必要があり、追加の同期が必要です。 詳細については、「割り込みコードの同期」を参照してください。
ドライバーが I/O 要求を処理するコールバック関数の自動同期を有効にする場合、フレームワークはこれらのコールバック関数を同期して、一度に 1 つずつ実行します。 次の表に、フレームワークが同期するコールバック関数の一覧を示します。
オブジェクトの種類 | 同期されたコールバック関数 |
---|---|
キュー オブジェクト |
|
File オブジェクト |
すべての コールバック関数 |
要求オブジェクト |
必要に応じて、フレームワークは、ドライバーがデバイスに提供する割り込み、DPC、作業項目、およびタイマー オブジェクトのコールバック関数 (割り込みオブジェクトの EvtInterruptIsr コールバック関数を除く) とこれらのコールバック関数を同期することもできます。 この追加の同期を有効にするには、ドライバーは、これらのオブジェクトの構成構造の AutomaticSerialization メンバーを TRUE に設定する必要があります。
要約すると、フレームワークの自動同期機能には、次の機能があります。
フレームワークは、常に各デバイスの PnP と電源管理コールバック関数を同期します。
必要に応じて、フレームワークは I/O キューの要求ハンドラーといくつかの追加のコールバック関数を同期できます (前の表を参照)。
ドライバーは、割り込み、DPC、作業項目、およびタイマー オブジェクトのコールバック関数を同期するようにフレームワークに要求できます。
ドライバーは、「割り込みコードの同期」で説明されている手法を使用して、サービスが割り込む、および割り込みデータにアクセスするコードを同期する必要があります。
フレームワークは、ドライバーの CompletionRoutine コールバック関数や I/O ターゲット オブジェクトが定義するコールバック関数など、ドライバーの他のコールバック関数を同期しません。 代わりに、フレームワークは、ドライバーがこれらのコールバック関数を同期するために使用できる追加のロックを提供します。
同期スコープの選択
フレームワークで、デバイスのすべての I/O キューに関連付けられているすべてのコールバック関数を同期するように選択できます。 または、デバイスの I/O キューごとに、フレームワークでコールバック関数を個別に同期するように選択することもできます。 ドライバーで使用できる同期オプションは次のとおりです。
デバイス レベルの同期
フレームワークは、デバイスのすべての I/O キューについて、前のテーブルに含まれているコールバック関数を同期して、一度に 1 つずつ実行します。 フレームワークは、コールバック関数を呼び出す前にデバイスの同期ロックを取得することで、この同期を実現します。
キュー レベルの同期
フレームワークは、それぞれの I/O キューについて、前のテーブルに含まれているコールバック関数を同期して、一度に 1 つずつ実行します。 フレームワークは、コールバック関数を呼び出す前にキューの同期ロックを取得することで、この同期を実現します。
同期なし
フレームワークは、前のテーブルに含まれているコールバック関数の実行を同期せず、コールバック関数を呼び出す前に同期ロックを取得しません。 同期が必要な場合は、ドライバーが指定する必要があります。
フレームワークでデバイス レベルの同期、キュー レベルの同期、またはドライバーの同期を提供するかどうかを指定するには、ドライバー オブジェクト、デバイス オブジェクト、またはキュー オブジェクトの同期スコープを指定できます。 オブジェクトの WDF_OBJECT_ATTRIBUTES 構造体の SynchronizationScope メンバーは、オブジェクトの同期スコープを識別します。 ドライバーで指定できる同期スコープの値は次のとおりです。
WdfSynchronizationScopeDevice
フレームワークは、デバイス オブジェクトの同期ロックを取得して同期します。
WdfSynchronizationScopeQueue
フレームワークは、キュー オブジェクトの同期ロックを取得して同期します。
WdfSynchronizationScopeNone
フレームワークは同期せず、同期ロックを取得しません。
WdfSynchronizationScopeInheritFromParent
フレームワークは、オブジェクトの親オブジェクトからオブジェクトの SynchronizationScope 値を取得します。
一般に、デバイス レベルの同期を使用することはお勧めしません。
同期スコープ値の詳細については、「WDF_SYNCHRONIZATION_SCOPE」を参照してください。
ドライバー オブジェクトの既定の同期スコープは WdfSynchronizationScopeNone です。 デバイス オブジェクトとキュー オブジェクトの既定の同期スコープは WdfSynchronizationScopeInheritFromParent です。
フレームワークですべてのデバイスにデバイス レベルの同期を提供する場合は、次の手順を使用できます。
ドライバーのドライバー オブジェクトの WDF_OBJECT_ATTRIBUTES 構造体で SynchronizationScope を WdfSynchronizationScopeDevice に設定します。
各デバイス オブジェクトの既定の WdfSynchronizationScopeInheritFromParent 値を使用します。
または、個々のデバイスにデバイス レベルの同期を提供するには、次の手順を使用できます。
ドライバー オブジェクトの既定の WdfSynchronizationScopeNone 値を使用します。
個々のデバイス オブジェクトの WDF_OBJECT_ATTRIBUTES 構造体で SynchronizationScope を WdfSynchronizationScopeDevice に設定します。
フレームワークでデバイスのキュー レベルの同期を提供する場合は、次の手法を使用できます。
フレームワーク バージョン 1.9 以降では、キュー オブジェクトの WDF_OBJECT_ATTRIBUTES 構造体で WdfSynchronizationScopeQueue を設定することで、個々のキューのキュー レベルの同期を有効にする必要があります。 この手法の使用をお勧めします。
または、すべてのフレームワーク バージョンで次の手順を使用できます。
- デバイス オブジェクトの WDF_OBJECT_ATTRIBUTES 構造体で SynchronizationScope を WdfSynchronizationScopeQueue に設定します。
- 各デバイスのキュー オブジェクトの既定の WdfSynchronizationScopeInheritFromParent 値を使用します。
フレームワークでドライバーの I/O 要求を処理するコールバック関数を同期させたくない場合は、ドライバーのドライバー、デバイス、キュー オブジェクトの既定の SynchronizationScope 値を使用します。 この場合、フレームワークはドライバーの I/O 要求関連のコールバック関数を自動的に同期せず、IRQL <= DISPATCH_LEVEL でコールバック関数を呼び出すことができます。
SynchronizationScope 値を設定すると、前のテーブルに含まれるコールバック関数のみが同期されることに注意してください。 フレームワークでドライバーの割り込み、DPC、作業項目、およびタイマー オブジェクトのコールバック関数も同期する場合、ドライバーは、これらのオブジェクトの構成構造体の AutomaticSerialization メンバーを TRUE に設定する必要があります。
ただし、AutomaticSerialization を TRUE に設定できるのは、同期するすべてのコールバック関数が同じ IRQL で実行される場合のみです。 次に説明する実行レベルを選択すると、互換性のない IRQL レベルになる可能性があります。 このような状況では、ドライバーは、AutomaticSerialization を設定する代わりにフレームワーク ロックを使用する必要があります。 割り込み、DPC、作業項目、タイマー オブジェクトの構成構造の詳細、およびこれらの構造体での AutomaticSerialization の設定に適用される制限の詳細については、「WDF_INTERRUPT_CONFIG」、「WDF_DPC_CONFIG」、「WDF_WORKITEM_CONFIG」、および「WDF_TIMER_CONFIG」を参照してください。
AutomaticSerialization を TRUE に設定した場合は、キュー レベルの同期を選択する必要があります。
実行レベルの選択
ドライバーは、いくつかの種類のフレームワーク オブジェクトを作成するときに、オブジェクトの実行レベルを指定できます。 実行レベルでは、フレームワークがドライバーの I/O 要求を処理するオブジェクトのイベント コールバック関数を呼び出す IRQL を指定します。
ドライバーが実行レベルを提供する場合、指定されたレベルは、キューおよびファイル オブジェクトのコールバック関数に影響します。 通常、ドライバーが自動同期を使用している場合、フレームワークは IRQL = DISPATCH_LEVEL でこれらのコールバック関数を呼び出します。 実行レベルを指定することで、ドライバーは IRQL = PASSIVE_LEVEL でこれらのコールバック関数を呼び出すフレームワークを強制できます。 フレームワークでは、キューとファイル オブジェクトのコールバック関数が呼び出される IRQL を設定するときに、次の規則が使用されます。
ドライバーが自動同期を使用する場合、IRQL = PASSIVE_LEVEL でコールバック関数を呼び出すようにドライバーがフレームワークに要求しない限り、そのキューとファイル オブジェクトのコールバック関数は IRQL = DISPATCH_LEVEL で呼び出されます。
ドライバーが自動同期を使用せず、実行レベルを指定しない場合は、IRQL <= DISPATCH_LEVEL でドライバーのキューとファイル オブジェクトのコールバック関数を呼び出すことができます。
ドライバーがファイル オブジェクトコールバック関数を提供する場合、ほとんどの場合、ファイル名などの一部のファイル データはページング可能であるため、IRQL = PASSIVE_LEVEL でこれらのコールバック関数を呼び出すフレームワークが必要になります。
実行レベルを指定するには、ドライバーは、オブジェクトの WDF_OBJECT_ATTRIBUTES 構造体の ExecutionLevel メンバーの値を指定する必要があります。 ドライバーで指定できる実行レベルの値は次のとおりです。
WdfExecutionLevelPassive
フレームワークは、IRQL = PASSIVE_LEVEL でオブジェクトのコールバック関数を呼び出します。
WdfExecutionLevelDispatch
フレームワークは、IRQL <= DISPATCH_LEVEL でオブジェクトのコールバック関数を呼び出すことができます。 (通常、ドライバーが自動同期を使用している場合、フレームワークは IRQL = DISPATCH_LEVEL で常にコールバック関数を呼び出します。)
WdfExecutionLevelInheritFromParent
フレームワークは、オブジェクトの親からオブジェクトの ExecutionLevel 値を取得します。
ドライバー オブジェクトの既定の実行レベルは WdfExecutionLevelDispatch です。 他のすべてのオブジェクトの既定の実行レベルは WdfExecutionLevelInheritFromParent です。
実行レベルの値の詳細については、「WDF_EXECUTION_LEVEL」を参照してください。
次の表は、フレームワークがキュー オブジェクトとファイル オブジェクトのドライバーのコールバック関数を呼び出すことができる IRQL レベルを示しています。
同期スコープ | 実行レベル | キューおよびファイル コールバック関数の IRQL |
---|---|---|
WdfSynchronizationScopeDevice |
WdfExecutionLevelPassive |
PASSIVE_LEVEL |
WdfSynchronizationScopeDevice |
WdfExecutionLevelDispatch |
DISPATCH_LEVEL |
WdfSynchronizationScopeQueue |
WdfExecutionLevelPassive |
PASSIVE_LEVEL |
WdfSynchronizationScopeQueue |
WdfExecutionLevelDispatch |
DISPATCH_LEVEL |
WdfSynchronizationScopeNone |
WdfExecutionLevelPassive |
PASSIVE_LEVEL |
WdfSynchronizationScopeNone |
WdfExecutionLevelDispatch |
<= DISPATCH_LEVEL |
ドライバー、デバイス、ファイル、キュー、タイマー、および一般的なオブジェクトの実行レベルを WdfExecutionLevelPassive または WdfExecutionLevelDispatch に設定できます。 その他のオブジェクトの場合、WdfExecutionLevelInheritFromParent のみが許可されます。
次の場合は、WdfExecutionLevelPassive を指定する必要があります。
ドライバーのコールバック関数は、IRQL = PASSIVE_LEVEL でのみ呼び出すことができるフレームワーク メソッドまたは Windows ドライバー モデル (WDM) ルーチンを呼び出す必要があります。
ドライバーのコールバック関数は、ページング可能なコードまたはデータにアクセスする必要があります。 (たとえば、ファイル オブジェクト コールバック関数は通常、ページング可能なデータにアクセスします。)
WdfExecutionLevelPassive を設定する代わりに、ドライバーは WdfExecutionLevelDispatch を設定し、IRQL = PASSIVE_LEVEL で一部の操作を処理する必要がある場合に作業項目を作成するコールバック関数を提供できます。
ドライバーでオブジェクトの実行レベルを WdfExecutionLevelPassive に設定するかどうかを決定する前に、ドライバーとドライバー スタック内の他のドライバーが呼び出される IRQL を決定する必要があります。 以下のような状況を考慮してください。
ドライバーがカーネルモード ドライバー スタックの最上位にある場合、システムは通常、IRQL = PASSIVE_LEVEL でドライバーを呼び出します。 このようなドライバーのクライアントは、UMDF ベースのドライバーまたはユーザーモード アプリケーションである可能性があります。 WdfExecutionLevelPassive を指定しても、ドライバーのパフォーマンスに悪影響を与えることはありません。フレームワークは、IRQL = PASSIVE_LEVEL で呼び出される作業項目に対するドライバーの呼び出しをキューに登録する必要がないためです。
ドライバーがスタックの一番上にない場合、システムは IRQL = PASSIVE_LEVEL でドライバーを呼び出さない可能性があります。 そのため、フレームワークは、後で IRQL = PASSIVE_LEVEL で呼び出される作業項目に対するドライバーの呼び出しをキューに入れる必要があります。 このプロセスにより、ドライバーのコールバック関数を IRQL <= DISPATCH_LEVEL で呼び出すことができる場合と比較して、ドライバーのパフォーマンスが低下する可能性があります。
DPC オブジェクトの場合、およびパッシブ レベルのタイマーを表さないタイマー オブジェクトの場合、親デバイスの実行レベルを WdfExecutionLevelPassive に設定している場合は、構成構造の AutomaticSerialization メンバーを TRUE に設定できないことに注意してください。 これは、フレームワークが IRQL = PASSIVE_LEVEL でデバイス オブジェクトのコールバック同期ロックを取得するため、IRQL = DISPATCH_LEVEL で実行する必要がある DPC またはタイマー オブジェクトのコールバック関数を同期するためにロックを使用できないためです。 このような場合、ドライバーは、相互に同期する必要がある任意のデバイス、DPC、またはタイマー オブジェクトのコールバック関数でフレームワーク スピン ロックを使用する必要があります。
また、パッシブ レベルのタイマーを表すタイマー オブジェクトの場合は、親デバイスの実行レベルが WdfExecutionLevelPassive に設定されている場合にのみ、構成構造の AutomaticSerialization メンバーを TRUE に設定できます。