By now, I assume you’re sold on the notion of converting to CSQ, and waiting with baited breath for details on how to go about it. If so, this article is for you!
Implementing CSQ in your driver project is really easy to do. There are two bacsic steps:
- Implement a few callbacks to pass to the CSQ library
- Implement the queuing system in your project
There are six callbacks that you have to implement. In many cases, the implementations will be completely trivial – Microsoft just provided lots of knobs in order to make the library as generally useful as possible. The other nice thing about these routines is that you basically don’t have to worry about synchronization at all, as they are called with the CSQ lock held when need be (but remember that queue-manipulation functions are called at DISPATCH_LEVEL!). All of these routines are documented in the DDK, but here’s a quickie implementor’s guide.
I like to put all of my CSQ support stuff into a sngle file. There are a couple of file-globals that I usually use: a LIST_ENTRY to point to my queue of IRPs, a KSPIN_LOCK to pass to acquire and release when CSQ tells me to, and of course an IO_CSQ struct.
By way of function implementations:
- CsqRemoveIrp – typically just RemoveEntryList. Note that you shouldn’t call an interlocked operation here because you don’t have to.
- CsqInsertIrp – typically just InsertTailList.
- CsqPeekNextIrp – this function returns a pointer to the next IRP in the list to be removed. This is the least straightforward function, and is worth a read of the DDK for reference. In general, you can return NULL if the list is empty, the head of the queue if no starting IRP is specified, or the IRP itself if one is specified.
- CsqAcquireLock – typically just KeAcquireSpinLock.
- CsqReleaseLock – typically just KeReleaseSpinLock.
- CsqCompleteCanceledIrp – a mini-completion routine. You may just be able to complete the IRP with STATUS_CANCELLED.
That, friends, is all that it takes. This doesn’t really take more than a couple of hundred lines of code, and the best part is that once you build your CSQ support file, you can re-use it over and over for similar projects.
Using The Queuing System
Using the CSQ library for the queuing operations in your driver is really a piece of cake. The first step, of course, is to initialize the library. This is typically done during DriverEntry, but can be done wherever is convenient for you. Initialization is done by passing pointers to your callbacks to IoCsqInitialize(). Once that is done, and your LIST_ENTRY and KSPIN_LOCK are initialized, you’re ready to use the queue. This is as easy as calling IoCsqInsertIrp and IoCsqRemoveNextIrp. If your queue management needs are more complex than just running the queue in FIFO fashion, other IoCsq APIs allow more control over queue operations.
This implementation guide covers the simple (and common) case of just needing a single basic FIFO queue to store IRPs during processing. More advanced scenarios are possible, of course. In particular, one of the most common IRP-queuing scenarios involves the use of the StartIo model. If your driver uses StartIo for IRP queuing, CSQ may be just what the doctor ordered for increased queue management and better overall performance. Microsoft has provided a special CSQ sample in the DDK for you; do have a look. The major tweak involves duplicating the semantics of insertion in your CsqInsertIrp callback.
I hope some of you have found this series helpful. Feel free to post questions or comments, and of course corrections. I’d be particularly interested to hear about anyone else’s experience in converting to CSQ – problems encountered, performance differences, etc.