メモリ バッファーのライフ サイクル

メモリ バッファーのライフ サイクルは、バッファーが作成されたときから削除されるまでの時間に及びます。 このトピックでは、バッファーの使用シナリオと、それらがバッファーの削除時にどのように影響するかについて説明します。

カーネル モード ドライバー フレームワーク (KMDF) では、要求オブジェクトは I/O 要求を表します。 すべての要求オブジェクトは 1 つ以上のメモリ オブジェクトに関連付けられます。各メモリ オブジェクトは、要求の入力または出力に使用されるバッファーを表します。

フレームワークは、受信 I/O 要求を表す要求オブジェクトとメモリ オブジェクトを作成するときに、関連付けられているメモリ オブジェクトの親として要求オブジェクトを設定します。 そのため、メモリ オブジェクトは、要求オブジェクトの有効期間より長く存続しない可能性があります。 フレームワーク ベースのドライバーが I/O 要求を完了すると、フレームワークは要求オブジェクトとメモリ オブジェクトを削除するため、これら 2 つのオブジェクトへのハンドルが無効になります。

ただし、基になるバッファーは異なります。 バッファーを作成したコンポーネントとバッファーの作成方法によっては、バッファーに参照カウントがあり、メモリ オブジェクトによって所有されている場合もあれば、所有されていない場合もあります。 メモリ オブジェクトがバッファーを所有している場合、バッファーには参照カウントがあり、その有効期間はメモリ オブジェクトの有効期間に制限されます。 他のコンポーネントによってバッファーが作成された場合、バッファーとメモリ オブジェクトの有効期間に関係性はありません。

フレームワーク ベースのドライバーは、I/O ターゲットに送信する独自の要求オブジェクトを作成することもできます。 ドライバーが作成した要求では、ドライバーが I/O 要求で受信した既存のメモリ オブジェクトを再利用できます。 I/O ターゲットに要求を頻繁に送信するドライバーは、作成する要求オブジェクトを再利用できます。

ドライバーが無効なハンドルまたはバッファー ポインターを参照しないようにするには、要求オブジェクト、メモリ オブジェクト、および基になるバッファーの有効期間を理解することが重要です。

以下のような使用シナリオが考えられます。

シナリオ 1: ドライバーは、KMDF から I/O 要求を受信し、処理し、完了します。

最も単純なシナリオでは、KMDF によってドライバーに要求がディスパッチされ、I/O が実行され、要求が完了します。 この場合、基になるバッファーは、ユーザー モード アプリケーション、別のドライバー、またはオペレーティング システム自体によって作成されている可能性があります。 バッファーにアクセスする方法については、「フレームワーク ベースのドライバーでのデータ バッファーへのアクセス」を参照してください。

ドライバーが要求を完了すると、フレームワークはメモリ オブジェクトを削除します。 その後、バッファー ポインターは無効です。

シナリオ 2: ドライバーは、KMDF から I/O 要求を受信し、I/O ターゲットに転送します。

このシナリオでは、ドライバーは I/O ターゲットに要求を転送します。 次のサンプル コードは、ドライバーが受信要求オブジェクトからメモリ オブジェクトへのハンドルを取得し、I/O ターゲットに送信する要求を書式設定し、要求を送信する方法を示しています:

VOID
EvtIoRead(
    IN WDFQUEUE Queue,
    IN WDFREQUEST Request,
    IN size_t Length
    )
{
    NTSTATUS status;
    WDFMEMORY memory;
    WDFIOTARGET ioTarget;
    BOOLEAN ret;
    ioTarget = WdfDeviceGetIoTarget(WdfIoQueueGetDevice(Queue));

    status = WdfRequestRetrieveOutputMemory(Request, &memory);
    if (!NT_SUCCESS(status)) {
        goto End;
    }

    status = WdfIoTargetFormatRequestForRead(ioTarget,
                                    Request,
                                    memory,
                                    NULL,
                                    NULL);
    if (!NT_SUCCESS(status)) {
        goto End;
    }

    WdfRequestSetCompletionRoutine(Request,
                                    RequestCompletionRoutine,
                                    WDF_NO_CONTEXT);

    ret = WdfRequestSend (Request, ioTarget, WDF_NO_SEND_OPTIONS);
    if (!ret) {
        status = WdfRequestGetStatus (Request);
        goto End;
    }

    return;

End:
    WdfRequestComplete(Request, status);
    return;

}

I/O ターゲットが要求を完了すると、フレームワークは、ドライバーが要求に設定した完了コールバックを呼び出します。 次のコードは、単純な完了コールバックを示しています:

VOID
RequestCompletionRoutine(
    IN WDFREQUEST                  Request,
    IN WDFIOTARGET                 Target,
    PWDF_REQUEST_COMPLETION_PARAMS CompletionParams,
    IN WDFCONTEXT                  Context
    )
{
    UNREFERENCED_PARAMETER(Target);
    UNREFERENCED_PARAMETER(Context);

    WdfRequestComplete(Request, CompletionParams->IoStatus.Status);

    return;

}

ドライバーが完了コールバックから WdfRequestComplete を呼び出すと、フレームワークはメモリ オブジェクトを削除します。 ドライバーが取得したメモリ オブジェクト ハンドルが無効になりました。

シナリオ 3: ドライバーは、既存のメモリ オブジェクトを使用する I/O 要求を発行します。

一部のドライバーは、独自の I/O 要求を発行し、I/O ターゲット オブジェクトで表される I/O ターゲットに送信します。 ドライバーは、独自の要求オブジェクトを作成するか、フレームワークによって作成された要求オブジェクトを再利用できます。 ドライバーは、いずれかの手法を使用して、前の要求のメモリ オブジェクトを再利用できます。 ドライバーは、基になるバッファーを変更することはできませんが、新しい I/O 要求を書式設定するときにバッファー オフセットを渡すことができます。

既存のメモリ オブジェクトを使用する新しい I/O 要求の書式を設定する方法については、「一般 I/O ターゲットへの I/O 要求の送信」を参照してください。

フレームワークは、I/O ターゲットに送信する要求を書式設定すると、I/O ターゲット オブジェクトに代わって、リサイクルされたメモリ オブジェクトの参照を取り出します。 I/O ターゲット オブジェクトは、次のいずれかのアクションが発生するまで、この参照を保持します:

新しい I/O 要求が完了すると、フレームワークは、この要求に対してドライバーが設定した I/O 完了コールバックを呼び出します。 この時点で、I/O ターゲット オブジェクトはメモリ オブジェクトの参照を保持します。 したがって、I/O 完了コールバックでは、ドライバーは、メモリ オブジェクトを取得した元の要求を完了する前に、ドライバーによって作成された要求オブジェクトに対して WdfRequestReuse を呼び出す必要があります。 ドライバーが WdfRequestReuse を呼び出さない場合、余分な参照のためにバグチェックが発生します。

シナリオ 4: ドライバーは、新しいメモリ オブジェクトを使用する I/O 要求を発行します。

このフレームワークには、基になるバッファーのソースに応じて、ドライバーが新しいメモリ オブジェクトを作成するための 3 つの方法が用意されています。 詳細については、「メモリ バッファーの使用」を参照してください。

バッファーがフレームワークまたはドライバーによって作成されたルックアサイド リストから割り当てられている場合、メモリ オブジェクトはバッファーを所有するため、メモリ オブジェクトが存在する限り、バッファー ポインターが有効のままになります。 非同期 I/O 要求を発行するドライバーは、メモリ オブジェクトによって所有されているバッファーを常に使用する必要があります。これによりフレームワークでは、I/O 要求が完了して発行ドライバーに戻るまでバッファーが確実に保持されます。

ドライバーが WdfMemoryCreatePreallocated を呼び出すことによって、新しいメモリ オブジェクトに以前に割り当てられたバッファーを割り当てる場合、メモリ オブジェクトはそのバッファーを所有しません。 この場合、メモリ オブジェクトの有効期間と基になるバッファーの有効期間は関係性がありません。 ドライバーは、バッファーの有効期間を管理する必要があり、無効なバッファー ポインターの使用を試みてはなりません。

シナリオ 5: ドライバーは、作成した要求オブジェクトを再利用します。

ドライバーは、作成した要求オブジェクトを再利用できますが、各再利用の前に WdfRequestReuse を呼び出すことによって、このような各オブジェクトを再初期化する必要があります。 詳細については、「フレームワーク要求オブジェクトの再利用」を参照してください。

要求オブジェクトを再初期化するサンプル コードについては、KMDF リリースで 提供される ToasterNdisEdge のサンプルを参照してください。