System Workerthreads
Ein Treiber, der eine verzögerte Verarbeitung erfordert, kann ein Arbeitselement verwenden, das einen Zeiger auf eine Treiberrückrufroutine enthält, die die eigentliche Verarbeitung ausführt. Der Treiber stellt das Arbeitselement in die Warteschlange, und ein Systemworkerthread entfernt das Arbeitselement aus der Warteschlange und führt die Rückrufroutine des Treibers aus. Das System verwaltet einen Pool dieser Systemworkerthreads, bei denen es sich um Systemthreads handelt, die jeweils jeweils ein Arbeitselement verarbeiten.
Der Treiber ordnet dem Arbeitselement eine WorkItem-Rückrufroutine zu. Wenn der Systemarbeitsthread das Arbeitselement verarbeitet, wird die zugeordnete WorkItem-Routine aufgerufen. In Windows Vista und höheren Versionen von Windows kann ein Treiber stattdessen eine WorkItemEx-Routine einem Arbeitselement zuordnen. WorkItemEx akzeptiert Parameter, die sich von den Parametern unterscheiden, die WorkItem akzeptiert.
WorkItem- und WorkItemEx-Routinen werden in einem Systemthreadkontext ausgeführt. Wenn eine Treiberverteilungsroutine in einem Threadkontext im Benutzermodus 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 Arbeitselement zu verwenden, führt ein Treiber die folgenden Schritte aus:
Ordnen Sie ein neues Arbeitselement zu, und initialisieren Sie es.
Das System verwendet eine IO_WORKITEM-Struktur , um ein Arbeitselement aufzunehmen. Um eine neue IO_WORKITEM-Struktur zuzuordnen und als Arbeitselement 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 zuordnen und IoInitializeWorkItem aufrufen, um die Struktur als Arbeitselement zu initialisieren. (Der Treiber sollte IoSizeofWorkItem aufrufen, um die Anzahl der Bytes zu bestimmen, die für ein Arbeitselement erforderlich sind.)
Ordnen Sie dem Arbeitselement eine Rückrufroutine zu, und stellen Sie das Arbeitselement in eine Warteschlange, damit es von einem Systemarbeitsthread verarbeitet wird.
Um dem Arbeitselement eine WorkItem-Routine zuzuordnen und das Arbeitselement in eine Warteschlange zu stellen, sollte der Treiber IoQueueWorkItem aufrufen. Um dem Arbeitselement stattdessen eine WorkItemEx-Routine zuzuordnen und das Arbeitselement in die Warteschlange zu stellen, sollte der Treiber IoQueueWorkItemEx aufrufen.
Nachdem das Arbeitselement nicht mehr benötigt wird, geben Sie es frei.
Ein Arbeitselement, das von IoAllocateWorkItem zugeordnet wurde, sollte von IoFreeWorkItem freigegeben werden. Ein Arbeitselement, das von IoInitializeWorkItem initialisiert wurde, muss von IoUninitializeWorkItem nicht initialisiert werden, bevor es freigegeben werden kann.
Das Arbeitselement kann nur nicht initialisiert oder freigegeben werden, wenn sich das Arbeitselement derzeit nicht in der Warteschlange befindet. Das System entfernt die Warteschlange des Arbeitselements, bevor es die Rückrufroutine des Arbeitselements aufruft, sodass IoFreeWorkItem und IoUninitializeWorkItem innerhalb des Rückrufs aufgerufen werden können.
Ein DPC, der eine Verarbeitungsaufgabe initiieren muss, die eine lange Verarbeitung erfordert oder einen blockierenden Aufruf ausgibt, sollte die Verarbeitung dieser Aufgabe an ein oder mehrere Arbeitselemente delegieren. Während ein DPC ausgeführt wird, werden alle Threads an der Ausführung gehindert. Darüber hinaus darf ein DPC, der unter IRQL = DISPATCH_LEVEL ausgeführt wird, keine blockierenden Aufrufe tätigen. Der Systemworkerthread, der ein Arbeitselement verarbeitet, wird jedoch unter IRQL = PASSIVE_LEVEL ausgeführt. Daher kann das Arbeitselement blockierende Aufrufe enthalten. Beispielsweise kann ein Systemworkerthread auf ein Verteilerobjekt 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 in Anspruch nehmen. Wenn eine dieser Routinen zu lange ausgeführt wird (wenn sie z. B. eine unbestimmte Schleife enthält) oder zu lange wartet, kann das System einen Deadlock ausführen. Wenn ein Treiber daher lange Zeiträume verzögerter Verarbeitung erfordert, sollte er stattdessen PsCreateSystemThread aufrufen, um einen eigenen Systemthread zu erstellen.
Rufen Sie ioQueueWorkItem oder IoQueueWorkItemEx nicht auf, um ein Arbeitselement in die Warteschlange zu stellen, das sich bereits in der Warteschlange befindet. Dies kann zu einer Beschädigung der Systemdatenstrukturen führen. Wenn Ihr Treiber bei jeder Ausführung einer bestimmten Treiberroutine das gleiche Arbeitselement in die Warteschlange stellt, können Sie die folgende Methode verwenden, um zu vermeiden, dass das Arbeitselement ein zweites Mal in die Warteschlange gestellt wird, wenn es sich bereits in der Warteschlange befindet:
- Der Treiber verwaltet eine Liste von Aufgaben für die Workerroutine.
- Diese Aufgabenliste ist in dem Kontext verfügbar, der für die Workerroutine bereitgestellt wird. Die Workerroutine und alle Treiberroutinen, die die Aufgabenliste ändern, synchronisieren ihren Zugriff auf die Liste.
- Jedes Mal, wenn die Workerroutine ausgeführt wird, führt sie alle Aufgaben in der Liste aus und entfernt jede Aufgabe aus der Liste, sobald die Aufgabe abgeschlossen ist.
- Wenn eine neue Aufgabe eintrifft, fügt der Treiber diese Aufgabe der Liste hinzu. Der Treiber stellt das Arbeitselement nur in die Warteschlange, wenn die Aufgabenliste zuvor leer war.
Der Systemarbeitsthread entfernt das Arbeitselement aus der Warteschlange, bevor er den Workerthread aufruft. Daher kann ein Treiberthread das Arbeitselement sicher wieder in die Warteschlange stellen, sobald der Workerthread ausgeführt wird.