[oR]
Processing Read and Write
Requests
The most basic of I/O requests is to exchange data between a
user
buffer and a device. The I/O Manager
presents
a traditional read/write abstraction to requestors for such data transfers. The requests are presented to a driver in the form of an IRP with major function code of either IRP_MJ_READ or IRP_MJ_WRITE. Another field within the IRP specifies the address of the
requestor
's buffer. Whether the buffer address is a direct virtual address or an intermediate nonpaged pool buffer allocated and
maintained
by the I/O Manager is determined by the device object's
Flags
field. Regardless, it is the responsibility of the read and write Dispatch routines to transfer data between the buffer and the actual device for the
requested
number of bytes.
User Buffer Access
As discussed in chapter 6, a driver can specify DO_DIRECT_IO or DO_BUFFERED_IO on a per-device basis (via the
Flags
field of the device object.) The exact behavior of the I/O Manager as well as the
subsequent
responsibilities of the Dispatch routine is discussed in the following sections.
BUFFERED I/O
At the start of either a read or write operation, the I/O Manager
validates
that all virtual memory pages
spanned
by the user's buffer are valid. For buffered I/O, it then
allocates
a nonpaged pool buffer of a
size
sufficient to hold the user's request. The address of this temporary buffer is place in the IRP field
AssociatedIrp.SystemBuffer
. This address remains valid for the duration of the transfer (i.e., until the IRP is
marked
as complete).
For read operations, the I/O Manager remembers the address of the original user buffer in the
UserBuffer
field of the IRP. It then uses this retained address upon completion of the request to copy data from the nonpaged pool into user memory.
For write operations, the I/O Manager copies the user buffer into the nonpaged buffer before invoking the write Dispatch routine. It then sets the
UserBuffer
field of the IRP to NULL, since there is no additional need to retain this state.
DIRECT I/O
At the start of the operation, the I/O Manager validates the page table entries spanned by the user's buffer. It then builds a data structure known as a
Memory Descriptor List
(MDL) and stores the address of the MDL in the IRP's
MdlAddress
field. Both the
AssociatedIrp. SystemBuffer
and
UserBuffer
fields are set to NULL.
For DMA operations, the MDL structure is used directly with an adapter object to perform the data transfer. Chapter 12 will discuss this process in detail. For programmed I/O devices, the MDL can be used with the function
MmGetSystemAddressForMdl
to get a system-address view of the user buffer. Using this technique, the user's buffer is locked down into physical memory (i.e., forced to be nonpagable) and is made accessible to driver code via an address above 0x80000000. (User-mode code must still access the same physical memory with its original address below 0x7FFFFFFF.) When the I/O request is ultimately completed, the user buffer is unlocked and unmapped from system address space.
NEITHER
METHOD
There are two bits within the
Flags
field of the device object which specify either DO_BUFFERED_IO or DO_DIRECT_IO. If neither bit is set for a device, the I/O Manager
performs
neither action specified above. Instead, it simply places the user-space address of the requestor's buffer into the IRP's
UserBuffer
field. The
AssociatedIrp.SystemBuffer
and
MdlAddress
fields are set to NULL.
A simple user-mode address is not terribly useful. Most routines within a driver cannot be assured that at the time of their execution the original requestor's page tables are mapped. Thus, the user buffer address is usually worthless. There is one exception: At the time a Dispatch routine of a highest-level driver is invoked, execution occurs using the original requestor's thread. As such, the user-space address is mapped and valid. An intermediate driver or any DPC or Interrupt Service Routine (ISR) can never rely upon a user-space buffer being valid.
|