Executive Work Items

[Previous] [Next]

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 an executive 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.

I'll describe a simple way of using an executive work item. First declare a structure that starts with an unnamed instance of the WORK_QUEUE_ITEM structure. Here's an example drawn from the WORKITEM sample on the companion disc:

struct _RANDOM_JUNK { struct _WORK_QUEUE_ITEM; <other stuff> } RANDOM_JUNK, *PRANDOM_JUNK;

Declaring the Work-Item Structure

The ability to have an unnamed union or structure that's a member of a bigger structure is a Microsoft extension to the C/C++ language. In the example shown in the text, you can directly reference members of the standard WORK_QUEUE_ITEM without needing to supply an intermediate level of name qualification.

If you're able to use C++ syntax—as I'm doing in the sample programs—there's a better way to declare a structure like the one I showed you in the text:

struct _RANDOM_JUNK : public _WORK_QUEUE_ITEM { <other stuff> }; typedef _RANDOM_JUNK RANDOM_JUNK, *PRANDOM_JUNK;

This syntax says that _RANDOM_JUNK is derived from _WORK_QUEUE_ITEM, meaning that it inherits all of the same members as the base structure. You're probably familiar with the concept of deriving C++ classes from other classes, but you can derive structures as well. Using this method of declaration, you can still directly reference WORK_QUEUE_ITEM fields without extra name qualification, but you won't be relying on a Microsoft language extension to do so.

When you're ready, allocate an instance of this structure from the heap and initialize it:

PRANDOM_JUNK item = (PRANDOM_JUNK) ExAllocatePool(PagedPool, sizeof(RANDOM_JUNK); ExInitializeWorkItem(item, (PWORKER_THREAD_ROUTINE) Callback, (PVOID) item); <additional initialization>

In the call to ExInitializeWorkItem, the first argument (item) is the address of the WORK_QUEUE_ITEM embedded in your structure. ExInitializeWorkItem is actually a macro that simply references WORK_QUEUE_ITEM fields using this pointer; I didn't need to supply a cast here because I declared the WORK_QUEUE_ITEM as an unnamed structure member. The second argument (Callback) is the address of a callback routine elsewhere in your driver. The third and final argument is a context parameter that will eventually be used as the single argument to the callback routine. I used the item pointer here for reasons that will become apparent when I show you the callback routine. ExInitializeWorkItem merely initializes that part of your structure (that is, WORK_QUEUE_ITEM) that the system knows about. After calling ExInitializeWorkItem, you need to do any initialization of your own data members that might be required.

At this point, you're ready to ask the system to put your work item into a queue, which can be done using the ExQueueWorkItem function:

ExQueueWorkItem(item, QueueIdentifier);

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 into 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 second argument to ExQueueWorkItem. 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 item) { PAGED_CODE(); ... ExFreePool(item); }

This callback receives a single argument (item), which is the context parameter you supplied earlier in the call to ExInitializeWorkItem. This fragment also shows the call to ExFreePool that balances the allocation we did earlier. Since you must release the work item memory if you allocated it from the heap in the first place, it's often convenient to pass the work queue item address itself as the context parameter. That's what I did here, in fact, because the work queue item occupies the first several bytes of the RANDOM_JUNK structure.

I have one more important point to make about work items. You can't remove a work item from the system queue. If, however, you were to honor a PnP request to remove your device, it's possible (though pretty unlikely) for your driver to be removed from memory while a work item is still pending. The remove lock mechanism I described in Chapter 6, "Plug andPlay," gives you a perfect way to prevent this from happening, as follows:

  • Before you queue a work item, use IoAcquireRemoveLock to establish a claim that will prevent your driver from being unloaded.
  • At the end of the work-item callback routine, call IoReleaseRemoveLock to release that claim. To do this, you'll need to have access to your device extension inside the callback routine. Chances are you'll need the device extension pointer for other reasons, anyway. So, you'll probably want to put a device extension or device object pointer inside the RANDOM_JUNK structure (to which you'll probably also give a better name!).

In addition, your callback routine needs to take whatever steps are necessary to avoid accessing hardware that's been surprise-removed or depowered, and so on.

IoAllocateWorkItem, IoQueueWorkItem, and IoFreeItem

Windows 2000 provides a new set of functions—IoAllocateWorkItem, IoQueueWorkItem, and IoFreeItem—that Microsoft recommends you use instead of the executive support functions I just described. The new functions surround calls to the executive-level functions with code that claims a reference to a device object you specify. That reference prevents your device object from disappearing, but it doesn't hold off the processing of IRP_MN_REMOVE_DEVICE requests. So long as you understand that you must prevent the disappearance of your driver and any resources that your work-item callback will access until after the callback executes, there's no compelling reason to use the new functions.

About the WORKITEM Sample

The WORKITEM sample driver on the companion disc illustrates the bare mechanics of using an executive work item. It's basically a reprise of the NOTIFY sample that works like this: The test application issues a DeviceIoControl with a code of IOCTL_SUBMIT_ITEM. The driver treats this as an asynchronous IOCTL by using the techniques I described earlier in this chapter. It also queues a work item before returning from the DEVICE_CONTROL dispatch function. When the work item callback occurs, the driver then completes the IOCTL_SUBMIT_ITEM.



Programming the Microsoft Windows Driver Model
Programming the Microsoft Windows Driver Model
ISBN: 0735618038
EAN: 2147483647
Year: 1999
Pages: 93
Authors: Walter Oney

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net