Freigeben über


System-Arbeitsthreads

Ein Treiber, der eine verzögerte Verarbeitung erfordert, kann eine Arbeitsaufgabe verwenden, die einen Zeiger auf eine Treiberrückrufroutine enthält, die die eigentliche Verarbeitung durchführt. Der Treiber stellt die Arbeitsaufgabe in die Warteschlange und ein System-Worker-Thread entfernt die Arbeitsaufgabe aus der Warteschlange und führt die Rückrufroutine des Treibers aus. Das System verwaltet einen Pool dieser Systemarbeitsthreads, bei denen es sich um Systemthreads handelt, die jeweils jeweils eine Arbeitsaufgabe verarbeiten.

Der Treiber ordnet eine WorkItem-Rückrufroutine der Arbeitsaufgabe zu. Wenn der Systemarbeitsthread die Arbeitsaufgabe verarbeitet, ruft er die zugeordnete WorkItem-Routine auf. In Windows Vista und höheren Versionen von Windows kann ein Treiber stattdessen eine WorkItemEx-Routine einer Arbeitsaufgabe zuordnen. WorkItemEx akzeptiert Parameter, die sich von den Parametern unterscheiden, die Von WorkItem verwendet werden.

WorkItem - und WorkItemEx-Routinen werden in einem Systemthreadkontext ausgeführt. Wenn eine Treiberverteilerroutine in einem Benutzermodus-Threadkontext ausgeführt werden kann, kann diese Routine eine WorkItem- oder WorkItemEx-Routine aufrufen, um alle Vorgänge auszuführen, die einen Systemthreadkontext erfordern.

Um ein Arbeitsobjekt zu verwenden, führt ein Treiber die folgenden Schritte aus:

  1. Zuordnen und Initialisieren einer neuen Arbeitsaufgabe

    Das System verwendet eine IO_WORKITEM Struktur, um eine Arbeitsaufgabe zu enthalten. Um eine neue IO_WORKITEM Struktur zuzuweisen und als Arbeitsaufgabe zu initialisieren, kann der Treiber IoAllocateWorkItem aufrufen. In Windows Vista und höheren Versionen von Windows kann der Treiber alternativ eine eigene IO_WORKITEM Struktur zuweisen und IoInitializeWorkItem aufrufen, um die Struktur als Arbeitsaufgabe zu initialisieren. (Der Treiber sollte IoSizeofWorkItem aufrufen, um die Anzahl der Bytes zu bestimmen, die zum Speichern einer Arbeitsaufgabe erforderlich sind.)

  2. Ordnen Sie der Arbeitsaufgabe eine Rückrufroutine zu, und stellen Sie die Arbeitsaufgabe in die Warteschlange, sodass sie von einem Systemarbeitsthread verarbeitet wird.

    Um eine WorkItem-Routine mit der Arbeitsaufgabe zu verknüpfen und die Arbeitsaufgabe in die Warteschlange zu stellen, sollte der Treiber IoQueueWorkItem aufrufen. Um stattdessen eine WorkItemEx-Routine mit der Arbeitsaufgabe zu verknüpfen und die Arbeitsaufgabe in die Warteschlange zu stellen, sollte der Treiber IoQueueWorkItemEx aufrufen.

  3. Nachdem die Arbeitsaufgabe nicht mehr erforderlich ist, geben Sie sie frei.

    Eine Arbeitsaufgabe, die von IoAllocateWorkItem zugewiesen wurde, sollte von IoFreeWorkItem freigegeben werden. Eine Arbeitsaufgabe, die von IoInitializeWorkItem initialisiert wurde, muss von IoUninitializeWorkItem nicht initialisiert werden, bevor es freigegeben werden kann.

    Die Arbeitsaufgabe kann nur uninitialisiert oder freigegeben werden, wenn die Arbeitsaufgabe derzeit nicht in die Warteschlange gestellt ist. Das System dequeues die Arbeitsaufgabe, bevor es die Rückrufroutine der Arbeitsaufgabe aufruft, sodass IoFreeWorkItem und IoUninitializeWorkItem innerhalb des Rückrufs aufgerufen werden können.

Ein DPC, der eine erforderliche Verarbeitungsaufgabe initiieren muss, die eine lange Bearbeitung erfordert oder einen blockierenden Aufruf macht, sollte die Verarbeitung dieser Aufgabe an ein oder mehrere Arbeitselemente delegieren. Während ein DPC ausgeführt wird, werden alle Threads daran gehindert, zu laufen. Darüber hinaus darf ein DPC, der unter IRQL = DISPATCH_LEVEL ausgeführt wird, keine Blockierungsaufrufe tätigen. Jedoch wird der Systemarbeitsthread, der ein Arbeitselement verarbeitet, unter IRQL = PASSIVE_LEVEL ausgeführt. Daher kann die Arbeitsaufgabe blockierende Aufrufe enthalten. Beispielsweise kann ein Systemarbeitsthread auf ein Dispatcherobjekt warten.

Da der Pool von Systemarbeitsthreads eine begrenzte Ressource ist, können WorkItem - und WorkItemEx-Routinen nur für Vorgänge verwendet werden, die einen kurzen Zeitraum dauern. Wenn eine dieser Routinen zu lang ausgeführt wird (wenn sie z. B. eine unbestimmte Schleife enthält) oder zu lange wartet, kann das System deadlocken. Wenn ein Treiber daher lange Zeit mit verzögerter Verarbeitung benötigt, sollte er stattdessen PsCreateSystemThread aufrufen, um einen eigenen Systemthread zu erstellen.

Rufen Sie IoQueueWorkItem oder IoQueueWorkItemEx nicht auf, um ein Arbeitsobjekt in die Warteschlange zu stellen, das sich bereits dort befindet. Dies kann zu Beschädigungen von Systemdatenstrukturen führen. Wenn Ihr Treiber jedes Mal, wenn eine bestimmte Treiberroutine ausgeführt wird, dieselbe Arbeitsaufgabe in die Warteschlange einreiht, können Sie die folgende Technik verwenden, um eine zweite Warteschlange zu vermeiden, wenn sie sich bereits in der Warteschlange befindet:

  • Der Treiber verwaltet eine Liste der Aufgaben für die Arbeitsroutine.
  • Diese Aufgabenliste ist im Kontext verfügbar, der der Arbeitsroutine zur Verfügung gestellt wird. Die Arbeitsroutine und alle Treiberroutinen, die die Aufgabenliste ändern, synchronisieren ihren Zugriff auf die Liste.
  • Jedes Mal, wenn die Arbeitsroutine ausgeführt wird, führt sie alle Aufgaben in der Liste aus und entfernt jede Aufgabe aus der Liste, während die Aufgabe abgeschlossen ist.
  • Wenn eine neue Aufgabe eingeht, fügt der Treiber diese Aufgabe der Liste hinzu. Der Treiber stellt das Arbeitsobjekt nur dann in die Warteschlange, wenn die Aufgabenliste zuvor leer war.

Der Systemarbeitsthread entfernt die Arbeitsaufgabe aus der Warteschlange, bevor der Arbeitsthread aufgerufen wird. Daher kann ein Treiberthread die Arbeitsaufgabe sicher erneut in die Warteschlange stellen, sobald der Arbeitsthread gestartet wird.