Figure 2.2 shows an IRP with two I/O stack locations, but an IRP can have any number of I/O stack locations, depending on how many layered drivers will handle a given request.
Figure 2.3 illustrates in more detail how the drivers of Figure 2.2 use I/O support routines (IoXxx routines) to process the IRP for a read or write request. Figure 2.3 also shows more detail of an IRP’s I/O stack location for a lowest-level driver, such as a physical disk driver.
Figure 2.3 Processing IRPs in Layered NT Drivers
Next, the FSD calls an I/O support routine to access the next-lower-level driver’s I/O stack location in its FSD-allocated IRP in order to set up the request for the next-lower driver, which happens to be the lowest-level driver in Figure 2.3. The FSD then calls an I/O support routine to pass that IRP on to the next driver.
If there were no higher-level driver, such a device driver would check whether the input parameters for an IRP_MJ_XXX operation are valid. If they are, a device driver usually calls I/O support routines to tell the I/O Manager that a device operation is pending on the IRP and to either queue or pass the IRP on to another driver-supplied routine that accesses the target device (here, a physical or logical device: the disk or a partition on the disk).
Figure 2.3 also shows two I/O stack locations in the original IRP because it shows two NT drivers, a file system driver and a mass-storage device driver. The I/O Manager gives each driver in a chain of layered NT drivers an I/O stack location of its own in every IRP that it sets up. The driver-allocated IRPs in Figure 2.3 do not have a stack location for the FSD that created them. Any higher-level driver that allocates IRPs for lower-level drivers also determines how many I/O stack locations the new IRPs should have, according to the StackSize value of the next-lower driver’s device object.
As shown in Figure 2.3, each driver-specific I/O stack location in an IRP contains the following general information:
An NT file system driver accesses the file object through its I/O stack location in IRPs. Other NT drivers usually ignore the file object.
The set of IRP major and minor function codes that a particular NT driver handles can be device-type-specific. However, NT device and intermediate drivers usually handle the following set of basic requests:
For more information about the major function codes and device I/O control codes that NT drivers for particular kinds of devices are required to handle, see the Kernel-Mode Driver Reference.
In general, the I/O Manager sends IRPs with at least two I/O stack locations to mass-storage device drivers because an NT file system is layered over NT drivers for mass-storage devices. The I/O Manager sends IRPs with a single stack location to any physical device driver that has no driver layered above it.
However, the NT I/O Manager provides support for adding a new driver to any chain of existing drivers in the system. For example, an intermediate mirror driver that backs up data on a given disk partition might be inserted between a pair of drivers, such as those shown in Figure 2.3. When this new driver attaches itself to the device driver, the I/O Manager adjusts the number of I/O stack locations in all IRPs it sends to the file system, mirror, and disk device drivers. Every IRP that the file system in Figure 2.3 allocated would also contain another I/O stack location for such a new mirror driver.
Note that this support for adding new NT drivers to an existing chain implies certain restrictions on any particular NT driver’s access to the I/O stack locations in IRPs:
Every writer of a higher-level NT driver must assume that any subsequently added driver will handle the same IRP major function codes (IRP_MJ_XXX) as the displaced next-lower-level driver did.
Every writer of a lowest-level NT driver must assume that the driver can continue to process IRPs using the information passed in its own I/O stack location, whatever the originating source of a given IRP and however many drivers are layered above it.
Like the file system driver shown in Figure 2.3, any new driver that is added to a chain of existing drivers can do all of the following:
For specific information about the support routines that intermediate and lowest-level NT drivers call and about device-type-specific requests these drivers must handle, see the Kernel-Mode Driver Reference.
As shown in Figure 2.3, an NT file system is a two-part driver:
The I/O Manager sends the corresponding IRP to the FSD. If the FSD sets up a completion routine for an IRP, its completion routine is not necessarily called in the original user-mode thread’s context.
An FSD can create a set of driver-dedicated system threads, but most FSDs use system worker threads in order to get work done without tying up user-mode threads that make I/O requests. Any FSD might set up its own process address space in which its driver-dedicated threads execute, but the system-supplied FSDs avoid this practice to conserve system memory.
NT file systems generally use system worker threads to set up and manage internal work queues of IRPs that they send to one or more lower-level drivers, possibly for different devices.
While the physical device driver shown in Figure 2.3 processes each IRP in stages through a set of discrete, driver-supplied routines, it does not use system threads as the file system does. A physical device driver does not need its own thread context unless setting up its device for I/O is such a protracted process that it has a noticable effect on system performance. Few NT device or intermediate drivers need to set up their own driver-dedicated or device-dedicated system threads, and those that do pay a performance penalty caused by context switches to their threads.
Most NT drivers, like the physical device driver in Figure 2.3, execute in an arbitrary thread context: that of whatever thread happens to be current when they are called to process an IRP. Consequently, NT drivers usually maintain state about their I/O operations and the devices they service in a driver-defined part of their device objects, called a device extension.
Each driver-created device object represents a physical, logical, or virtual device for which a particular NT driver carries out I/O requests. For guidelines about how different kinds of device drivers use device objects to represent their respective physical, logical, and virtual devices, see Section 2.5 later in this chapter. For detailed information about creating and setting up a device object, see Chapter 3.
As Figure 2.3 also shows, most NT drivers process each IRP in stages through a driver-supplied set of system-defined standard routines, but drivers at different levels in a chain necessarily have different standard routines. For example, only lowest-level NT drivers handle interrupts from a physical device, so only a lowest-level driver would have an ISR and a DPC that completes interrupt-driven I/O operations. On the other hand, a lowest-level driver cannot register a completion routine for a given IRP as higher-level drivers can, so only a higher-level NT driver would have one or more completion routines like the FSD in Figure 2.3. See Section 2.3 for a brief introduction to the system-defined routines that NT drivers must or can have. See Chapter 4 for an overview of these standard driver routines and subsequent chapters for routine-specific requirements.