キャンセル セーフの IRP キュー
独自の IRP キューを実装するドライバーは、 キャンセル セーフ IRP キュー フレームワークを使用する必要があります。 キャンセル セーフな IRP キューは、IRP 処理を次の 2 つの部分に分割します。
ドライバーは、ドライバーの IRP キューに標準操作を実装するコールバック ルーチンのセットを提供します。 提供される操作には、キューからの IRP の挿入と削除、キューのロックとロック解除が含まれます。 キャンセルセーフ IRP キュー実装を参照してください。
ドライバーが実際に挿入またはキューから IRP を削除する必要がある場合は常に、システム提供 の IoCsqXxx ルーチンを使用します。 これらのルーチンは、ドライバーのすべての同期と IRP キャンセル ロジックを処理します。
キャンセル セーフな IRP キューを使用するドライバーは、IRP 取り消しをサポートするために Cancel ルーチンを実装しません。
フレームワークにより、ドライバーがキューから IRP をアトミックに挿入および削除することが保証されます。 また、IRP の取り消しが正しく実装されていることも確認します。 フレームワークを使用しないドライバーは、挿入と削除を実行する前に、キューを手動でロックおよびロック解除する必要があります。 また、Cancel ルーチンの実装時に発生する可能性のある競合状態を回避する必要があります。 (発生する可能性がある競合状態の説明については 、IRP の取り消しを同期しています。)
キャンセル セーフな IRP キュー フレームワークは、Windows XP 以降のバージョンの Windows に含まれています。 Windows 2000 および Windows 98/Me でも動作する必要があるドライバーは、Windows Driver Kit (WDK) に含まれている Csq.lib ライブラリにリンクできます。 Csq.lib ライブラリは、このフレームワークの実装を提供します。
IoCsqXxx ルーチンは、Windows XP 以降のバージョンの Wdm.h および Ntddk.h で宣言されています。 Windows 2000 および Windows 98/Me でも動作する必要があるドライバーには、宣言に Csq.h を含める必要があります。
WDK の \src\general\cancel ディレクトリで、キャンセル セーフな IRP キューを使用する方法の完全なデモンストレーションを確認できます。 これらのキューの詳細については、「キャンセルセーフ IRP キューの制御フロー」ホワイト ペーパーも参照してください。
キャンセルセーフ IRP キューの実装を参照してください。
キャンセル セーフ IRP キューを実装するには、ドライバーは、次のルーチンを提供する必要があります。
キューに IRP を挿入するルーチンは、CsqInsertIrp または CsqInsertIrpEx のいずれかです。 CsqInsertIrpEx は CsqInsertIrp の拡張バージョンです。キューは、いずれかを使用して実装されます。
指定した IRP をキューから削除する CsqRemoveIrp ルーチン。
キュー内の指定した IRP に続く次の IRP へのポインターを返す CsqPeekNextIrp ルーチン。 ここで、システムは IoCsqRemoveNextIrp から受け取る PeekContext 値を渡します。 ドライバーは、どのような方法でもその値を解釈できます。
システムが IRP キューをロックおよびロック解除できるようにするための次のルーチンの両方: CsqAcquireLock と CsqReleaseLock.
取り消された IRP を完了する CsqCompleteCanceledIrp ルーチン。
ドライバーのルーチンへのポインターは、キューを記述する IO_CSQ 構造体に格納されます。 ドライバーは、IO_CSQ 構造体の記憶域を割り当てます。 IO_CSQ構造体は固定サイズメイン保証されているため、ドライバーはデバイス拡張機能内に構造体を安全に埋め込むことができます。
ドライバーは、IoCsqInitialize または IoCsqInitializeEx を使用して構造体を初期化します。 キューが IoCsqInitialize を実装する場合は IoCsqInitializeEx を使用し、キューが CsqInsertIrp を実装する場合は CsqInsertIrpEx を使用します。
ドライバーは、各コールバック ルーチンに不可欠な機能のみを提供する必要があります。 たとえば、CsqAcquireLock ルーチンと CsqReleaseLock ルーチンのみがロック処理を実装します。 システムはこれらのルーチンを自動的に呼び出して、必要に応じてキューをロックおよびロック解除します。
適切なディスパッチ ルーチンが提供されている限り、任意の種類の IRP キュー メカニズムをドライバーに実装できます。 たとえば、ドライバーは、リンクされたリストとして、または優先順位キューとしてキューを実装できます。
CsqInsertIrpEx は、CsqInsertIrp よりも柔軟なキューへのインターフェイスを提供します。 ドライバーは、その戻り値を使用して、操作の結果を示すことができます。エラー コードが返された場合、挿入に失敗しました。 CsqInsertIrp ルーチンは値を返さないため、挿入に失敗したことを示す簡単な方法はありません。 また、 CsqInsertIrpEx は、キューの実装で使用される追加のドライバー固有の情報を指定するために使用できる追加のドライバー定義 の InsertContext パラメーターを受け取ります。
ドライバーは、CsqInsertIrpEx を使用して、より高度な IRP 処理を実装できます。 たとえば、保留中の IRP がない場合、 CsqInsertIrpEx ルーチンはエラー コードを返すことができますし、ドライバーは IRP をすぐに処理できます。 同様に、IRP をキューに登録できなくなった場合、 CsqInsertIrpEx はエラー コードを返して、その事実を示すことができます。
ドライバーは、すべての IRP キャンセル処理から絶縁されます。 システムは、キュー内の IRP のCancel ルーチンを提供します。 このルーチンは CsqRemoveIrp を呼び出してキューから IRP を削除し、CsqCompleteCanceledIrp を呼び出して IRP の取り消しを完了します。
次の図は、IRP の取り消しの制御フローを示しています。
CsqCompleteCanceledIrp の基本的な実装は次のとおりです。
VOID CsqCompleteCanceledIrp(PIO_CSQ Csq, PIRP Irp) {
Irp->IoStatus.Status = STATUS_CANCELLED;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
}
ドライバーは、オペレーティング システムの同期プリミティブのいずれかを使用して、CsqAcquireLock ルーチンとCsqReleaseLock ルーチンを実装できます。 使用可能な同期プリミティブには、スピン ロック と ミューテックス オブジェクト が含まれます。
ドライバーがスピン ロックを使用してロックを実装する方法の例を次に示します。
/*
The driver has previously initialized the SpinLock variable with
KeInitializeSpinLock.
*/
VOID CsqAcquireLock(PIO_CSQ IoCsq, PKIRQL PIrql)
{
KeAcquireSpinLock(SpinLock, PIrql);
}
VOID CsqReleaseLock(PIO_CSQ IoCsq, KIRQL Irql)
{
KeReleaseSpinLock(SpinLock, Irql);
}
システムは IRQL 変数 へのポインターを CsqAcquireLock および CsqReleaseLock に渡します。 ドライバーがスピン ロックを使用してキューのロックを実装する場合、ドライバーはこの変数を使用して、キューがロックされているときに現在の IRQL を格納できます。
ドライバーはスピン ロックを使用する必要はありません。 たとえば、ドライバーはミューテックスを使用してキューをロックできます。 ドライバーで使用できる同期手法の詳細については、「同期手法」を参照してください。
キャンセル セーフ IRP キューの使用
ドライバーは、IRP をキューに登録およびデキューするときに、次のシステム ルーチンを使用します。
キューに IRP を挿入するには、次のいずれかを実行します。IoCsqInsertIrp または IoCsqInsertIrpEx。
キュー内の次の IRP を削除する IoCsqRemoveNextIrp。 ドライバーは必要に応じてキー値を指定できます。
次の図は、IoCsqRemoveNextIrp の制御フローを示しています。
- 指定した IRP をキューから削除する IoCsqRemoveIrp。
次の図は、IoCsqRemoveIrp の制御フローを示しています。
これらのルーチンは、ドライバーが提供するルーチンにディスパッチします。
CsqInsertIrpEx ルーチンは、IoCsqInsertIrpEx ルーチンの拡張機能へのアクセスを提供します。 CsqInsertIrpEx によって 返された状態値を返します。 呼び出し元は、この値を使用して、IRP が正常にキューに登録されたかどうかを判断できます。 IoCsqInsertIrpEx を使用すると、呼び出し元は CsqInsertIrpEx の InsertContext パラメーターの 値を指定することもできます。
IoCsqInsertIrp と IoCsqInsertIrpEx は、キューに CsqInsertIrp ルーチンまたは CsqInsertIrpEx ルーチンがあるかどうかに関係なく、任意のキャンセル セーフ キューで呼び出すことができます。 IoCsqInsertIrp はどちらの場合も同じように動作します。 IoCsqInsertIrpEx が CsqInsertIrp ルーチンを持つキューに渡された場合、IoCsqInsertIrp と同じように動作します。
次の図は、IoCsqInsertIrp の制御フローを示しています。
次の図は、IoCsqInsertIrpEx の制御フローを示しています。
IoCsqXxx ルーチンを使用して IRP をキューに入れ、デキューするには、いくつかの自然な方法があります。 たとえば、ドライバーは、受信した順序で処理される IRP をキューに入れるだけで済みます。 ドライバーは、次のように IRP をキューに入れる可能性があります。
status = IoCsqInsertIrpEx(IoCsq, Irp, NULL, NULL);
ドライバーが特定の IRP を区別する必要がない場合は、次のようにキューに入れた順序で単純にデキューできます。
IoCsqRemoveNextIrp(IoCsq, NULL);
または、ドライバーは特定の IRP をキューに入れ、デキューできます。 ルーチンは、不透明な IO_CSQ_IRP_CONTEXT 構造を使用して、キュー内の特定の IRP を識別します。 ドライバーは、次のように IRP をキューに入れます。
IO_CSQ_IRP_CONTEXT ParticularIrpInQueue;
IoCsqInsertIrp(IoCsq, Irp, &ParticularIrpInQueue);
その後、ドライバーは、IO_CSQ_IRP_CONTEXT 値を使用して同じ IRP をデキューできます。
IoCsqRemoveIrp(IoCsq, Irp, &ParticularIrpInQueue);
ドライバーは、特定の条件に基づいてキューから IRP を削除する必要がある場合もあります。 たとえば、ドライバーは、優先順位の高い IRP が最初にデキューされるように、各 IRP に優先順位を関連付けることができます。 ドライバーは、PeekContext 値を IoCsqRemoveNextIrp に渡す可能性があります。この値は、システムがキュー内の次の IRP を要求したときにドライバーに戻されます。