One at a time! Protecting shared data from concurrent routines
As a savvy driver writer, you know that Windows can pre-empt the thread in which your driver is running to run a higher-priority thread at any time. And you know that on a multiprocessor system, including systems with hyper-threaded CPUs, your driver can run concurrently on more than one processor at a time. In either situation, your driver must synchronize access to shared, writable data. But do you know exactly which kernel-mode driver routines can be called concurrently - and therefore exactly which data you need to protect?
Two routines that can run at the same time are said to be concurrent. For a driver, concurrency generally means that the operating system might call one routine before the other routine has returned.
When two routines can run concurrently, you must ensure that any shared, writable data is accessed by only one routine at a time unless all such accesses are read-only. If you understand which routines can be called concurrently, you can code your driver to use locks when necessary and, in some cases, you can lay out your driver's data structures to avoid unnecessary locks.
Exactly which routines can be called concurrently depends on the objects they are called with. For example, two driver routines that are called concurrently with the same driver object are said to be concurrent with respect to the driver object. Driver routines can be concurrent with respect to the driver object, to a device object, or to a file object.
Driver object concurrency. Most driver routines are concurrent with respect to the driver object because most driver tasks are performed for a particular file object or device object, rather than the driver object itself.
Device object concurrency. Driver routines that change the state of a physical device - such as Plug and Play and power management dispatch routines - are typically not concurrent with respect to the device object.
File object concurrency. Driver routines that handle traditional I/O requests (such as create, close, read, write, and so forth) typically manipulate file objects, so their concurrency is related to the state of the file object. For example, a driver's DispatchCleanup routine is not called until the handle to the specified file object has been closed. Therefore, it cannot be called for a given file object while the DispatchClose or DispatchCreate routine is running for that same file object.
You can determine which routines can be called concurrently by checking the concurrency tables provided in the paper Multiprocessor Considerations for Kernel-Mode Drivers. The tables list the routines that are concurrent with respect to driver, device, and file objects.
The following shows an excerpt from the table of file object concurrency:
This excerpt shows the file object concurrency of Cancel routines with themselves and with dispatch routines for file objects. The table provides the following information about when a driver's Cancel routine can be called for a particular file object:
- While the Cancel routine is running, it can be called again for the same file object. This means that a driver must be able to support the simultaneous cancellation of more than one I/O request on a particular file object at a time.
- While a driver's DispatchCleanup routine is running (that is, while the driver is handling an IRP_MJ_CLEANUP request for a particular file object), the driver's Cancel routine can be called for the file object.
- While a driver's DispatchClose or DispatchCreate routine is running (that is, while the driver is handling an IRP_MJ_CLOSE or IRP_MJ_CREATE request for a particular file object), the driver's Cancel routine cannot be called for the file object. (However, cancellation of IRP_MJ_CREATE requests is planned for Windows Vista and Windows Server 2008.)
- While any of the driver's other file I/O dispatch routines are running, the Cancel routine can be called for the same file object.
- Identify writable data and memory locations that are shared and might be accessed concurrently. Use locks to ensure that all potentially concurrent accesses occur serially.
- Isolate driver-specific, device-specific, and file-object-specific data whenever possible.
- Assume that your driver will run on multiprocessor systems, and test it accordingly.