最下位レベルドライバーのStartIo ルーチン
ドライバーのディスパッチ ルーチンに対する I/O マネージャーの呼び出しは、デバイス I/O 要求を満たす最初の段階です。 StartIo ルーチンは第2段階です。 StartIo ルーチンを持つすべてのデバイスドライバは、 IoStartPacket を DispatchRead および DispatchWrite ルーチンから呼び出し、通常は DispatchDeviceControl ルーチンでサポートする I/O 制御コードのサブセットに対して呼び出しが行われます。 IoStartPacket ルーチンは、IRPをシステムが提供するデバイスキューに追加するか、キューが空の場合は、直ちにドライバの StartIo ルーチンを呼び出してIRPを処理します。
ドライバの StartIo ルーチンが呼び出されたとき、ターゲットデバイスはビジー状態ではないと仮定できます。 これは、I/Oマネージャーが2つの状況で StartIo を呼び出すからです。ドライバのディスパッチルーチンの 1 つが IoStartPacket を呼び出したばかりでデバイスキューが空であったか、ドライバの DpcForIsr ルーチンが別の要求を完了し、次の IRP をデキューするために IoStartNextPacket を呼び出したばかりであったかのいずれかです。
最上位レベルのデバイスドライバの StartIo ルーチンが呼び出される前に、そのドライバのディスパッチルーチンは、 StartIo ルーチンにキューイングされた IRP に有効なマップされたバッファリングアドレスを設定するために、必要に応じて、ユーザーバッファリングを調査し、ロックダウンしている必要があります。 最上位レベルのデバイスドライバがそのデバイスオブジェクトをダイレクトI/O用に設定する場合(またはバッファ付きI/OでもダイレクトI/Oでもない場合)、ドライバはその StartIo ルーチンにユーザーバッファのロックを延期することはできません。すべての StartIo ルーチンは、IRQL = DISPATCH_LEVELで任意のスレッドコンテキストで呼び出されます。
注
ドライバの StartIo ルーチンがアクセスするバッファリングメモリは、ロックダウンされているか、常駐しているシステム空間メモリから割り当てられ、任意のスレッドコンテキストでアクセスできなければなりません。
I一般に、下位レベルデバイスドライバの StartIo ルーチンは、入力 IRP で IoGetCurrentIrpStackLocation を呼び出し、そのデバイス上で I/O 操作を開始するために必要なリクエスト固有の処理を行う責任があります。 要求固有の処理には、以下を含むことができます:
ドライバが保持する現在のリクエストに関する状態情報の設定または更新。 状態情報は、ターゲットデバイスオブジェクトのデバイス拡張か、ドライバが割り当てたページングされていないプールの他の場所に格納される場合がありますい。
例えば、デバイスドライバが現在の転送操作に対して InterruptExpected Boolean を保持している場合、 StartIo ルーチンはこの変数を TRUEに設定する場合があります。 ドライバが現在の操作に対してタイムアウトカウンタを保持している場合、その StartIo ルーチンはこの値を設定するかもしれませんし、同 StartIo ルーチンはドライバの CustomTimerDpc ルーチンのキューに入れるかもしれません。
StartIo ルーチンが他のドライバルーチンと状態情報や ハードウェアリソース へのアクセスを共有する場合、状態情報やリソースはスピンロックによって保護されなければなりません。 ( スピンロックを参照してください。)
StartIo ルーチンがドライバの InterruptService ルーチンと状態情報またはリソースへのアクセスを共有する場合、 StartIo は KeSynchronizeExecution を使用して、状態またはリソース情報にアクセスする SynchCritSection ルーチンを呼び出す要があります。 ( クリティカルセクションの使用を参照してください。)
IRPの処理中にドライバがデバイス I/Oエラーを記録する必要がある場合に備えて、IRPにシーケンス番号を割り当てます。
詳細は、 ログインエラー を参照してください。
必要に応じて、ドライバのI/Oスタック位置のパラメータをデバイス固有の値に変換します。
例えば、ディスクドライバは、転送操作のための物理ディスクアドレスに対する開始セクタやバイトオフセット、要求された転送の長さが特定のセクタ境界を越えるかどうか、あるいはその物理デバイスの転送容量を超えるかどうかを計算する必要がある場合があります。
ドライバがリムーバブルメディアデバイスを制御する場合、I/Oのためにデバイスをプログラムする前にメディアの変更をチェックし、メディアが変更された場合、その上にあるファイルシステムに通知します。
詳細については、 リムーバブルメディアのサポートをご覧ください。
デバイスが DMA を使用する場合、要求された Length (転送されるバイト数で、IRP のドライバの I/Oスタックの場所にある) が、 Input/Output Techniquesで説明されているように、部分転送オペレーションに分割されるべきかどうかをチェックします。
このようなデバイス ドライバの StartIo ルーチンは、 KeFlushIoBuffers の呼び出し、およびドライバがパケットベースのDMAを使用する場合、 AllocateAdapterChannel を、ドライバの AdapterControl ルーチンで呼び出す役割も担います。
詳細は アダプタオブジェクトと DMAおよび キャッシュの一貫性の維持を参照してください。
デバイスが PIO を使用する場合、 Irp->MdlAddressの IRP に記述されているバッファリングのベース仮想アドレスを、 MmGetSystemAddressForMdlSafeでシステム空間アドレスにマッピングします。
読み取り要求の場合、デバイスドライバの StartIo ルーチンは、PIO 操作を開始する前に KeFlushIoBuffers を呼び出す責任を負うことができます。 詳細については、 キャッシュの一貫性維持 をご覧ください。
WDM以外のドライバがコントローラオブジェクトを使用する場合、 IoAllocateController を呼び出して、 ControllerControl ルーチンを登録します。
ドライバがキャンセル可能なIRPを処理する場合、入力IRPがすでにキャンセルされているかどうかをチェックします。
入力 IRP が完了まで処理される前にキャンセルできる場合、 StartIo ルーチンは、 IoSetCancelRoutine を、IRP とドライバの Cancel ルーチンのエントリポイントを指定して呼び出す必要があります。 StartIo ルーチンは、 IoSetCancelRoutineへの呼び出しのためにキャンセルスピンロックを取得する必要があります。 あるいは、ドライバは、 IoSetStartIoAttributes を使用して、 NonCancelable 属性( 於ける StartIo ルーチン)を TRUEに設定することもできます。 これは、 StartIo に、 IoStartPacketの呼び出しによって渡されたIRPをシステムがキャンセルしようとするのを防ぎます。
一般的なルールとして、バッファリングI/Oを使用するドライバは、ダイレクトI/Oを使用するドライバよりもシンプルな StartIo ルーチンを持ちます。 バッファリングI/O を使用するドライバは、各転送要求に対して少量のデータを転送しますが、ダイレクトI/O を使用するドライバは(DMAまたはPIOにかかわらず)、システムメモリ内の物理ページ境界をまたぐことができるロックダウンされたバッファリングとの間で大量のデータを転送します。
物理デバイスドライバの上に階層化された上位レベルドライバは、通常、それぞれのデバイスドライバと一致するようにデバイスオブジェクトを設定します。 しかし、最上位レベルのドライバ、特にファイルシステムドライバは、直接I/OでもバッファリングI/Oでもないデバイスオブジェクトを設定することができます。
バッファリング付きI/O用にデバイスオブジェクトを設定するドライバは、ドライバに送信するすべてのIRPで有効なバッファリングを渡すうえでI/Oマネージャがに依存できます。 直接I/O用にデバイスオブジェクトを設定する下位レベルのドライバは、中間ドライバを経由して基盤となる下位レベルデバイスドライバに送信されるすべてのIRPで有効なバッファリングを渡すうえで、そのチェーンの最上位レベルのドライバに依存することができます。
StartIo ルーチンでのバッファリングI/O の使用
ドライバの DispatchRead, DispatchWrite, または DispatchDeviceControl ルーチンが、要求が有効であると判断し、 IoStartPacketを呼び出すと、I/Oマネージャはドライバの StartIo ルーチンを呼び出し、デバイスキューが空の場合は直ちにIRPを処理します。 キューが空でなければ、 IoStartPacket はIRPをキューに入れます。 最終的には、 IoStartNextPacket への、ドライバ DpcForIsr または CustomDpc ルーチンからの呼び出しによって、I/O マネージャはIRPをデキューし、ドライバの StartIo ルーチンを呼び出します。
The StartIo ルーチンは IoGetCurrentIrpStackLocation を呼び出し、要求を満たすためにどの操作を実行しなければならないかを決定します。 物理デバイスをプログラミングしてI/O要求を実行する前に、必要な方法でIRPを前処理します。
物理デバイス(またはデバイス拡張)へのアクセスが InterruptService ルーチンと同期されなければならない場合、 StartIo ルーチンは SynchCritSection ルーチンを呼び出し、必要なデバイスプログラミングを実行する必要があります。 詳細については、「クリティカル セクションの使用」を参照してください。
バッファ付き I/O を使用する物理デバイスドライバは Irp->AssociatedIrp.SystemBufferの各IRPでドライバが見つける、I/O マネージャによって割り当てられたシステム空間バッファリングへ、またはシステム空間バッファからデータを転送します。
StartIoルーチンでのダイレクトI/O の使用
ドライバの DispatchRead、 DispatchWrite、 または DispatchDeviceControl ルーチンが要求が有効であると判断し、 IoStartPacketを呼び出すと、I/O マネージャはドライバの StartIo ルーチンを呼び出し、デバイスキューが空の場合は直ちにIRPを処理します。 キューが空でなければ、 IoStartPacket はIRPをキューに入れます。 最終的には、 IoStartNextPacket への、ドライバ DpcForIsr または CustomDpc ルーチンからの呼び出しによって、I/O マネージャはIRPをデキューし、ドライバの StartIo ルーチンを呼び出します。
The StartIo ルーチンは IoGetCurrentIrpStackLocation を呼び出し、要求を満たすためにどの操作を実行しなければならないかを決定します。 例えば、大きなDMA転送要求を部分的な転送範囲に分割したり、分割されなければならない着信転送要求の Length に関する状態を保存したりします。 次に、物理デバイスにI/O要求を実行するようにプログラムします。
物理デバイス(またはデバイス拡張)へのアクセスがドライバのISRと同期されなければならない場合、 StartIo ルーチンはドライバが提供する SynchCritSection ルーチンを使用して必要なプログラミングを実行する必要があります。 詳細については、「クリティカル セクションの使用」を参照してください。
ダイレクトI/Oを使用するドライバは、 Irp->MdlAddressのIRPでドライバが見つけるメモリ記述子リスト(MDL)で記述されたロックダウンされたバッファリングにデータを読み込むか、そこからデータを書き込みます。 このようなドライバは、デバイス制御要求に対してバッファリングI/Oを使用するのが一般的です。 詳細については、 StartIoルーチンでのI/O制御要求の処理を参照してください。
MDLは、ドライバが直接アクセスしない不透明な種類です。 その代わりに、PIO を使用するドライバは、 MmGetSystemAddressForMdlSafe を、 パラメータ Irp->MdlAddress により呼び出すことで、ユーザー空間バッファリングの再マップを行います。 DMAを使用するドライバは、転送動作中にサポートルーチンに Irp->MdlAddress を渡して、バッファリングアドレスをデバイスの論理範囲に再マップさせることもできます。
密接に結合された上位レベルドライバが、基盤となるデバイスドライバのために大きなDMA転送要求を分割しない限り、最下位レベルのデバイスドライバの StartIo ルーチンは、そのデバイスが1回の転送操作で管理できるよりも大きな各転送要求を分割しなければなりません。 システムDMAを使用するドライバは、システムDMAコントローラやそのデバイスが1回の転送操作で処理するには大きすぎる転送要求を分割する必要があります。
デバイスが下位のDMAデバイスである場合、そのドライバは、DMAチャネルを表すドライバ割り当てのアダプタオブジェクトと、ドライバが提供する AdapterControl ルーチンを使用して、システムDMAコントローラ経由で転送を同期する必要があります。 バスマスタDMAデバイスのドライバも、その転送を同期するためにドライバ割り当てアダプタオブジェクトを使用し、システムのパケットベースDMAサポートを使用する場合は AdapterControl ルーチンを、システムのスキャッタ/ギャザ(散布/収集)サポートを使用する場合は AdapterListControl ルーチンを供給する必要があります。
ドライバの設計によっては、物理デバイス上の転送とデバイス制御操作をコントローラオブジェクトと同期させ、 ControllerControl ルーチンを供給することがあります。
詳細は アダプタオブジェクトとDMA および コントローラオブジェクト を参照してください。
StartIoルーチンでのI/O制御要求の処理
一般に、ドライバの DispatchDeviceControl または DispatchInternalDeviceControl ルーチンから、ドライバの StartIo ルーチンでさらに処理するために渡されるのは、デバイスI/O制御要求のサブセットのみです。 ドライバの StartIo ルーチンは、デバイス状態の変更を必要とする、または現在のデバイス状態に関し不安定な情報を返す有効なデバイス制御要求だけを処理することになります。
各新しいドライバは、同じ種類のデバイスに対する他のすべてのドライバと同じパブリックI/O制御コードのセットをサポートしなければなりません。 システムは、 IRP_MJ_DEVICE_CONTROL 要求のためのパブリックでデバイスタイプ固有のI/O制御コードを、バッファリングされた要求として定義します。
その結果、物理デバイスドライバは、 Irp->AssociatedIrp.SystemBuffer のIRPで各ドライバがデバイス制御要求のために見つけるシステム空間バッファリングとの間でデータ転送を行います。 ダイレクトI/Oのためにデバイスオブジェクトをセットアップするドライバでさえ、パブリックI/O制御コードでデバイス制御要求を満たすためにバッファリング付きI/Oを使用します。
各I/O制御コードの定義によって、その要求に対して転送されるデータがバッファリングされるかどうかが決まります。 ドライバ固有の IRP_MJ_INTERNAL_DEVICE_CONTROL 要求のために非公開で定義されたI/O制御コードは、ペアとなるドライバ間で、バッファリング方法、ダイレクト方法、またはどちらでもない方法のコードを定義することができます。 一般的なルールとして、密接に結合された上位レベルドライバがその要求のためにバッファリングを割り当てる必要がある場合、非公開に定義されたI/O制御コードは、どちらでもない方法で定義されなければなりません。
I/O操作のためのデバイスのプログラミング
通常、最下位デバイスレベルドライバの StartIo ルーチンは、 KeSynchronizeExecution を使用して、ドライバが提供する SynchCritSection ルーチンを呼び出すことにより、ドライバのISRと共有するメモリまたはデバイスレジスタへのアクセスを同期する必要があります。 ドライバの StartIo ルーチンは SynchCritSection ルーチンを使用して、DIRQLで実際に物理デバイスをI/Oプログラムします。 詳細については、「クリティカル セクションの使用」を参照してください。
KeSynchronizeExecutionを呼び出す前に、 StartIo ルーチンは要求に対して必要な前処理を行わなければなりません。 前処理には、初期部分転送範囲の計算や、他のドライバルーチンのために元の要求に関する状態情報を保存することが含まれる場合があります。
デバイスドライバがDMAを使用する場合、その StartIo ルーチンは通常 AllocateAdapterChannel を、ドライバが提供する AdapterControl ルーチンで呼び出します。 このような状況では、 StartIo ルーチンは物理デバイスのプログラミング責任を AdapterControl ルーチンに先送りします。 このルーチンは、 KeSynchronizeExecution を呼び出して、ドライバが提供する SynchCritSection ルーチンにDMA転送のためにデバイスをプログラムさせることができます。