3.8.2  Setting up an Interlocked Queue and Queueing IRPs

NT drivers with device-dedicated threads or drivers that use executive worker threads, such as most system FSDs, are the most likely types of NT drivers to manage their own internal queueing of IRPs in an interlocked queue. Usually, these drivers set up a doubly linked interlocked queue because the driver can requeue IRPs for retry operations in a doubly linked interlocked queue. An NT driver cannot requeue IRPs for retries if it sets up a singly linked interlocked queue.

Such a driver must set up its interlocked queue when it initializes. Figure 3.22 illustrates a doubly linked interlocked queue, the support routines the DriverEntry routine must call to set up such a queue, and a set of ExInterlockedXxx routines an NT driver can call to insert IRPs into and remove IRPs from the queue.

Figure 3.22    Using an Interlocked Queue

As Figure 3.22 shows, a driver must provide the storage for the queue itself and for the following in order to set up a doubly linked interlocked queue:

·An ExecutiveSpinLock that the DriverEntry routine must initialize by calling KeInitializeSpinLock

·The ListHead for the queue that the DriverEntry routine must initialize by calling InitializeListHead, which is one of the NT kernel-mode runtime library routines, even though this routine does not have the Rtl prefix

Most NT drivers that use interlocked queues provide the necessary storage in the device extension of a driver-created device object, but such a queue and executive spin lock can be in a controller extension if the driver uses a controller object (see Section 3.4) or in nonpaged pool allocated by the driver.

A queue with a ListHead of type LIST_ENTRY, as shown in Figure 3.22, is a doubly linked list. One with a ListHead of type SINGLE_LIST_ENTRY is a singly linked list. The DriverEntry routine initializes the ListHead for a singly linked interlocked queue by setting it to NULL.

After such a driver is loaded, it can insert an IRP into its queue by calling either of the following support routines if the ListHead is of type LIST_ENTRY, as shown in Figure 3.22:

·ExInterlockedInsertTailList to place the IRP at the end of the queue

·ExInterlockedInsertHeadList to place the IRP at the front of the queue, which drivers usually call only when they must retry a particular request

The driver must pass pointers to the IRP (ListEntry), as well the ListHead and ExecutiveSpinLock (Lock) pointers that were initialized by the DriverEntry routine, to each of these ExInterlockedInsert..List routines. It must pass pointers only to the ListHead and Lock when the driver dequeues an IRP by calling ExInterlockedRemoveHeadList.

A driver that never retries operations can use ExInterlockedPushEntryList and ExInterlockedPopEntryList to manage its queueing of IRPs internally in a singly linked interlocked queue. Any driver that uses such an interlocked queue also must provide resident storage for the queue, for a ListHead of type SINGLE_LIST_ENTRY, and for an ExecutiveSpinLock, as shown in Figure 3.22, and must set up its queue in a similar manner when the driver initializes.

Because its interlocked queue is protected by the executive spin lock, the driver can insert IRPs into its queue and remove them in a multiprocessor-safe manner from any driver routine running at less than or equal to IRQL DISPATCH_LEVEL.

For more information about managing IRQLs and using spin locks, see Chapter 16. For support-routine-specific IRQL requirements, see the Kernel-Mode Driver Reference.