デバイス キューの管理

ドライバーが IoCreateDevice を呼び出すと、I/O マネージャーは通常 (FSD を除く) 関連付けられたデバイス キュー オブジェクトを作成します。 また、IoStartPacketIoStartNextPacket も提供し、ドライバーはこれらを呼び出すことで、I/O マネージャーが関連付けられているデバイス キューに IRP を挿入したり、StartIo ルーチンを呼び出したりすることができます。

そのため、ドライバーが IRP 用に独自のデバイス キュー オブジェクトを設定する必要 (あるいはそれが特段有用であるということ) はほとんどありません。 これが必要となることが考えられるドライバーは、SCSI ポート ドライバーのようなドライバーで、1 つのコントローラーやバス アダプターを通してサービスが提供される異種デバイスのために、密接に結合されたいくつかのクラス ドライバーからの IRP の入力を調整しなければならないようなドライバーです。

言い換えると、ディスク アレイ コントローラーのドライバーは、補助デバイス キュー オブジェクトを設定するよりも、ドライバーによって作成されたコントローラー オブジェクトを使用する可能性が高く、アドオン バス アダプターや一連のクラス ドライバーのドライバーは、補助デバイス キューを使用する可能性が若干高くなります。

StartIo ルーチンでの補助デバイス キューの使用

IoStartPacketIoStartNextPacket を呼び出すことによって、ドライバーの Dispatch および DpcForIsr (または CustomDpc) ルーチンは、ドライバーがデバイス オブジェクトを作成したときに I/O マネージャーによって作成されたデバイス キューを使用して、StartIo ルーチンへの呼び出しを同期します。 StartIo ルーチンを持つポート ドライバーの場合、IoStartPacketIoStartNextPacket は、IRP を、ポート ドライバーの共有デバイス コントローラー/アダプターのデバイス キューへ挿入したり、デバイス キューから削除したりします。 また、ポート ドライバーが、密接に結合された上位レベルのクラス ドライバーからの要求を保持するように補助デバイス キューを設定する場合は、着信 IRP を補助デバイス キュー (通常は StartIo ルーチン) に "並べ替える" 必要があります。

ポート ドライバーは、適切なキューにその IRP を挿入する前に、各 IRP がどの補助デバイス キューに属しているかを特定する必要があります。 ターゲット デバイス オブジェクトへのポインターは、ドライバーの Dispatch ルーチンに IRP と共に渡されます。 ドライバーは、受信 IRP の "並べ替え" で使用するポインターを保存する必要があります。 StartIo ルーチンに渡されるデバイス オブジェクト ポインターは、デバイス コントローラー/アダプターを表すドライバー独自のデバイス オブジェクトであるため、この目的には使用できないことに注意してください。

IRP をキューに登録した後、ドライバーは共有コントローラー/アダプターをプログラムし、要求を実行します。 このようにして、ポート ドライバーは、KeInsertDeviceQueue への呼び出しによって IRP が、特定のクラス ドライバーのデバイス キューに入れられるまで、先着順ですべてのデバイスの受信要求を処理することができます。

StartIo ルーチンを介して処理されるすべての IRP に対して独自のデバイス キューを使用することで、基になるポート ドライバーは、共有デバイス (またはバス) コントローラー/アダプターを介して、接続されているすべてのデバイスに対する操作をシリアル化します。 サポートされている各デバイスの IRP を個別のデバイス キューに保持することで、このポート ドライバーは、共有ハードウェアを介して I/O を実行するすべてのデバイスの I/O スループットを向上させながら、既にビジー状態のデバイスの IRP の処理を抑制します。

ポート ドライバーの Dispatch ルーチンからの IoStartPacket の呼び出しに応答して、I/O マネージャーは、そのドライバーの StartIo ルーチンを直ちに呼び出すか、ポート ドライバーの共有コントローラー/アダプターのデバイス オブジェクトに関連付けられているデバイス キューに IRP を入れます。

ポート ドライバーは、共有デバイス コントローラー/アダプターを通じてサービスを提供する異種デバイスのそれぞれについて、ドライバー自身の状態情報を保持する必要があります。

補足デバイス キューを使用してクラス/ポート ドライバーを設計する場合は、次の点に注意してください。

  • ドライバーは、デバイス スタックの最上位にあるデバイス オブジェクトを除き、それ自身よりも上位にあるドライバーによって作成されたデバイス オブジェクトへのポインターを簡単に取得することはできません。

    設計上、I/O マネージャーでは、このようなポインターを取得するためのサポート ルーチンは提供されません。 さらに、ドライバーが読み込まれる順序により、下位レベルのドライバーは、デバイスを追加するときにまだ作成されていない上位レベルのドライバーのデバイス オブジェクトのポインターを取得できなくなります。

    IoGetAttachedDeviceReference はドライバーのスタック内の最上位レベルのデバイス オブジェクトへのポインターを返しますが、ドライバーはこのポインターを、そのスタックへの I/O 要求のターゲットを指定するためだけに使用する必要があります。 ドライバーは、デバイス オブジェクトの読み取りまたは書き込みを試行してはなりません。

  • ドライバーは、それ自身のデバイス スタックの先頭に要求を送信する場合を除き、それ自身の上に階層化されたドライバーによって作成されたデバイス オブジェクトへのポインターを使用できません。

    マルチプロセッサ セーフな方法で、2 つのドライバー間で 1 つのデバイス オブジェクト (およびそのデバイス拡張機能) へのアクセスを同期する方法はありません。 どちらのドライバーも、もう一方のドライバーが現在どのような I/O 処理を行っているかについて推測することはできません。

密接に結合されたクラス/ポート ドライバーの場合でも、各クラス ドライバーは、IoCallDriver を使用して IRP を渡す場合にのみ、ポート ドライバーのデバイス オブジェクトへのポインターを使用する必要があります。 基になるポート ドライバーは、そのデバイス拡張において、密接に結合されたクラス ドライバーのデバイスに対して処理する要求に関する、自身の状態を保持する必要があります。

ドライバー ルーチン間での補助デバイス キューの管理

密接に結合された一連のクラス ドライバーの補助デバイス キューに IRP を入れたポート ドライバーは、次の状況も効率的に処理する必要があります。

  1. Dispatch ルーチンによって、特定のデバイスの IRP が、そのデバイスのドライバーによって作成されたデバイス キュー内に挿入されています。

  2. その他のデバイス用の IRP は、IoStartPacket を使用してドライバーの StartIo ルーチンにキューイングされ、共有デバイス コントローラーを介して処理されるように、引き続き入ってきます。

  3. デバイス コントローラーはアイドル状態になりませんが、ドライバーが作成したデバイス キューに保持されている各 IRP も、できるだけ早くドライバーの StartIo ルーチンにキューイングされる必要があります。

したがって、ポート ドライバーの DpcForIsr ルーチンは、次のようにして、ポート ドライバーが IRP を完了するたびに、特定のデバイスのドライバーの内部デバイス キューから共有アダプター/コントローラーのデバイス キューに IRP を転送する必要があります。

  1. DpcForIsr ルーチンは IoStartNextPacket を呼び出して、StartIo ルーチンに、共有デバイス コントローラーのキューに登録されている次の IRP の処理を開始させます。

  2. DpcForIsr ルーチンは、KeRemoveDeviceQueue を呼び出して、IRP を完了しようとしているデバイスのための内部デバイス キューに保持されている、次の IRP (ある場合) をデキューします。

  3. KeRemoveDeviceQueue が NULL 以外のポインターを返す場合、DpcForIsr ルーチンは、デキューされたばかりの IRP で IoStartPacket を呼び出し、共有デバイス コントローラー/アダプターキューイングさせます。 それ以外の場合、KeRemoveDeviceQueue 呼び出しは単にデバイス キュー オブジェクトの状態をビジーではない状態にリセットし、DpcForIsr ルーチンは IoStartPacket の呼び出しを省略します。

  4. 次に、DpcForIsr ルーチンは、エラーで I/O 状態ブロックを設定するか、I/O 要求を満たすことによって、ポート ドライバーが I/O 処理を完了したばかりの入力 IRP で IoCompleteRequest を呼び出します。

上記のシーケンスは、DpcForIsr ルーチンが、IRP の内部でのキューイングを効率的に管理するために、完了している現在の (入力) IRP がどのデバイスのものかも特定する必要があることを意味します。

ポート ドライバーが、補助デバイス キューに保持されている IRP をデキューする前に、共有コントローラー/アダプターがアイドル状態になるまで待機しようとすると、ドライバーは、現在の I/O 要求が実際にははるかに軽いデバイスには迅速にサービスを提供する一方で、大きな I/O 要求があるデバイスを枯渇状態にする可能性があります。