How to Control Device Object Reentrancy

0 Likes

Problem:

How to Control Device Object Reentrancy

Resolution:

Device Objects are central to the I/O architecture of Windows NT. They provide targets for I/O requests (IRPs) from applications and drivers. The driver routines that the system calls to process an IRP are referred to as dispatch routines. A driver may supply a dispatch routine for each type of IRP. In its DriverEntry routine, a typical driver initializes the driver objects with pointers to its dispatch routines. In DriverWorks™, member functions of class KDevice play the role of dispatch routines.

Most devices require some kind of serialization of I/O requests, simply because most devices can only do one thing at a time. If a driver started processing each IRP as soon as it was received, it would risk interfering with other requests that were already in progress. The most common approach to handle this problem is to take advantage of the IRP queue built into each device object. The DDK provides services IoMarkPending, IoStartPacket, and IoStartNextPacket to facilitate serializing of requests through a single entry point, generally named StartIo.

The StartIo mechanism handles one kind of reentrancy problem, but there are other levels of reentrancy that the driver may control. For example, it is not that unusual for a driver to require that there be only a single application with a single handle open on a particular device object. In other words, a driver may want to restrict device access to one process at a time. A driver could implement this by calling InterlockedIncrement to test and set a busy flag in the Create handler, but the system will provide the same functionality if the DO_EXCLUSIVE flag is set in the device object. If an application successfully opens a device which has this flag set, then subsequent attempts to open the device will fail with ERROR_ACCESS_DENIED until the original handle is closed. The driver never sees a Create call, because the exclusion is implemented by the system.

If you are using DriverWorks, you can set the DO_EXCLUSIVE bit in the flags argument of the constructor of class KDevice. If you are using the raw DDK, set parameter Exclusive to TRUE in the call to IoCreateDevice. The effect is that there will never be more than one active file object for the device at any given time.

One might think that if a device object has its DO_EXCLUSIVE bit set, then its dispatch routines could not be reentered. That's not the case. The single handle to the device could be shared by several threads, each of which is trying to access the device. In this case, the question of whether the dispatch routines may be reentered depends not on the device object, but on the attributes of the file object. If the call to CreateFile that opened the device was made with flag FILE_FLAG_OVERLAPPED, then the system will allow multiple threads to simultaneously enter the device's dispatch routines. However, it the caller of CreateFile does not set FILE_FLAG_OVERLAPPED, then calls to the device on that handle are serialized. It's entirely possible (as long as DO_EXCLUSIVE is not set) that there could be multiple handles open on a device, some of which allowed asynchronous I/O while others did not. Serialization by the system will only occur on handles for which FILE_FLAG_OVERLAPPED was not set. Note that it doesn't matter if the call to ReadFile, WriteFile, or DeviceIoControl passes a non-NULL OVERLAPPED structure or not; the behavior of the handle is determined when the device is opened.

A driver that wants to prevent an application from using overlapped I/O can put a check in the Create handler of a device, and fail the call if the overlapped I/O is requested. The logic goes like this (assuming DriverWorks):

NTSTATUS MyDevice::Create(KIrp I)

{

ULONG CreateOptions = I.CurrentStackLocation()->Parameters.Create.Options;

if ( (CreateOptions & FILE_SYNCHRONOUS_IO_NONALERT) == 0 )

{

// overlapped I/O was requested

return I.Complete(STATUS_INVALID_PARAMETER);

}

. . .

In summary, device object flag DO_EXCLUSIVE determines if only one file object for a device may exist at a given time. This ensures that only a single process has access to the device. Independent of the DO_EXCLUSIVE flag is the synchronous attribute of the file object through which an application makes an I/O request. This determines if the system waits for a dispatch routine to return before calling into the device again. Correct usage of these features enables control of the circumstances under which dispatch routines for a device object may be reentered.

Old KB# 11871
Comment List
Anonymous
Related Discussions
Recommended