The I/O Manager creates this MDL for IRP_MJ_READ and IRP_MJ_WRITE requests if the topmost device object’s flags indicate DO_DIRECT_IO.. For IRP_MJ_READ and IRP_MJ_WRITE operations, the I
Trang 1Các cấu trúc dữ liệu (Data
Structures )
Bởi:
Khoa CNTT ĐHSP KT Hưng Yên
Two data structures are crucial to the handling of I/O requests: the I/O request packet itself and the IO_STACK_LOCATION structure I’ll describe both structures in this section
Structure of an IRP
Figure 5-1 illustrates the IRP data structure, with opaque fields shaded in the usual convention of this book A brief description of the important fields follows
Trang 2MdlAddress (PMDL) is the address of a memory descriptor list (MDL) describing the user-mode buffer associated with this request The I/O Manager creates this MDL for IRP_MJ_READ and IRP_MJ_WRITE requests if the topmost device object’s flags indicate DO_DIRECT_IO It creates an MDL for the output buffer used with an
METHOD_IN_DIRECT or METHOD_OUT_DIRECT The MDL itself describes the user-mode virtual buffer and also contains the physical addresses of locked pages containing that buffer A driver has to do additional work, which can be quite minimal,
to actually access the user-mode buffer
Figure 5-1 I/O request packet data structure.
Flags (ULONG) contains flags that a device driver can read but not directly alter None
of these flags are relevant to a Windows Driver Model (WDM) driver
Trang 3AssociatedIrp (union) is a union of three possible pointers The alternative that a typical WDM driver might want to access is named AssociatedIrp.SystemBuffer The SystemBuffer pointer holds the address of a data buffer in nonpaged kernel-mode memory For IRP_MJ_READ and IRP_MJ_WRITE operations, the I/O Manager creates this data buffer if the topmost device object’s flags specify DO_BUFFERED_IO For IRP_MJ_DEVICE_CONTROL operations, the I/O Manager creates this buffer if the I/O control function code indicates that it should (See Chapter 9.) The I/O Manager copies data sent by user-mode code to the driver into this buffer
as part of the process of creating the IRP Such data includes the data involved in a WriteFile call or the so-called input data for a call to DeviceIoControl For read requests, the device driver fills this buffer with data; the I/O Manager later copies the buffer back
to the user-mode buffer For control operations that specify METHOD_BUFFERED, the driver places the so-called output data in this buffer, and the I/O Manager copies it
to the user-mode output buffer
IoStatus (IO_STATUS_BLOCK) is a structure containing two fields that drivers set when they ultimately complete a request IoStatus.Status will receive an NTSTATUS code, while IoStatus.Information is a ULONG_PTR that will receive an information value whose exact content depends on the type of IRP and the completion status A common use of the Information field is to hold the total number of bytes transferred by
an operation such as IRP_MJ_READ that transfers data Certain Plug and Play (PnP) requests use this field as a pointer to a structure that you can think of as the answer to a query
RequestorMode will equal one of the enumeration constants UserMode or KernelMode, depending on where the original I/O request originated Drivers sometimes inspect this value to know whether to trust some parameters
PendingReturned (BOOLEAN) is meaningful in a completion routine and indicates whether the next lower dispatch routine returned STATUS_PENDING This chapter contains a disagreeably long discussion of how to use this flag
Cancel (BOOLEAN) is TRUE if IoCancelIrp has been called to cancel this request and FALSE if it hasn’t (yet) been called IRP cancellation is a relatively complex topic that I’ll discuss fully later on in this chapter (in “Cancelling I/O Requests”)
CancelIrql (KIRQL) is the interrupt request level (IRQL) at which the special cancel spin lock was acquired You reference this field in a cancel routine when you release the spin lock
CancelRoutine (PDRIVER_CANCEL) is the address of an IRP cancellation routine
in your driver You use IoSetCancelRoutine to set this field instead of modifying it directly
Trang 4UserBuffer (PVOID) contains the user-mode virtual address of the output buffer for
an IRP_MJ_DEVICE_CONTROL request for which the control code specifies METHOD_NEITHER It also holds the user-mode virtual address of the buffer for read and write requests, but a driver should usually specify one of the device flags DO_BUFFERED_IO or DO_DIRECT_IO and should therefore not usually need to access the field for reads or writes When handling a METHOD_NEITHER control operation, the driver can create its own MDL using this address
Tail.Overlay is a structure within a union that contains several members potentially useful to a WDM driver Refer to Figure 5-2 for a map of the Tail union In the figure, items at the same level as you read left to right are alternatives within a union, while the vertical dimension portrays successive locations within a structure
Tail.Overlay.DriverContext (PVOID[4]) are alternatives within an unnamed union within Tail.Overlay The I/O Manager uses DeviceQueueEntry as a linking field within the standard queue of requests for a device The cancel-safe queuing routines IoCsqXxx use the last entry in the DriverContext array If these system usages don’t get in your way, at moments when the IRP is not in some queue that uses this field and when you own the IRP, you can use the four pointers in DriverContext in any way you please Tail.Overlay.ListEntry (LIST_ENTRY) is available for you to use as a linking field for IRPs in any private queue you choose to implement
(PIO_STACK_LOCATION) aren’t documented for use by drivers because support functions such as IoGetCurrentIrpStackLocation can be used instead During debugging, however, it might help you to realize that CurrentLocation is the index of the current I/O stack location and CurrentStackLocation is a pointer to it
Trang 5Figure 5-2 Map of the Tail union in an IRP.
The I/O Stack
Whenever any kernel-mode program creates an IRP, it also creates an associated array
of IO_STACK_LOCATION structures: one stack location for each of the drivers that will process the IRP and sometimes one more stack location for the use of the originator
of the IRP (See Figure 5-3.) A stack location contains type codes and parameter information for the IRP as well as the address of a completion routine Refer to Figure 5-4 for an illustration of the stack structure
Trang 6Figure 5-3 Parallelism between driver and I/O stacks.
A final consideration in calling the two synchronous IRP routines is that you can’t create just any kind of IRP using these routines See Table 5-1 for the details A common trick for creating another kind of synchronous IRP is to ask for an IRP_MJ_SHUTDOWN, which has no parameters, and then alter the MajorFunction code in the first stack location
Table 5-1 Synchronous IRP
Types
IoBuildSynchronousFsdRequest
IRP_MJ_READ IRP_MJ_WRITE IRP_MJ_FLUSH_BUFFERS IRP_MJ_SHUTDOWN IRP_MJ_PNP IRP_MJ_POWER (but only for
IRP_MN_POWER_SEQUENCE) IoBuildDeviceIoControlRequest IRP_MJ_DEVICE_CONTROL
IRP_MJ_INTERNAL_DEVICE_CONTROL Creating Asynchronous IRPs
The other two IRP creation functions—IoBuildAsynchronousFsdRequest and IoAllocateIrp—create an asynchronous IRP Asynchronous IRPs don’t belong to the
Trang 7creating thread, and the I/O Manager doesn’t schedule an APC and doesn’t clean up when the IRP completes Consequently:
• When a thread terminates, the I/O Manager doesn’t try to cancel any
asynchronous IRPs that you happen to have created in that thread
• It’s OK to create asynchronous IRPs in an arbitrary or nonarbitrary thread
• Because the I/O Manager doesn’t do any cleanup when the IRP completes, you must provide a completion routine that will release buffers and call IoFreeIrp to release the memory used by the IRP
• Because the I/O Manager doesn’t automatically cancel asynchronous IRPs, you might have to provide code to do that when you no longer want the operation to occur
• Because you don’t wait for an asynchronous IRP to complete, you can create and send one at IRQL <= DISPATCH_LEVEL (assuming, that is, that the driver to which you send the IRP can handle the IRP at elevated IRQL—you must check the specifications for that driver!) Furthermore, it’s OK to create and send an asynchronous IRP while owning a fast mutex
Refer to Table 5-2 for a list of the types of IRP you can create using the two asynchronous IRP routines Note that IoBuildSynchronousFsdRequest and IoBuildAsynchronousFsdRequest support the same IRP major function codes
Table 5-2 Asynchronous IRP
Types
IoBuildAsynchronousFsdRequest
IRP_MJ_READ IRP_MJ_WRITE IRP_MJ_FLUSH_BUFFERS IRP_MJ_SHUTDOWN IRP_MJ_PNP IRP_MJ_POWER (but only for
IRP_MN_POWER_SEQUENCE) IoAllocateIrp Any (but you must initialize the MajorFunction
field of the first stack location)
IRP-handling scenario numbers 5 and 8 at the end of this chapter contain “cookbook” code for using asynchronous IRPs
Forwarding to a Dispatch Routine
After you create an IRP, you call IoGetNextIrpStackLocation to obtain a pointer to the first stack location Then you initialize just that first location If you’ve used IoAllocateIrp to create the IRP, you need to fill in at least the MajorFunction code
Trang 8If you’ve used another of the four IRP-creation functions, the I/O Manager might have already done the required initialization You might then be able to skip this step, depending on the rules for that particular type of IRP Having initialized the stack, you call IoCallDriver to send the IRP to a device driver:
PDEVICE_OBJECT DeviceObject; // <== somebody gives you this
PIO_STACK_LOCATION stack = IoGetNextIrpStackLocation(Irp);
stack->MajorFunction = IRP_MJ_Xxx;
<other initialization of "stack">NTSTATUS status = IoCallDriver(DeviceObject, Irp);
The first argument to IoCallDriver is the address of a device object that you’ve obtained somehow Often you’re sending an IRP to the driver under yours in the PnP stack In that case, the DeviceObject in this fragment is the LowerDeviceObject you saved in your device extension after calling IoAttachDeviceToDeviceStack I’ll describe some other common ways of locating a device object in a few paragraphs
The I/O Manager initializes the stack location pointer in the IRP to 1 before the actual first location Because the I/O stack is an array of IO_STACK_LOCATION structures, you can think of the stack pointer as being initialized to point to the “-1” element, which doesn’t exist (In fact, the stack “grows” from high toward low addresses, but that detail shouldn’t obscure the concept I’m trying to describe here.) We therefore ask for the
“next” stack location when we want to initialize the first one
What IoCallDriver Does
You can imagine IoCallDriver as looking something like this (but I hasten to add that this is not a copy of the actual source code):
NTSTATUS IoCallDriver(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
IoSetNextIrpStackLocation(Irp);
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
stack->DeviceObject = DeviceObject;
ULONG fcn = stack->MajorFunction;
PDRIVER_OBJECT driver = DeviceObject->DriverObject;
Trang 9return (*driver->MajorFunction[fcn])(DeviceObject, Irp);
}
As you can see, IoCallDriver simply advances the stack pointer and calls the appropriate dispatch routine in the driver for the target device object It returns the status code that that dispatch routine returns Sometimes I see online help requests wherein people attribute one or another unfortunate action to IoCallDriver (For example, “IoCallDriver
is returning an error code for my IRP….”) As you can see, the real culprit is a dispatch routine in another driver
Locating Device Objects
Apart from IoAttachDeviceToDeviceStack, drivers can locate device objects in at least two ways I’ll tell you here about IoGetDeviceObjectPointer and IoGetAttachedDeviceReference
IoGetDeviceObjectPointer
If you know the name of the device object, you can call IoGetDeviceObjectPointer as shown here:
PUNICODE_STRING devname; // <== somebody gives you this
ACCESS_MASK access; // <== more about this later
PDEVICE_OBJECT DeviceObject;
PFILE_OBJECT FileObject;
NTSTATUS status;
ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
status = IoGetDeviceObjectPointer(devname, access,
&FileObject, &DeviceObject);
This function returns two pointers: one to a FILE_OBJECT and one to a DEVICE_OBJECT
To help defeat elevation-of-privilege attacks, specify the most restricted access consistent with your needs For example, if you’ll just be reading data, specify FILE_READ_DATA
Trang 10When you create an IRP for a target you discover this way, you should set the FileObject pointer in the first stack location Furthermore, it’s a good idea to take an extra reference
to the file object until after IoCallDriver returns The following fragment illustrates both these ideas:
PIRP Irp = IoXxx( );
PIO_STACK_LOCATION stack = IoGetNextIrpStackLocation(Irp);
ObReferenceObject(FileObject);
stack->FileObject = FileObject;<etc.>
IoCallDriver(DeviceObject, Irp);
ObDereferenceObject(FileObject);
After making this call, don’t use either of the file or device object pointers
IoGetDeviceObjectPointer performs several steps to locate the two pointers that it returns to you:
1 It uses ZwOpenFile to open a kernel handle to the named device object
Internally, this will cause the Object Manager to create a file object and to send
an IRP_MJ_CREATE to the target device ZwOpenFile returns a file handle
2 It calls ObReferenceObjectByHandle to get the address of the FILE_OBJECT that the handle represents This address becomes the FileObject return value
3 It calls IoGetRelatedDeviceObject to get the address of the DEVICE_OBJECT
to which the file object refers This address becomes the DeviceObject return value
4 It calls ZwClose to close the handle
Names for Device Objects
For you to use IoGetDeviceObjectPointer, a driver in the stack for the device to which you want to connect must have named a device object We studied device object naming
in Chapter 2 Recall that a driver might have specified a name in the \Device folder in its call to IoCreateDevice, and it might have created one or more symbolic links in the
\DosDevices folder If you know the name of the device object or one of the symbolic links, you can use that name in your call to IoGetDeviceObjectPointer
Mechanically, completing an IRP entails filling in the Status and Information members within the IRP’s IoStatus block and calling IoCompleteRequest The Status value is one of the codes defined by manifest constants in the DDK header file NTSTATUS.H
Trang 11Refer to Table 5-3 for an abbreviated list of status codes for common situations The Information value depends on what type of IRP you’re completing and on whether you’re causing the IRP to succeed or to fail Most of the time, when you’re causing
an IRP to fail (that is, completing it with an error status of some kind), you’ll set Information to 0 When you cause an IRP that involves data transfer to succeed, you ordinarily set the Information field equal to the number of bytes transferred
Table 5-3 Some Commonly Used
NTSTATUS Codes
STATUS_UNSUCCESSFUL Request failed, but no other status code
describes the reason specifically
STATUS_NOT_IMPLEMENTED A function hasn’t been implemented STATUS_INVALID_HANDLE An invalid handle was supplied for an -operation. STATUS_INVALID_PARAMETER A parameter is in error
STATUS_INVALID_DEVICE_REQUEST The request is invalid for this device
STATUS_DELETE_PENDING The device is in the process of being
removed from the system
STATUS_INSUFFICIENT_RESOURCES Not enough system resources (often -memory) to perform an operation.
When you call IoCompleteRequest, you supply a priority boost value to be applied to whichever thread is currently waiting for this request to complete You normally choose
a boost value that depends on the type of device, as suggested by the manifest constant names listed in Table 5-4 The priority adjustment improves the throughput of threads that frequently wait for I/O operations to complete Events for which the end user is directly responsible, such as keyboard or mouse operations, result in greater priority boosts in order to give preference to interactive tasks Consequently, you want to choose the boost value with at least some care Don’t use IO_SOUND_INCREMENT for absolutely every operation a sound card driver finishes, for example—it’s not necessary
to apply this extraordinary priority increment to a get-driver-version control request Table 5-4 Priority Boost Values for IoCompleteRequest