Programming the Microsoft Windows Driver Model
Work Items
From time to time, you might wish that you could temporarily lower the processor s interrupt request level (IRQL) to carry out some task or another that must be done at PASSIVE_LEVEL. Lowering IRQL is, of course, a definite no-no. So long as you re running at or below DISPATCH_LEVEL, however, you can queue a work item to request a callback into your driver later. The callback occurs at PASSIVE_LEVEL in the context of a worker thread owned by the operating system. Using a work item can save you the trouble of creating your own thread that you only occasionally wake up.
NOTE
Don t hijack one of the system worker threads by scheduling a work item that takes a long time to execute. There aren t a great number of worker threads, and you can lock up the system by preventing other drivers work items from executing.
You would ordinarily declare a context structure of some sort to tell your work item callback routine what to do. Whatever else it contains, it will need a pointer to an IO_WORKITEM structure, as shown here:
typedef struct _RANDOM_JUNK {
At the point where you want to queue the work item, you create an instance of the context structure, and an IO_WORKITEM:
PRANDOM_JUNK stuff = (PRANDOM_JUNK) ExAllocatePool(NonPagedPool, sizeof(RANDOM_JUNK)); stuff->item = IoAllocateWorkItem(fdo);
where fdo is the address of a DEVICE_OBJECT, ordinarily one with which the work item is associated in some way.
You now initialize the context structure and issue the following call to actually place the work item in the queue for a system worker thread:
IoQueueWorkItem(stuff->item, (PIO_WORKITEM_ROUTINE) Callback, QueueIdentifier, stuff);
QueueIdentifier can be either of these two values:
-
DelayedWorkQueue indicates that you want your work item executed in the context of a system worker thread that executes at variable priority that is, not at a real-time priority level.
-
CriticalWorkQueue indicates that you want your work item executed in the context of a system worker thread that executes at a real-time priority.
You choose the delayed or the critical work queue depending on the urgency of the task you re trying to perform. Putting your item in the critical work queue will give it priority over all noncritical work in the system at the possible cost of reducing the CPU time available for other critical work. In any case, the activities you perform in your callback can always be preempted by activities that run at an elevated IRQL.
After you queue the work item, the operating system will call you back in the context of a system worker thread having the characteristics you specified as the third argument to IoQueueWorkItem. You ll be at IRQL PASSIVE_LEVEL. What you do inside the callback routine is pretty much up to you except for one requirement: you must release or otherwise reclaim the memory occupied by the work queue item. Here s a skeleton for a work-item callback routine:
VOID Callback(PRANDOM_JUNK stuff) { PAGED_CODE();
This callback receives a single argument (stuff), which is the context parameter you supplied earlier in the call to IoQueueWorkItem. This fragment also shows the calls to ExFreePool and IoFreeWorkItem that balance the allocations we did earlier.
In between the time you call IoQueueWorkItem and the time your callback routine returns, the I/O Manager owns an extra reference to the device object you specified in the original call to IoAllocateWorkItem. The extra reference pins your driver in memory at least until the callback routine returns. Without this protection, it would be perfectly possible for your driver to queue a work item and then unload before the callback finished executing. A bug check would then occur because the system would attempt to execute code at a suddenly invalid address. There is nothing you can do inside your own driver to totally avoid this kind of problem because you have to execute at least a return instruction to get out of your own code and back to the system.
In Windows versions prior to Windows 2000, there was a routine named ExQueueWorkItem and a macro named ExInitializeWorkItem for creating and queuing work items. These functions are now deprecated because of the driver unload problem. In fact, the Windows Hardware Quality Lab (WHQL) test suite flags calls to ExQueueWorkItem. This actually poses a bit of an obstacle to creating a binary-compatible driver for all WDM platforms, as discussed in Windows 98/Me Compatibility Notes at the end of this chapter.
On the CD The WORKITEM sample in the companion content illustrates the mechanics of using the IoXxxWorkItem functions discussed in the text.