The Windows I/O model governs how the system and associated drivers handle I/O requests. The model encompasses more than data transfers to and from a device-it operates as a general packet-based mechanism that handles communication between the clients who issue the requests and the device stack. Clients include applications, kernel subsystems such as the PnP manager, and the drivers themselves.
All Windows I/O requests are carried by I/O request packets (IRPs). In addition to carrying I/O-related requests that applications send to drivers-read, write, create, and so on-IRPs are also used to carry Plug and Play requests, power management requests, WMI requests, notifications of changes in device status, and queries about device and driver resources.
Applications typically access a device synchronously. For example, when an application requests that a driver read some data from a device, the application waits for the data to be returned and then uses the data for some computation. The application then requests more data, does more computation, and so on until all the data has been retrieved.
From the perspective of drivers, however, Windows I/O is inherently asynchronous. For example, when the driver receives a read request from an application, the driver simply returns the requested data if it can do so quickly. However, sometimes read requests require a significant amount of time to satisfy. If so, the driver starts the necessary operation and informs the I/O manager that the request is pending. In the interim, the driver is free to process additional requests.
Applications often use synchronous I/O. In that case, the I/O manager simply waits until the driver finishes the read operation before returning the data to the application. However, an application can also take advantage of the way in which drivers handle I/O and can use asynchronous I/O. When the driver informs the I/O manager that the request is pending, the I/O manager informs the application and returns the thread to the application's control. While the request is being satisfied, the application can then use the thread for other purposes.
Eventually, the device signals the driver that the read operation is complete. The driver informs the I/O manager, which notifies the application, and the application can then process the data. Asynchronous I/O can significantly boost the performance of multithreaded applications, because the application does not waste time or threads waiting for time-consuming I/O requests to be completed.
Occasionally the question comes up about how a driver can tell if an application wants asynchronous or synchronous I/O. Part of the beauty of the Windows I/O model is that you don't need to know, because you can go through the same steps in either case. If the data is available immediately, then complete the request right away. Otherwise, start the process of retrieving the data (running the command, writing the data, and so on) and return control to the I/O manager. In many ways, this reduces the complexity of the device driver.
-Peter Wieland, Windows Driver Foundation Team, Microsoft
The Windows I/O model reflects the layered architecture shown in Figure 2-1. The most common clients of drivers are user-mode applications, which issue I/O requests by obtaining a handle for the device and calling an appropriate Windows function. The request propagates downward through the system until it reaches the driver and eventually the device. The system then returns any response to the application.
The following are the most common types of I/O requests:
Write requests These requests pass data to the driver to be written to the device.
Read requests These requests pass a buffer to the driver to be filled with data from the device.
Device I/O control requests These requests are used to communicate with drivers for any purpose other than reading or writing data. For some devices, I/O control requests are the most common request type.
The standard Windows functions that applications use to issue these I/O requests are WriteFile, ReadFile, and DeviceIoControl, respectively.
Tip See "Device Management" in the WDK for information about how an application obtains a device handle-online at http://go.microsoft.com/fwlink/?LinkId=82273.
When a client sends an I/O request to a device, the I/O manager packages the request and related information such as data buffers into an IRP. The I/O manager locates the pointer to the appropriate dispatch routine in the top device object in the stack and calls the routine and passes the IRP to the associated driver for processing. Figure 2-4 shows how the device stack handles the IRP.
Figure 2-4: How a device stack handles IRPs
If the top driver can satisfy the IRP, it directs the I/O manager to complete the request. This action returns the IRP to the I/O manager, which in turn passes any requested data back to the client. However, a driver cannot always satisfy a request by itself. In that case, the driver processes the IRP as required and then directs the I/O manager to pass the IRP to the next lower driver in the device stack. That driver does what it can to satisfy the request, and so on. For example, a filter driver might modify a write IRP, but then it must pass the IRP down the stack so that the function driver can transfer the data to the device and complete the request. Sometimes IRPs must be passed all the way to the bus driver.
Eventually, the request arrives at a driver that can satisfy and complete the request. Drivers must often communicate with the device to satisfy a request, either directly through hardware registers or indirectly through a lower-level protocol driver. Occasionally, the communication can be handled quickly and the driver can promptly return any requested data to the client. More often, communication with the device involves a lengthy delay-in terms of CPU cycles-before the device responds. In these cases, the driver can return control to the client with the request still pending and complete the request when the communication process has finished.
When a driver passes a request down the stack, some additional processing might be required after the request has been completed but before it is returned to the client. If so, a driver can optionally set an I/O completion routine before calling the I/O manager to pass the IRP to the next driver. When the request is finally completed, the I/O manager calls any completion routines in the opposite order in which they were set; that is, it "unwinds" back up the device stack.
Chapter 8, "I/O Flow and Dispatching," provides a discussion of the I/O process.
IRPs convey three types of information:
Control information, such as the type of request.
Information about the status of request process.
Data buffers for the driver to transfer from client to driver or vice versa.
In particular, these buffers contain the data that is to be transferred to or from the device.
Usually, the driver does not care what data is in the buffers, it just needs to transfer the bytes to or from the device. Drivers access the data buffers that are associated with an IRP in one of the following ways:
Buffered I/O The I/O manager creates a system buffer equal in size to the buffer that the client created and copies the client's data into that buffer. The I/O manager then passes the driver a pointer to the system buffer in the IRP, and the driver reads from and writes to the system buffer. This transfer type is sometimes referred to as METHOD_BUFFERED.
Direct I/O Windows locks the client's buffer in memory. It then passes the driver a memory descriptor list (MDL), which is a list of the memory pages that make up the buffer. The driver can use the MDL to access the pages. This transfer type is sometimes referred to as METHOD_DIRECT.
Neither buffered nor direct I/O Windows passes the buffer's size and its starting address in the client's address space. This transfer type is sometimes referred to as METHOD_NEITHER or, more simply, "neither I/O."
Client and driver run in different address spaces, so the driver must access the buffer carefully. In particular, a driver cannot simply dereference a pointer to a user-mode buffer with any certainty that the data at that address is meaningful or that the pointer is even valid.
The following three transfer types have varying degrees of risk:
Buffered I/O is inherently safe.
The system guarantees the validity of the buffer by creating it in the kernel-mode address space and copying the data to and from user mode.
Direct I/O has some risks.
The system guarantees the validity of the user-mode addresses in the buffer by locking the pages into memory. However, there is no guarantee that the application will not change the data at those addresses.
Neither I/O is inherently unsafe.
Neither I/O simply passes the buffer's user-mode address to the driver. The driver has no assurance that the pointer is even valid by the time it attempts to access the buffer. A driver can safely access the user-mode buffer, but only if the driver takes steps to ensure that the buffer pointer is valid.
Chapter 8, "I/O Flow and Dispatching," provides a discussion of how to perform I/O by using each of these transfer mechanisms.
Many I/O requests require the driver to transfer data to or from a device. Typically, data transfer is handled by the device's function driver. When the transfer is complete, a device typically signals an interrupt to notify the driver. At the most general level, an interrupt notifies the system about an event that must be dealt with immediately. The name derives from the fact that the system must interrupt normal thread processing to immediately deal with the event.
The details of the data transfer process depend on the type of device:
For protocol-based devices such as USB devices and certain other types of devices, the process of transferring data to the device is handled by system-provided drivers.
The function driver packages the buffer in the appropriate format and passes it to the system-provided driver. That driver manages the data transfer and handles the interrupt. The system-provided driver then returns control to the function driver, along with any requested data.
For other types of devices, the driver must directly handle the process of transferring the data to or from the device by using DMA.
The driver must also implement an interrupt service routine (ISR) to handle the interrupt that is generated when the transaction is complete.
Chapter 16, "Hardware Resources and Interrupts," discusses interrupts. Chapter 17, "Direct Memory Access," contains a discussion of DMA techniques.