Overview
Buffering determines how data is transferred between user‑mode applications and kernel‑mode drivers. Windows provides three primary buffering models:
- Buffered I/O – System allocates an intermediate buffer in non‑paged pool.
- Direct I/O – Driver receives a pointer to the user buffer after MDL locking.
- Cached I/O – Uses the system cache; suitable for file system operations.
Buffered I/O
Ideal for small control requests where data size does not exceed IRP_STACK_LOCATION::Parameters.DeviceIoControl.OutputBufferLength
.
NTSTATUS
MyDeviceControl(
_In_ PDEVICE_OBJECT DeviceObject,
_Inout_ PIRP Irp)
{
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp);
ULONG inLen = irpSp->Parameters.DeviceIoControl.InputBufferLength;
ULONG outLen = irpSp->Parameters.DeviceIoControl.OutputBufferLength;
PUCHAR inBuf = (PUCHAR)Irp->AssociatedIrp.SystemBuffer;
PUCHAR outBuf = (PUCHAR)Irp->AssociatedIrp.SystemBuffer;
// Process data...
RtlCopyMemory(outBuf, inBuf, min(inLen, outLen));
Irp->IoStatus.Information = outLen;
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
Direct I/O
Used for large data transfers. The driver works directly with the user buffer after the I/O manager locks it via an MDL.
NTSTATUS
MyRead(
_In_ PDEVICE_OBJECT DeviceObject,
_Inout_ PIRP Irp)
{
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp);
PMDL mdl = Irp->MdlAddress;
PUCHAR buffer = MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority);
if (!buffer) return STATUS_INSUFFICIENT_RESOURCES;
// Fill buffer with data...
RtlZeroMemory(buffer, irpSp->Parameters.Read.Length);
Irp->IoStatus.Information = irpSp->Parameters.Read.Length;
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
Cached I/O
Recommended for file system drivers. The I/O manager caches data, reducing physical I/O operations.
NTSTATUS
MyCachedRead(
_In_ PDEVICE_OBJECT DeviceObject,
_Inout_ PIRP Irp)
{
Irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_NOT_SUPPORTED;
}
Best Practices
- Prefer Direct I/O for large buffers to avoid extra copy.
- Validate all lengths against
IRP
parameters. - Always check the result of
MmGetSystemAddressForMdlSafe
. - Use
NT_SUCCESS
macro for status checks. - Release resources in the
IRP_MJ_CLEANUP
&IRP_MJ_CLOSE
handlers.