Work Items and Driver Threads

Although a KMDF driver can create a new thread by calling PsCreateSystemThread, drivers rarely do so. Switching thread context is a relatively time-consuming operation that can degrade driver performance if it occurs often. Therefore, drivers should create dedicated threads only to perform continually repeated or long-term activities, such as polling a device or managing multiple data streams, as a network driver might do.

To perform a short-term, finite task, a driver should not create its own thread. Instead, it can temporarily "borrow" a system thread by queuing a work item. The system maintains a pool of dedicated threads that all drivers share. When a driver queues a work item, the system dispatches it to one of these threads for execution. Drivers use work items to run code in the system thread and security context, or to call functions that are available only at IRQL PASSIVE_LEVEL. A driver's CompletionRoutine callbacks-which the framework can call at IRQL DISPATCH_LEVEL-commonly use work items to access pageable data or to call a function that runs at IRQL PASSIVE_LEVEL.

Because the system has a limited supply of dedicated worker threads, the tasks assigned to them should complete quickly. Consider the following guidelines for implementing work items in your driver:

  • Do not create a work item that runs continuously until the driver is unloaded.

    Instead, queue work items only as needed. The work item function should exit when its work is complete.

  • Never include an infinite loop in a work item.

  • Avoid queuing excessive numbers of work items, because tying up the system worker threads can deadlock the system.

    Instead, create a single work item function that performs any outstanding work and then exits when there is no more work to perform immediately.

  • Do not wait for an event for an extended period of time in any work item.

    In particular, do not wait on an event that is signaled by another work item. If all of the worker threads are busy, the system does not schedule a new work item until an existing work item exits. In this situation, a deadlock could occur.

About Work Items

In KMDF drivers, a WDFWORKITEM object represents a work item. To use a work item, a driver must:

  • Implement a work item callback function.

  • Configure and create a work item object.

  • Queue the work item object.

The work item callback function has the following prototype:

 typedef VOID   (*PFN_WDF_WORKITEM) (     IN WDFWORKITEM  WorkItem     ); 

As the prototype shows, the EvtWorkItem callback function does not return a value. The function has one input parameter, which is the WDFWORKITEM object itself. To pass data to the EvtWorkItem callback, the driver should use the context area of the work item object. The callback performs the tasks of the work item.

Each work item object is associated with a particular EvtWorkItem callback function. When the driver calls the WdfWorkItemEnqueue method, the framework adds the work item to the system's delayed work queue.

A system worker thread later removes the work item from the queue and runs the EvtWorkItem callback function in a system thread context at IRQL PASSIVE_LEVEL. To synchronize the actions of the callback function with other driver functions, the driver can use a WDF wait lock or a Windows synchronization mechanism.

KMDF Example: Use a Work Item

The Usbsamp sample driver performs I/O to a USB target pipe. If certain errors occur during I/O completion processing, the driver queues a work item from its I/O completion callback to reset the target pipe. The driver cannot reset the pipe directly from the I/O completion call-back. The reason is that the framework can invoke the I/O completion callback at DISPATCH_LEVEL, and the WdfUsbTargetPipeResetSynchronously method-which the driver calls to reset the pipe-must be called at PASSIVE_LEVEL.

The code in Listing 15-1, which is from the Usbsamp\Sys\Bulkrwr.c file, creates and queues the work item.

Listing 15-1: Creating and queuing a work item

image from book
 NTSTATUS QueuePassiveLevelCallback(     IN WDFDEVICE    Device,     IN WDFUSBPIPE   Pipe     ) {     NTSTATUS                        status = STATUS_SUCCESS;     PWORKITEM_CONTEXT               context;     WDF_OBJECT_ATTRIBUTES           attributes;     WDF_WORKITEM_CONFIG             workitemConfig;     WDFWORKITEM                     hWorkItem;     WDF_OBJECT_ATTRIBUTES_INIT(&attributes);     WDF_OBJECT_ATTRIBUTES_SET_CONTEXT_TYPE(&attributes, WORKITEM_CONTEXT);             attributes.ParentObject = Device;     WDF_WORKITEM_CONFIG_INIT(&workitemConfig, ReadWriteWorkItem);     status = WdfWorkItemCreate( &workitemConfig,                                 &attributes,                                 &hWorkItem);     if (!NT_SUCCESS(status)) {         return status;     }     context = GetWorkItemContext(hWorkItem);     context->Device = Device;     context->Pipe = Pipe;     WdfWorkItemEnqueue(hWorkItem);     return STATUS_SUCCESS; } 
image from book

The driver in Listing 15-1 configures an attributes structure and a work item configuration structure before it creates the work item object. The call to WDF_OBJECT_ATTRIBUTES_INIT initializes the attributes structure and the call to WDF_OBJECT_ATTRIBUTES_SET_CONTEXT_TYPE sets the type of the context area for the object. The driver uses the context area to pass data to the work item callback function.

By default, a work item object has no parent, so the Usbsamp driver specifies the device object as the parent by setting the Parent field of the attributes structure. Making the device object the parent ensures that the framework deletes the work item object when it deletes the device object.

The Usbsamp driver initializes a work item configuration structure by calling WDF_WORKITEM_CONFIG_INIT and passing a pointer to the work item configuration structure and a pointer to the driver's EvtWorkItem callback, which is named ReadWriteWorkItem.

The Usbsamp driver then creates the work item object by calling WdfWorkItemCreate and passing pointers to the two structures it just initialized. WdfWorkItemCreate returns a handle to the work item object.

Assuming that creation succeeded, the Usbsamp driver gets a pointer to the work item's context area by calling GetWorkItemContext, which is the accessor function. The driver stores the device object and pipe object handles that were passed to QueuePassiveLevelCallback in the context area so that the work item has easy access to them when it runs. The driver then calls WdfWorkItemEnqueue with a handle to the work item object that it just created.

Although the sample driver creates and queues the work item during its I/O completion processing, a driver that queues such work items frequently should instead preallocate the work item object. The driver can then reuse the work item, thus avoiding any allocation failures that could occur in the I/O completion callback. However, the driver must not requeue the same work item until the previously queued work item callback has run to completion. The Toastmon sample shows how to track the state of a work item in this situation by using a flag and an interlocked operation.

Listing 15-2 shows the code for the EvtWorkItem callback function in the UsbSamp driver. This callback function is also in the Usbsamp\Sys\Bulkrwr.c source file.

Listing 15-2: Work item callback function

image from book
 VOID ReadWriteWorkItem(     IN WDFWORKITEM WorkItem     ) {     PWORKITEM_CONTEXT pItemContext;     NTSTATUS status;     pItemContext = GetWorkItemContext(WorkItem);     status = ResetPipe(pItemContext->Pipe);     if (!NT_SUCCESS(status)) {         status = ResetDevice(pItemContext->Device);         if(!NT_SUCCESS(status)){         . . . // Code omitted         }     }     WdfObjectDelete(WorkItem);     return; } 
image from book

The work item callback resets the target pipe and, if necessary, the USB port. The work item function is called with a handle to the work item. The driver immediately calls the accessor function to get a pointer to the work item's context area and then passes the pipe handle that it stored in the context area to the ResetPipe helper function. ResetPipe calls the framework's WdfUsbTargetPipeResetSynchronously method to reset the USB pipe. If this fails, the driver calls its ResetDevice helper function, which calls WdfUsbTargetDeviceResetPortSynchronously to reset the USB port.

When the reset tasks are complete, the work item object is no longer required, so the driver calls WdfObjectDelete to delete it and the function returns.

Developing Drivers with the Microsoft Windows Driver Foundation
Developing Drivers with the Windows Driver Foundation (Pro Developer)
ISBN: 0735623740
EAN: 2147483647
Year: 2007
Pages: 224

Similar book on Amazon
Windows Internals, Part 1: Covering Windows Server 2008 R2 and Windows 7
Windows Internals, Part 1: Covering Windows Server 2008 R2 and Windows 7
Windowsu00ae Internals: Including Windows Server 2008 and Windows Vista, Fifth Edition (Pro Developer)
Windowsu00ae Internals: Including Windows Server 2008 and Windows Vista, Fifth Edition (Pro Developer)
Advanced Windows Debugging
Advanced Windows Debugging
The Windows 2000 Device Driver Book: A Guide for Programmers (2nd Edition)
The Windows 2000 Device Driver Book: A Guide for Programmers (2nd Edition) © 2008-2017.
If you may any questions please contact us: