IRP をキャンセルするときに考慮すべき点
このセクションでは、キャンセル ルーチンを実装し、キャンセル可能な IRP を処理するためのガイドラインについて説明します。 キャンセル可能な IRP の処理の詳細については、「キャンセルセーフ IRP キューの制御フロー」を参照してください。
すべてのキャンセル ルーチンの一般的なガイドライン
I/O マネージャーは、ドライバーのキャンセル ルーチンを呼び出すたびにキャンセル スピン ロックを保持します。 そのため、すべてのキャンセル ルーチンは次の操作を行う必要があります。
制御を返す前に IoReleaseCancelSpinLock を呼び出します。
IoReleaseCancelSpinLock を必ず最初に呼び出してから、IoAcquireCancelSpinLock を呼び出してください。
IoAcquireCancelSpinLock に対して行う呼び出しごとに、IoReleaseCancelSpinLock に対して相互呼び出しを行います。
キャンセル ルーチンは、IoReleaseCancelSpinLock を呼び出すたびに、最新の呼び出しによって返される IRQL を IoAcquireCancelSpinLock に渡す必要があります。 I/O マネージャーによって取得されたスピン ロックを解除 (およびキャンセル ルーチンが呼び出されたときに保持) する場合、キャンセル ルーチンは Irp->CancelIrql を渡す必要があります。
デッドロックが発生する可能性があるため、スピン ロックを保持している間、ドライバーは外部ルーチン (IoCompleteRequest など) を呼び出すことはできません。
I/O マネージャーによって定義されたキューの使用
ドライバーが IRP の独自の内部キューを管理しない限り、その キャンセル ルーチンは、次のいずれかの可能性がある受信 IRP で呼び出されます。
入力ターゲット デバイス オブジェクトの CurrentIrp
ターゲット デバイス オブジェクトに関連付けられているデバイス キュー内のエントリ
ドライバーが IRP の独自の内部キューを管理しない限り、そのキャンセル ルーチンは、入力 IRP で KeRemoveEntryDeviceQueue を呼び出して、ターゲット デバイス オブジェクトに関連付けられているデバイス キュー内のエントリであるかどうかをテストする必要があります。 ドライバーのキャンセル ルーチンは、KeRemoveDeviceQueue または KeRemoveByKeyDeviceQueue を呼び出すことはできません。これは、特定の IRP のデバイス キュー内での具体的な位置を想定できないためです。
入力 IRP の現在の状態
ドライバーが既に I/O 処理を開始している IRP でキャンセル ルーチンが呼び出され、要求が間もなく完了する場合、キャンセル ルーチンは、システムのキャンセル スピン ロックを解除し、制御を返す必要があります。
入力 IRP の現在の状態が保留中の場合、キャンセル ルーチンは、次の操作を行う必要があります。
入力 IRP の I/O 状態ブロックを、Status に STATUS_CANCELLED を、Information にゼロを指定して設定します。
システムのキャンセル スピン ロックを含め、保持しているスピン ロックを解除します。
指定された IRP を使用して IoCompleteRequest を呼び出します。
キャンセル可能な状態の IRP の保持
キャンセル可能な状態で IRP を保持するドライバー ルーチンは、IoMarkIrpPending を呼び出す必要があり、IRP のキャンセル ルーチンのエントリ ポイントを設定するために IoSetCancelRoutine を呼び出す必要があります。 そのドライバー ルーチンが、IoStartPacket、IoAllocateController、ExInterlockedInsert ルーチンなどの追加のサポート ルーチンを呼び出せるのは、その後に限ります。
キャンセル可能な IRP をその後処理するドライバー ルーチンは、要求を満たすための操作を開始する前に、IRP が既にキャンセルされているかどうかをチェックする必要があります。 このルーチンは、IRP のキャンセル ルーチンのエントリ ポイントを NULL にリセットするため、IoSetCancelRoutine を呼び出す必要があります。 そのルーチンが入力 IRP の I/O 処理を開始できるのは、その後に限ります。
ルーチンは、他のドライバー ルーチンによるその後処理のために IRP を渡す場合も、IRP のキャンセル ルーチンのエントリ ポイントのリセットが必要な場合があり、これらの IRP はキャンセル可能な状態に保持される可能性があります。
キャンセル可能な状態で IRP を保持する上位レベルのドライバーは、IoCallDriver で次の下位ドライバーに IRP を渡す前に、キャンセル エントリ ポイントを NULL にリセットする必要があります。
IRP のキャンセル
上位レベルのドライバーは、下位レベルのドライバーがその後処理するために割り当てて、渡した IRP を使用して IoCancelIrp を呼び出すことができます。 ただし、このようなドライバーは、下位ドライバーが指定された IRP を STATUS_CANCELLED で完了することは想定できません。
同期
ドライバーは、デバイス拡張機能に追加の状態情報を保持して、IRP のキャンセル可能な状態を追跡できます (または、その設計によっては必須です)。 IRQL <= DISPATCH_LEVEL で実行されているドライバー ルーチンによってこの状態が共有されている場合、共有データは、ドライバーが割り当て、初期化したスピン ロックで保護する必要があります。
ドライバーは、システムのキャンセル スピン ロックと独自のスピン ロックの取得と解除を慎重に管理する必要があります。 可能な限り短い間隔でシステムのキャンセル スピン ロックを保持する必要があります。 キャンセル可能な IRP にアクセスする前に、このようなドライバーは常に IoSetCancelRoutine の戻り値をチェックして、キャンセル ルーチンが既に実行中か (または実行しようとしているか) どうかを判断する必要があります。そうである場合は、キャンセル ルーチンに IRP を完了させる必要があります。
さまざまなドライバー ルーチンが ISR と共有するキャンセル可能な IRP に関する状態情報をデバイス ドライバーが維持している場合、これらの他のルーチンは、ISR と共有する状態へのアクセスを同期する必要があります。 ドライバーが提供する SynchCritSection ルーチンのみが、マルチプロセッサ セーフの方法で ISR と共有されている状態情報にアクセスできます。
詳細については、「同期手法」を参照してください。