Lebenszyklus des Speicherpuffers

Der Lebenszyklus eines Speicherpuffers erstreckt sich über die Zeit von der Erstellung des Puffers bis zum Zeitpunkt des Löschens. In diesem Thema werden Szenarien zur Puffernutzung und deren Auswirkungen auf das Löschen des Puffers beschrieben.

Im Kernelmodustreiberframework (KMDF) stellt ein Anforderungsobjekt eine E/A-Anforderung dar. Jedes Anforderungsobjekt ist einem oder mehreren Speicherobjekten zugeordnet, und jedes Speicherobjekt stellt einen Puffer dar, der für die Eingabe oder Ausgabe in der Anforderung verwendet wird.

Wenn das Framework Anforderungs- und Arbeitsspeicherobjekte erstellt, um eine eingehende E/A-Anforderung darzustellen, legt es das Anforderungsobjekt als übergeordnetes Element der zugeordneten Speicherobjekte fest. Daher kann das Speicherobjekt nicht länger als die Lebensdauer des Anforderungsobjekts beibehalten werden. Wenn der frameworkbasierte Treiber die E/A-Anforderung abschließt, löscht das Framework das Anforderungsobjekt und das Speicherobjekt, sodass die Handles für diese beiden Objekte ungültig werden.

Der zugrunde liegende Puffer unterscheidet sich jedoch. Je nachdem, welche Komponente den Puffer erstellt hat und wie der Puffer erstellt wurde, verfügt der Puffer möglicherweise über eine Verweisanzahl und ist möglicherweise im Besitz des Speicherobjekts – oder nicht. Wenn das Speicherobjekt den Puffer besitzt, verfügt der Puffer über eine Verweisanzahl, und seine Lebensdauer ist auf die des Speicherobjekts beschränkt. Wenn eine andere Komponente den Puffer erstellt hat, hängen die Lebensdauern des Puffers und des Speicherobjekts nicht zusammen.

Ein frameworkbasierter Treiber kann auch eigene Anforderungsobjekte erstellen, die an E/A-Ziele gesendet werden sollen. Eine vom Treiber erstellte Anforderung kann ein vorhandenes Speicherobjekt wiederverwenden, das der Treiber in einer E/A-Anforderung empfangen hat. Ein Treiber, der häufig Anforderungen an E/A-Ziele sendet, kann die erstellten Anforderungsobjekte wiederverwenden .

Es ist wichtig, die Lebensdauer des Anforderungsobjekts, des Speicherobjekts und des zugrunde liegenden Puffers zu verstehen, um sicherzustellen, dass ihr Treiber nicht versucht, auf ein ungültiges Handle oder einen ungültigen Pufferzeiger zu verweisen.

Betrachten Sie die folgenden Verwendungsszenarien:

Szenario 1: Der Treiber empfängt eine E/A-Anforderung von KMDF, verarbeitet sie und schließt sie ab.

Im einfachsten Szenario sendet KMDF eine Anforderung an den Treiber, der E/A ausführt und die Anforderung abschließt. In diesem Fall wurde der zugrunde liegende Puffer möglicherweise von einer Anwendung im Benutzermodus, von einem anderen Treiber oder vom Betriebssystem selbst erstellt. Informationen zum Zugreifen auf Puffer finden Sie unter Zugreifen auf Datenpuffer in Framework-Based Treibern.

Wenn der Treiber die Anforderung abgeschlossen hat, löscht das Framework das Speicherobjekt. Der Pufferzeiger ist dann ungültig.

Szenario 2: Der Treiber empfängt eine E/A-Anforderung von KMDF und leitet sie an ein E/A-Ziel weiter.

In diesem Szenario leitet der Treiber die Anforderung an ein E/A-Ziel weiter. Der folgende Beispielcode zeigt, wie ein Treiber ein Handle für das Speicherobjekt aus einem eingehenden Anforderungsobjekt abruft, die Anforderung so formatiert, dass sie an das E/A-Ziel gesendet wird, und sendet die Anforderung:

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;

}

Wenn das E/A-Ziel die Anforderung abgeschlossen hat, ruft das Framework den Vervollständigungsrückruf auf, den der Treiber für die Anforderung festgelegt hat. Der folgende Code zeigt einen einfachen Vervollständigungsrückruf:

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;

}

Wenn der Treiber WdfRequestComplete aus seinem Abschlussrückruf aufruft, löscht das Framework das Speicherobjekt. Das Vom Treiber abgerufene Speicherobjekthandle ist jetzt ungültig.

Szenario 3: Der Treiber gibt eine E/A-Anforderung aus, die ein vorhandenes Speicherobjekt verwendet.

Einige Treiber stellen ihre eigenen E/A-Anforderungen aus und senden sie an E/A-Ziele, die durch E/A-Zielobjekte dargestellt werden. Der Treiber kann entweder ein eigenes Anforderungsobjekt erstellen oder ein vom Framework erstelltes Anforderungsobjekt wiederverwenden. Mit beiden Verfahren kann ein Treiber ein Speicherobjekt aus einer vorherigen Anforderung wiederverwenden. Der Treiber darf den zugrunde liegenden Puffer nicht ändern, kann jedoch einen Pufferoffset übergeben, wenn er die neue E/A-Anforderung formatiert.

Informationen zum Formatieren einer neuen E/A-Anforderung, die ein vorhandenes Speicherobjekt verwendet, finden Sie unter Senden von E/A-Anforderungen an allgemeine E/A-Ziele.

Wenn das Framework die Anforderung formatiert, die an das E/A-Ziel gesendet werden soll, nimmt es im Namen des E/A-Zielobjekts einen Verweis auf das wiederverwendete Speicherobjekt aus. Das E/A-Zielobjekt behält diesen Verweis bei, bis eine der folgenden Aktionen ausgeführt wird:

  • Die Anforderung wurde abgeschlossen.
  • Der Treiber reformiert das Anforderungsobjekt erneut, indem er eine der Methoden WdfIoTargetFormatRequestXxx oder WdfIoTargetSendXxxSynchronously aufruft . Weitere Informationen zu diesen Methoden finden Sie unter Framework-E/A-Zielobjektmethoden.
  • Der Treiber ruft WdfRequestReuse auf.

Wenn die neue E/A-Anforderung abgeschlossen ist, ruft das Framework den E/A-Vervollständigungsrückruf auf, den der Treiber für diese Anforderung festgelegt hat. An diesem Punkt enthält das E/A-Zielobjekt noch einen Verweis auf das Speicherobjekt. Daher muss der Treiber im E/A-Vervollständigungsrückruf WdfRequestReuse für das vom Treiber erstellte Anforderungsobjekt aufrufen, bevor er die ursprüngliche Anforderung abschließt, aus der er das Speicherobjekt abgerufen hat. Wenn der Treiber WdfRequestReuse nicht aufruft, wird aufgrund des zusätzlichen Verweises eine Fehlerüberprüfung durchgeführt.

Szenario 4: Der Treiber gibt eine E/A-Anforderung aus, die ein neues Speicherobjekt verwendet.

Das Framework bietet Treibern drei Möglichkeiten, je nach Quelle des zugrunde liegenden Puffers neue Speicherobjekte zu erstellen. Weitere Informationen finden Sie unter Verwenden von Speicherpuffern.

Wenn der Puffer vom Framework oder aus einer vom Treiber erstellten Lookaside-Liste zugeordnet wird, besitzt das Speicherobjekt den Puffer, sodass der Pufferzeiger gültig bleibt, solange das Speicherobjekt vorhanden ist. Treiber, die asynchrone E/A-Anforderungen ausstellen, sollten immer Puffer verwenden, die sich im Besitz von Speicherobjekten befinden, damit das Framework sicherstellen kann, dass die Puffer beibehalten werden, bis die E/A-Anforderung zurück an den ausstellenden Treiber abgeschlossen ist.

Wenn der Treiber durch Aufrufen von WdfMemoryCreatePreallocated einen zuvor zugewiesenen Puffer einem neuen Speicherobjekt zuweist, besitzt das Speicherobjekt den Puffer nicht. In diesem Fall hängen die Lebensdauer des Speicherobjekts und die Lebensdauer des zugrunde liegenden Puffers nicht zusammen. Der Treiber muss die Lebensdauer des Puffers verwalten und darf nicht versuchen, einen ungültigen Pufferzeiger zu verwenden.

Szenario 5: Der Treiber verwendet ein anforderungsobjekt, das er erstellt hat.

Ein Treiber kann die anforderungsobjekte wiederverwenden, die er erstellt, muss jedoch jedes objekt neu initialisieren, indem er WdfRequestReuse vor jeder Wiederverwendung aufruft. Weitere Informationen finden Sie unter Wiederverwenden von Framework-Anforderungsobjekten.

Beispielcode, der ein Anforderungsobjekt neu initialisiert, finden Sie in den Beispielen Toaster und NdisEdge , die mit der KMDF-Version bereitgestellt werden.