IRP Processing Overview
An I/O Request Packet (IRP) is the core mechanism the Windows kernel uses to communicate I/O requests between the I/O manager, drivers, and the underlying hardware. Understanding the IRP life‑cycle and the responsibilities of each driver in the stack is essential for writing robust kernel‑mode drivers.
- Created by I/O manager for each I/O operation.
- Traverses the driver stack from the highest level (client driver) to the lowest (lower‑level driver or device).
- Each driver can complete, forward, or modify the IRP.
- When the IRP reaches the lower driver, the I/O manager may call the driver’s
Dispatch
routine, complete it synchronously, or queue it for asynchronous processing.
IRP Lifecycle
- Allocation – The I/O manager allocates an IRP with
IoAllocateIrp
(or creates a system‑generated IRP for certain operations).
- Initialization – Stack locations are filled in for each driver in the stack.
- Dispatch – The target driver’s
IRP_MJ_*
dispatch routine processes the request.
- Forwarding – A driver may forward the IRP down the stack using
IoCallDriver
.
- Completion – The lowest driver completes the IRP with
IoCompleteRequest
, unwinding the stack.
- Cleanup – The IRP is freed by the I/O manager.
Stage | Key Routine | Typical Action |
Allocate | IoAllocateIrp | Reserve memory for IRP structure. |
Initialize | IoSetCurrentIrpStackLocation | Set up driver‑specific parameters. |
Dispatch | DriverDispatchXxx | Handle request or forward. |
Complete | IoCompleteRequest | Notify I/O manager of final status. |
Major IRP Types
Each IRP is identified by a major function code (IRP_MJ_*
) that indicates the kind of operation requested.
IRP_MJ_CREATE
– Open/create a handle to a device.
IRP_MJ_READ
– Read data from a device.
IRP_MJ_WRITE
– Write data to a device.
IRP_MJ_DEVICE_CONTROL
– Send an I/O control code.
IRP_MJ_CLOSE
– Close a handle.
IRP_MJ_POWER
– Power state changes.
IRP_MJ_PNP
– Plug and Play requests.
Drivers also handle minor function codes that refine the operation (e.g., IRP_MN_START_DEVICE
for PNP).
Sample Driver – Simple Read/Write
The code below demonstrates a minimal driver that processes IRP_MJ_READ
and IRP_MJ_WRITE
requests.
// SimpleReadWrite.c
#include <ntddk.h>
VOID DriverUnload(_In_ PDRIVER_OBJECT DriverObject) {
KdPrint(("SimpleReadWrite: Unloaded\n"));
}
NTSTATUS
SimpleRead(
_In_ PDEVICE_OBJECT DeviceObject,
_Inout_ PIRP Irp
) {
UNREFERENCED_PARAMETER(DeviceObject);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0; // no data to return
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS
SimpleWrite(
_In_ PDEVICE_OBJECT DeviceObject,
_Inout_ PIRP Irp
) {
UNREFERENCED_PARAMETER(DeviceObject);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = Irp->AssociatedIrp.SystemBuffer
? Irp->Tail.Overlay.CurrentStackLocation->Parameters.Write.Length
: 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
extern "C"
NTSTATUS
DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
UNREFERENCED_PARAMETER(RegistryPath);
DriverObject->DriverUnload = DriverUnload;
DriverObject->MajorFunction[IRP_MJ_READ] = SimpleRead;
DriverObject->MajorFunction[IRP_MJ_WRITE] = SimpleWrite;
KdPrint(("SimpleReadWrite: Loaded\n"));
return STATUS_SUCCESS;
}
Compile with the Windows Driver Kit (WDK) and load the driver to see the IRP flow in the kernel debugger.
Best Practices for IRP Handling
- Always set
Irp->IoStatus.Status
before completing.
- Release any allocated resources (IRPs, MDLs, buffers) before returning.
- Use
IoMarkIrpPending
for asynchronous operations and complete later.
- Validate user‑mode buffers with
ProbeForRead
/ProbeForWrite
when applicable.
- Keep the IRP stack location pointer consistent with
IoGetCurrentIrpStackLocation
.
- Minimize work in dispatch routines; defer heavy processing to worker threads or system work items.