Windows Kernel I/O Buffering

Overview

Buffering determines how data is transferred between user‑mode applications and kernel‑mode drivers. Windows provides three primary buffering models:

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

  1. Prefer Direct I/O for large buffers to avoid extra copy.
  2. Validate all lengths against IRP parameters.
  3. Always check the result of MmGetSystemAddressForMdlSafe.
  4. Use NT_SUCCESS macro for status checks.
  5. Release resources in the IRP_MJ_CLEANUP & IRP_MJ_CLOSE handlers.

See Also