Cancel-Safe IRP-Warteschlangen

Treiber, die eigene IRP-Warteschlangen implementieren, sollten das abbruchsichere IRP-Warteschlangenframework verwenden. Abbruchsichere IRP-Warteschlangen teilen die IRP-Behandlung in zwei Teile auf:

  1. Der Treiber stellt eine Reihe von Rückrufroutinen bereit, die Standardvorgänge in der IRP-Warteschlange des Treibers implementieren. Zu den bereitgestellten Vorgängen gehören das Einfügen und Entfernen von IRPs aus der Warteschlange sowie das Sperren und Entsperren der Warteschlange. Weitere Informationen finden Sie unter Implementieren der Cancel-Safe IRP-Warteschlange.

  2. Wenn der Treiber tatsächlich ein IRP aus der Warteschlange einfügen oder entfernen muss, verwendet er die vom System bereitgestellten IoCsqXxx-Routinen . Diese Routinen behandeln die gesamte Synchronisierungs- und IRP-Abbruchlogik für den Treiber.

Treiber, die abbruchsichere IRP-Warteschlangen verwenden, implementieren keine Cancel-Routinen, um den IRP-Abbruch zu unterstützen.

Das Framework stellt sicher, dass Treiber IRPs atomar einfügen und aus ihrer Warteschlange entfernen. Außerdem wird sichergestellt, dass die IRP-Abbruch ordnungsgemäß implementiert wird. Treiber, die das Framework nicht verwenden, müssen die Warteschlange manuell sperren und entsperren, bevor sie ein- und löscht. Sie müssen auch die Racebedingungen vermeiden, die sich bei der Implementierung einer Cancel-Routine ergeben können. (Eine Beschreibung der Rennbedingungen, die auftreten können, finden Sie unter Synchronisieren der IRP-Absage.)

Das abbruchsichere IRP-Warteschlangenframework ist in Windows XP und höheren Versionen von Windows enthalten. Treiber, die auch mit Windows 2000 und Windows 98/Me funktionieren müssen, können mit der Csq.lib-Bibliothek verknüpft werden, die im Windows Driver Kit (WDK) enthalten ist. Die Csq.lib-Bibliothek stellt eine Implementierung dieses Frameworks bereit.

Die IoCsqXxx-Routinen werden in windows XP und höheren Versionen von Wdm.h und Ntddk.h deklariert. Treiber, die auch mit Windows 2000 und Windows 98/Me funktionieren müssen, müssen csq.h für die Deklarationen enthalten.

Eine vollständige Demonstration der Verwendung von abbruchsicheren IRP-Warteschlangen finden Sie im Verzeichnis \src\general\cancel des WDK. Weitere Informationen zu diesen Warteschlangen finden Sie auch im Whitepaper Ablauf der Steuerung für Cancel-Safe IRP Queuing .

Implementieren der Cancel-Safe IRP-Warteschlange

Um eine abbruchsichere IRP-Warteschlange zu implementieren, müssen Treiber die folgenden Routinen bereitstellen:

  • Eine der folgenden Routinen zum Einfügen von IRPs in die Warteschlange: CsqInsertIrp oder CsqInsertIrpEx. CsqInsertIrpEx ist eine erweiterte Version von CsqInsertIrp. die Warteschlange wird mithilfe der einen oder der anderen implementiert.

  • Eine CsqRemoveIrp-Routine , die den angegebenen IRP aus der Warteschlange entfernt.

  • Eine CsqPeekNextIrp-Routine , die einen Zeiger auf die nächste IRP zurückgibt, die dem angegebenen IRP in der Warteschlange folgt. An dieser Stelle übergibt das System den PeekContext-Wert , den es von IoCsqRemoveNextIrp empfängt. Der Treiber kann diesen Wert in beliebiger Weise interpretieren.

  • Die beiden folgenden Routinen ermöglichen es dem System, die IRP-Warteschlange zu sperren und zu entsperren: CsqAcquireLock und CsqReleaseLock.

  • Eine CsqCompleteCanceledIrp-Routine , die eine abgebrochene IRP abschließt.

Zeiger auf die Routinen des Treibers werden in der IO_CSQ-Struktur gespeichert, die die Warteschlange beschreibt. Der Treiber weist den Speicher für die IO_CSQ-Struktur zu. Die IO_CSQ-Struktur bleibt garantiert eine feste Größe, sodass ein Treiber die Struktur sicher in seine Geräteerweiterung einbetten kann.

Der Treiber verwendet entweder IoCsqInitialize oder IoCsqInitializeEx , um die Struktur zu initialisieren. Verwenden Sie IoCsqInitialize , wenn die Warteschlange CsqInsertIrp implementiert, oder IoCsqInitializeEx , wenn die Warteschlange CsqInsertIrpEx implementiert.

Treiber müssen nur die wesentlichen Funktionen in jeder Rückrufroutine bereitstellen. Beispielsweise implementieren nur die Routinen CsqAcquireLock und CsqReleaseLock die Sperrbehandlung. Das System ruft diese Routinen automatisch auf, um die Warteschlange bei Bedarf zu sperren und zu entsperren.

Sie können jeden Typ von IRP-Warteschlangenmechanismus in Ihrem Treiber implementieren, solange die entsprechenden Dispatchroutinen bereitgestellt werden. Beispielsweise könnte der Treiber die Warteschlange als verknüpfte Liste oder als Prioritätswarteschlange implementieren.

CsqInsertIrpEx bietet eine flexiblere Schnittstelle zur Warteschlange als CsqInsertIrp. Der Treiber kann seinen Rückgabewert verwenden, um das Ergebnis des Vorgangs anzugeben. wenn ein Fehlercode zurückgegeben wird, ist beim Einfügen ein Fehler aufgetreten. Eine CsqInsertIrp-Routine gibt keinen Wert zurück, sodass es keine einfache Möglichkeit gibt, anzugeben, dass ein Einfügefehler aufgetreten ist. Außerdem verwendet CsqInsertIrpEx einen zusätzlichen vom Treiber definierten InsertContext-Parameter , mit dem zusätzliche treiberspezifische Informationen angegeben werden können, die von der Warteschlangenimplementierung verwendet werden sollen.

Treiber können CsqInsertIrpEx verwenden, um eine komplexere IRP-Behandlung zu implementieren. Wenn beispielsweise keine ausstehenden IRPs vorhanden sind, kann die CsqInsertIrpEx-Routine einen Fehlercode zurückgeben, und der Treiber kann den IRP sofort verarbeiten. Wenn IRPs nicht mehr in die Warteschlange eingereiht werden können, kann csqInsertIrpEx einen Fehlercode zurückgeben, um diese Tatsache anzugeben.

Der Treiber ist von allen IRP-Abbruchbehandlungen isoliert. Das System bietet eine Cancel-Routine für IRPs in der Warteschlange. Diese Routine ruft CsqRemoveIrp auf, um die IRP aus der Warteschlange zu entfernen, und CsqCompleteCanceledIrp , um den IRP-Abbruch abzuschließen.

Das folgende Diagramm veranschaulicht den Ablauf der Steuerung für den IRP-Abbruch.

Diagramm, das den Ablauf der Steuerung für den Irp-Abbruch veranschaulicht.

Eine grundlegende Implementierung von CsqCompleteCanceledIrp lautet wie folgt.

VOID CsqCompleteCanceledIrp(PIO_CSQ Csq, PIRP Irp) {
  Irp->IoStatus.Status = STATUS_CANCELLED;
  Irp->IoStatus.Information = 0;

  IoCompleteRequest(Irp, IO_NO_INCREMENT);
}

Treiber können alle Synchronisierungsgrundsätze des Betriebssystems verwenden, um ihre CsqAcquireLock - und CsqReleaseLock-Routinen zu implementieren. Zu den verfügbaren Synchronisierungsgrundtypen gehören Spinsperren und Mutex-Objekte.

Hier sehen Sie ein Beispiel dafür, wie ein Treiber die Sperrung mithilfe von Drehsperren implementieren kann.

/* 
  The driver has previously initialized the SpinLock variable with
  KeInitializeSpinLock.
 */

VOID CsqAcquireLock(PIO_CSQ IoCsq, PKIRQL PIrql)
{
    KeAcquireSpinLock(SpinLock, PIrql);
}

VOID CsqReleaseLock(PIO_CSQ IoCsq, KIRQL Irql)
{
    KeReleaseSpinLock(SpinLock, Irql);
}

Das System übergibt einen Zeiger auf eine IRQL-Variable an CsqAcquireLock und CsqReleaseLock. Wenn der Treiber eine Drehsperre verwendet, um die Sperrung für die Warteschlange zu implementieren, kann der Treiber diese Variable verwenden, um den aktuellen IRQL zu speichern, wenn die Warteschlange gesperrt ist.

Treiber müssen keine Drehsperren verwenden. Der Treiber könnte beispielsweise einen Mutex verwenden, um die Warteschlange zu sperren. Eine Beschreibung der für Treiber verfügbaren Synchronisierungstechniken finden Sie unter Synchronisierungstechniken.

Verwenden der Cancel-Safe IRP-Warteschlange

Treiber verwenden die folgenden Systemroutinen beim Anstehen und Trennen von IRPs:

  • Eine der folgenden Optionen, um einen IRP in die Warteschlange einzufügen: IoCsqInsertIrp oder IoCsqInsertIrpEx.

  • IoCsqRemoveNextIrp , um die nächste IRP in der Warteschlange zu entfernen. Der Treiber kann optional einen Schlüsselwert angeben.

Das folgende Diagramm veranschaulicht den Ablauf der Steuerung für IoCsqRemoveNextIrp.

Diagramm, das den Ablauf der Steuerung für iocsqremovenextirp veranschaulicht.

  • IoCsqRemoveIrp , um die angegebene IRP aus der Warteschlange zu entfernen.

Das folgende Diagramm veranschaulicht den Ablauf der Steuerung für IoCsqRemoveIrp.

Diagramm, das den Ablauf der Steuerung für iocsqremoveirp veranschaulicht.

Diese Routinen wiederum werden an vom Fahrer bereitgestellte Routinen verteilt.

Die IoCsqInsertIrpEx-Routine bietet Zugriff auf die erweiterten Features einer CsqInsertIrpEx-Routine . Es gibt den status Wert zurück, der von CsqInsertIrpEx zurückgegeben wurde. Der Aufrufer kann diesen Wert verwenden, um zu bestimmen, ob die IRP erfolgreich in die Warteschlange eingereiht wurde oder nicht. IoCsqInsertIrpEx ermöglicht es dem Aufrufer auch, einen Wert für den InsertContext-Parameter von CsqInsertIrpEx anzugeben.

Beachten Sie, dass sowohl IoCsqInsertIrp als auch IoCsqInsertIrpEx in jeder abbruchsicheren Warteschlange aufgerufen werden können, unabhängig davon, ob die Warteschlange über eine CsqInsertIrp-Routine oder eine CsqInsertIrpEx-Routine verfügt. IoCsqInsertIrp verhält sich in beiden Fällen gleich. Wenn IoCsqInsertIrpEx eine Warteschlange übergeben wird, die über eine CsqInsertIrp-Routine verfügt, verhält sie sich identisch mit IoCsqInsertIrp.

Das folgende Diagramm veranschaulicht den Ablauf der Steuerung für IoCsqInsertIrp.

Diagramm, das den Ablauf der Steuerung für iocsqinsertirp veranschaulicht.

Das folgende Diagramm veranschaulicht den Ablauf der Steuerung für IoCsqInsertIrpEx.

Diagramm, das den Ablauf der Steuerung für iocsqinsertirpex veranschaulicht.

Es gibt mehrere natürliche Möglichkeiten, die IoCsqXxx-Routinen zu verwenden, um IRPs in die Warteschlange zu stellen und zu beenden. Beispielsweise könnte ein Treiber irPs einfach in die Warteschlange stellen, um verarbeitet zu werden, in der Reihenfolge, in der sie empfangen werden. Der Treiber könnte eine IRP wie folgt in die Warteschlange stellen:

    status = IoCsqInsertIrpEx(IoCsq, Irp, NULL, NULL);

Wenn der Treiber nicht zwischen bestimmten IRPs unterscheiden muss, könnte er sie einfach in der Reihenfolge dequenieren, in der sie in die Warteschlange gestellt wurden, wie folgt:

    IoCsqRemoveNextIrp(IoCsq, NULL);

Alternativ kann der Treiber bestimmte IRPs in die Warteschlange stellen und ausstellen. Die Routinen verwenden die undurchsichtige IO_CSQ_IRP_CONTEXT-Struktur , um bestimmte IRPs in der Warteschlange zu identifizieren. Der Treiber stellt die IRP wie folgt in die Warteschlange:

    IO_CSQ_IRP_CONTEXT ParticularIrpInQueue;
    IoCsqInsertIrp(IoCsq, Irp, &ParticularIrpInQueue);

Der Treiber kann dann denselben IRP mithilfe des IO_CSQ_IRP_CONTEXT-Werts aus der Warteschlange entfernen.

    IoCsqRemoveIrp(IoCsq, Irp, &ParticularIrpInQueue);

Möglicherweise muss der Treiber auch IRPs basierend auf einem bestimmten Kriterium aus der Warteschlange entfernen. Beispielsweise kann der Treiber jedem IRP eine Priorität zuordnen, sodass IRPs mit höherer Priorität zuerst dequeuiert werden. Der Treiber übergibt möglicherweise einen PeekContext-Wert an IoCsqRemoveNextIrp, den das System an den Treiber zurückgibt, wenn es den nächsten IRP in der Warteschlange anfordert.