I/O 操作の前方進行の保証
システムのページング デバイスの記憶域ドライバーなど、一部のドライバーは、重要なシステム データの損失を回避するために、サポートされている I/O 操作の少なくとも一部を失敗なく実行する必要があります。 ドライバーの障害の考えられる原因の 1 つは、メモリ不足の状況です。 フレームワークまたはドライバーが I/O 要求を処理するのに十分なメモリを割り当てることができない場合は、どちらか一方がエラー状態の値で I/O 要求を完了することによって失敗する必要があります。
バージョン 1.9 より前のバージョンの KMDF では、I/O マネージャーがドライバーに送信した I/O 要求パケット (IRP) にフレームワーク要求オブジェクトを割り当てることができない場合、フレームワークは常に I/O 要求を失敗させます。 ドライバーにメモリ不足の状況で I/O 要求を処理する機能を提供するために、バージョン 1.9 以降のフレームワークでは、I/O キューに対して保証付フォワード プログレス機能が提供されます。
この機能により、フレームワークとドライバーは、要求オブジェクトのセットと要求に関連するドライバー コンテキスト バッファーのメモリをそれぞれ事前に割り当てることができます。 フレームワークとドライバーは、システム メモリの量が少ない場合にのみ、この事前に割り当てられたメモリを使用します。
保証付フォワード プログレスの機能
I/O キューに対してフレームワークの保証付フォワード プログレスを使用することで、ドライバーは次のことができます:
メモリ不足の状況で特定の I/O キューで使用する一連の要求オブジェクトを事前に割り当てるようにフレームワークに依頼します。
メモリ不足の状況でフレームワークから事前に割り当てられた要求オブジェクトを受け取ったときにドライバーが使用できる要求固有のリソースを事前に割り当てるコールバック関数を提供します。
メモリ不足の状況が検出されて いない 場合に、I/O 要求にドライバー固有のリソースを割り当てる別のコールバック関数を指定します。 メモリ不足が原因でこのコールバック関数の割り当てが失敗した場合は、フレームワークが事前に割り当てられた要求オブジェクトのいずれかを使用する必要があるかどうかを示すことができます。
事前に割り当てられた要求オブジェクトを使用する必要がある I/O 要求を指定します。 オプションには、すべての IRP に対して事前に割り当てられたオブジェクトを使用する、それらをページング I/O 操作が進行中の場合にのみ使用する、または追加のドライバー コールバック関数で各 IRP を調べて、事前に割り当てられたオブジェクトを使用するかどうかを判断することが含まれます。
ドライバーが 1 つ以上の I/O キューの保証付フォワード プログレスを実装している場合、ドライバーはメモリ不足の状況で正常に I/O 要求を処理できるようになります。 デバイスの既定の I/O キューと、ドライバーが WdfDeviceConfigureRequestDispatching を呼び出して構成する任意の I/O キューに対して、保証付フォワード プログレスを実装できます。
フレームワークの保証付フォワード プログレス機能は、ドライバーとドライバーの I/O ターゲットの両方が保証付フォワード プログレスを実装している場合にのみ、ドライバーに対して機能します。 つまり、ドライバーがデバイスの保証付フォワード プログレスを実装する場合、デバイスのドライバー スタック内のすべての下位レベルのドライバーも、保証付フォワード プログレスを実装する必要があります。
I/O キューの転送の保証付フォワード プログレスを有効にする
I/O キューの転送の保証付フォワード プログレスを有効にするには、ドライバーは WDF_IO_QUEUE_FORWARD_PROGRESS_POLICY 構造体を初期化し、WdfIoQueueAssignForwardProgressPolicy メソッドを呼び出します。 ドライバーが WdfDeviceConfigureRequestDispatching を呼び出して I/O キューを構成する場合は、WdfIoQueueAssignForwardProgressPolicy を呼び出す前に行う必要があります。
ドライバーが WdfIoQueueAssignForwardProgressPolicy を呼び出すときに、次の 3 つのイベント コールバック関数を指定できます。これらはすべて省略可能です:
EvtIoAllocateResourcesForReservedRequest
ドライバーの EvtIoAllocateResourcesForReservedRequest コールバック関数は、メモリ不足の状況でフレームワークが予約している要求オブジェクトに対して要求固有のリソースを割り当てて格納します。
フレームワークは、予約済み要求オブジェクトを作成するたびに、このコールバック関数を呼び出します。 ドライバーは、通常、予約済み要求オブジェクトのコンテキスト領域を使用して、1 つの I/O 要求に要求固有のリソースを割り当てる必要があります。
EvtIoAllocateRequestResources
ドライバーの EvtIoAllocateRequestResources コールバック関数は、すぐに使用するために要求固有のリソースを割り当てます。 フレームワークが IRP を受信し、IRP の要求オブジェクトを作成した直後に呼び出されます。
コールバック関数によるリソースの割り当てに失敗した場合、コールバック関数はエラー状態の値を返します。 その後、フレームワークは新しく作成された要求オブジェクトを削除し、その予約済み要求オブジェクトの 1 つを使用します。 さらに、ドライバーの要求ハンドラーは、その EvtIoAllocateRequestResources コールバック関数が以前に割り当てた要求固有のリソースを使用します。
EvtIoWdmIrpForForwardProgress
ドライバーの EvtIoWdmIrpForForwardProgress コールバック関数は、IRP を調べ、IRP の予約済み要求オブジェクトを使用するか、エラー状態の値で完了して I/O 要求を失敗させるかをフレームワークに指示します。
フレームワークは、フレームワークが新しい要求オブジェクトを作成できず、(ドライバー のWDF_IO_QUEUE_FORWARD_PROGRESS_POLICY 構造体にフラグを設定して) メモリ不足の状況でドライバーが IRP を調べるという意向が示されている場合にのみ、このコールバック関数を呼び出します。 言い換えると、ドライバーは各 IRP を評価し、メモリ不足の状況でも処理する必要があるかどうかを判断できます。
ドライバーは、WdfIoQueueAssignForwardProgressPolicy を呼び出すときに、メモリ不足の状況に対してフレームワークで事前に割り当てる予約済み要求オブジェクトの数も指定します。 デバイスとドライバーに適した要求オブジェクトの数を選択できます。 パフォーマンスの低下を防ぐために、ドライバーは通常、ドライバーとデバイスが並列で処理できる I/O 要求の数に近い数を指定する必要があります。
ただし、ドライバーが WdfIoQueueAssignForwardProgressPolicy とそのEvtIoAllocateResourcesForReservedRequest コールバック関数を呼び出すと、過剰な予約された要求オブジェクトや、要求固有のリソース メモリが事前に割り当てられる場合、ドライバーは実際に処理しようとしているメモリ不足の状況に影響を与える可能性があります。 最適な数値を選択するには、ドライバーとデバイスのパフォーマンスをテストし、メモリ不足のシミュレーションを含める必要があります。
WdfIoQueueAssignForwardProgressPolicy が返される前に、フレームワークはドライバーが指定した要求オブジェクトの数を作成して予約します。 要求オブジェクトを予約するたびに、フレームワークはドライバーの EvtIoAllocateResourcesForReservedRequest コールバック関数をすぐに呼び出して、フレームワークが予約済み要求オブジェクトを実際に使用する場合に備えてドライバーが要求固有のリソースを割り当てて保存できるようにします。
ドライバーの要求ハンドラーの 1 つが I/O キューから I/O 要求を受信すると、WdfRequestIsReserved メソッドを呼び出して、要求オブジェクトがメモリ不足の状況でフレームワークによって事前に割り当てられたものであるかどうかを判断できます。 このメソッドが TRUE を返す場合、ドライバーは EvtIoAllocateResourcesForReservedRequest コールバック関数が予約したリソースを使用する必要があります。
フレームワークが予約済み要求オブジェクトの 1 つを使用する場合、ドライバーが要求を完了した後、そのオブジェクトを予約済みオブジェクトのセットに返します。 フレームワークは、要求オブジェクトと、別のメモリ不足の状況が発生した場合に再利用するために、WdfDeviceInitSetRequestAttributes または WdfObjectAllocateContext を呼び出すことによってドライバーが作成したコンテキスト領域を保存します。
フレームワークとドライバーが保証された前進をサポートする方法
I/O キューの保証された前進をサポートするためにドライバーとフレームワークが実行する手順を次に示します:
ドライバーは、WdfIoQueueAssignForwardProgressPolicy を呼び出します。
応答として、フレームワークは、ドライバーが指定する要求オブジェクトの数を割り当てて格納します。 ドライバーが以前に WdfDeviceInitSetRequestAttributes を呼び出した場合、各割り当てには、WdfDeviceInitSetRequestAttributes が指定したコンテキスト領域が含まれます。
さらに、ドライバーが EvtIoAllocateResourcesForReservedRequest コールバック関数を提供している場合、フレームワークは要求オブジェクトを割り当てて格納するたびにコールバック関数を呼び出します。
フレームワークは、I/O マネージャーがドライバーに送信している I/O 要求パケット (IRP) を受信します。
フレームワークは、IRP の要求オブジェクトを割り当てようとします。 要求の種類に対してドライバーが作成した I/O キューで保証された前進がサポートされている場合、次の手順は割り当てが成功するか失敗するかによって異なります:
要求オブジェクトの割り当てが成功します。
ドライバーが EvtIoAllocateRequestResources コールバック関数を提供した場合、フレームワークはそれを呼び出します。 コールバック関数がSTATUS_SUCCESSを返す場合、フレームワークは I/O キューに要求を追加します。 コールバック関数がエラー状態値を返す場合、フレームワークは先ほど作成した要求オブジェクトを削除し、事前に割り当てられた要求オブジェクトのいずれかを使用します。 ドライバーの要求ハンドラーは、要求オブジェクトを受け取ると、要求オブジェクトが事前に割り当てられているかどうか、それによってドライバーの事前に割り当てられたリソースを使用する必要があるかどうかを判断します。
ドライバーが EvtIoAllocateRequestResources コールバック関数を提供しなかった場合、フレームワークは、ドライバーが保証された前進を有効にしなかった場合と同様に、I/O キューに要求を追加します。
要求オブジェクトの割り当てが失敗します。
フレームワークが次に行う処理は、ドライバーが WDF_IO_QUEUE_FORWARD_PROGRESS_POLICY 構造体の ForwardProgressReservedPolicy メンバーに指定した値によって異なります。 このメンバーは、予約済み要求を使用する以下のタイミングをフレームワークに通知します: 常に、I/O 要求がページング I/O 操作である場合のみ、または EvtIoWdmIrpForForwardProgress コールバック関数が予約済み要求を使用する必要があることを示す場合のみ。
いずれの場合も、ドライバーの要求ハンドラーはWdfRequestIsReserved を呼び出して、フレームワークが予約済み要求オブジェクトを使用したかどうかを判断できます。 その場合、ドライバーは、その EvtIoAllocateResourcesForReservedRequest コールバック関数が割り当てた要求リソースを使用する必要があります。
保証付フォワード プログレス保証のシナリオ
あなたはシステムのページング ファイルを含む可能性があるストレージ デバイスのドライバーを作成しています。 ページング ファイルに対する読み取り操作と書き込み操作が成功することが重要です。
読み取り操作と書き込み操作用に個別の I/O キューを作成し、これらの両方の I/O キューに対して保証付フォワード プログレスを有効にすることにします。 他のすべての要求の種類に対して 3 番目の I/O キューを作成することにします。保証付フォワード プログレスは有効にしません。
ドライバー スタックとデバイスは、4 つの書き込み操作を並列で処理できるため、WdfIoQueueAssignForwardProgressPolicy を呼び出す前に、WDF_IO_QUEUE_FORWARD_PROGRESS_POLICY 構造体の TotalForwardProgressRequests メンバーを 4 に設定します。
ドライバーのデバイスがページング デバイスである場合にのみ、フォワード プログレスを保証することが重要であると判断し、すなわちドライバーは、WDF_IO_QUEUE_FORWARD_PROGRESS_POLICY 構造体の ForwardProgressReservedPolicy メンバーを WdfIoForwardProgressReservedPolicyPagingIOに設定します。
ドライバーには、読み取り要求と書き込み要求ごとにフレームワーク メモリ オブジェクトが必要であるため、メモリ不足の状況で、ドライバーは WdfIoTargetFormatRequestForRead および WdfIoTargetFormatRequestForWrite の呼び出しに使用するメモリ オブジェクトを事前に割り当てる必要があると判断します。
そのため、ドライバーは、読み取りキュー用の EvtIoAllocateResourcesForReservedRequest コールバック関数と書き込みキュー用のもう 1 つのコールバック関数を提供します。 フレームワークがこれらのコールバック関数のいずれかを呼び出すたびに、そのコールバック関数は WdfMemoryCreate を呼び出し、メモリ不足の状況で返されたオブジェクト ハンドルを保存します。 コールバック関数は、事前に割り当てられた要求オブジェクトへのハンドルを受け取るため、メモリ オブジェクトを要求オブジェクトの親にすることができます。 (DMA デバイスのドライバーは、フレームワーク DMA オブジェクトを事前に割り当てる場合もあります。)
読み取りおよび書き込みキューの要求ハンドラーは、受信した各要求オブジェクトが、メモリ不足の状況のためにフレームワークが予約した要求オブジェクトであるかどうかを判断する必要があります。 要求ハンドラーは WdfRequestIsReserved を呼び出すことができます。または、要求オブジェクト ハンドルを、EvtIoAllocateResourcesForReservedRequest コールバック関数が以前に受信したハンドルと比較できます。
ドライバーは、読み取りキュー用の EvtIoAllocateRequestResources コールバック関数と書き込みキュー用のもう 1 つのコールバック関数を提供します。 フレームワークは、I/O マネージャーから読み取りまたは書き込み要求を受信し、要求オブジェクトを正常に作成するときに、これらのコールバック関数のいずれかを呼び出します。 これらの各コールバック関数は、WdfMemoryCreate を呼び出して、要求のメモリ オブジェクトを割り当てます。 割り当てが失敗した場合、コールバック関数はエラー状態の値を返して、メモリ不足の状況が発生したことをフレームワークに通知します。 フレームワークは、エラーの戻り値を検出して、先ほど作成した要求オブジェクトを削除し、事前に割り当てられたオブジェクトのいずれかを使用します。
フレームワークが I/O キューに追加する前に個々の読み取りまたは書き込み IRP を調べる必要がないため、このドライバーは EvtIoWdmIrpForForwardProgress コールバック関数を提供しません。
ドライバーがデバイスの保証付フォワード プログレスを実装する場合、デバイスのドライバー スタック内のすべての下位レベルのドライバーも、保証付フォワード プログレスを実装する必要があることに注意してください。