Figure 3.4 illustrates how the I/O Manager sets up an IRP, requesting a transfer operation, for drivers that OR their device object(s)’ Flags with DO_DIRECT_IO.
Figure 3.4 Direct I/O on User Buffers
As Figure 3.4 shows, some range of user-space virtual addresses represents the current thread’s buffer, and that buffer’s contents might actually be stored on some number of physically discontiguous pages. An MDL is created to describe this buffer. As already mentioned in Section 3.1, an MDL is an opaque Memory-Manager-defined data structure that maps a particular virtual address range to one or more paged-based physical address ranges.
Figure 3.4 also shows an overview of how drivers can use the IRP’s MdlAddress to transfer data for a read request, when a driver has ORed the device object’s Flags with DO_DIRECT_IO:
As Figure 3.4 also shows, the I/O Manager provides a pointer to this MDL (MdlAddress) in an IRP that requests a transfer operation. Until the I/O Manager or file system calls MmUnlockPages after the driver completes the IRP, the physical pages described in the MDL remain locked down and assigned to the buffer. However, the virtual addresses in such an MDL can become invisible (and invalid), even before the IRP is sent to the device driver or to any intermediate driver that might be layered above the device driver.
When the driver completes the IRP by calling IoCompleteRequest, the I/O Manager or file system releases the MDL’s doubly mapped system-space range if the driver called MmGetSystemAddressForMdl. The I/O manager or file system unlocks the pages described in the MDL, and disposes of the MDL and IRP on the driver’s behalf. For better performance, NT drivers should avoid doubly mapping MDL physical addresses to system space, as described in Step 3, unless they must use virtual addresses. Releasing a doubly mapped system-space address range causes every processor in the machine to have its data cache flushed.
The current user thread’s buffer(s) and the thread itself are guaranteed to be resident in physical memory only while that thread is current. For the thread shown in Figure 3.4, its user buffer’s contents could be paged out to secondary storage while another process’s threads are run. When another process’s thread is run, the system physical memory for the requesting thread’s buffer can be overwritten unless the NT Memory Manager has locked down and preserved the corresponding physical pages that contain the original thread’s buffer.
However, the original thread’s virtual addresses for its buffer do not remain visible while another thread is current, even if the Memory Manager does preserve the buffer’s physical pages. Consequently, NT drivers cannot use a virtual address returned by MmGetMdlVirtualAddress to access memory. Callers of this routine must pass its results to IoMapTransfer (along with the IRP’s MdlAddress pointer) in order to transfer data using system or packet-based DMA.
NT drivers that use DMA also must use an adapter object, described later in Section 3.3. For more information on maintaining cache coherency during DMA and PIO transfers, see also Chapter 16.