The Big Picture: A Work Order in an Envelope
Think of a big office. A customer asks for something. The front desk writes the request on a form, puts it in an envelope and sends it to department after department. Each department reads it, does its part, maybe stamps it, and passes it on. At the end the envelope comes back with the result.
In Windows, that envelope is the IRP (I/O Request Packet). Almost everything a driver does is in response to an IRP. If you understand IRPs, you understand how requests flow through Windows drivers.
9.1 What an IRP Is
What it is: An IRP is a data structure in kernel memory that describes one I/O request: "read 4096 bytes from this file," "send this to the device," "the device was just removed." It is the standard envelope Windows uses to talk to every driver.
What is inside an IRP:
| Part | Meaning |
|---|---|
| Major function code | The kind of request (see table below) |
| Minor function code | A sub-kind (used mostly by Plug and Play and power requests) |
| Buffer information | Where the data to read or write is in memory, and how big |
| Status block | Where the result goes: success or error, and how many bytes were done |
| Stack locations | One small slot per driver layer in the stack (see 9.3) |
| Cancel / completion info | Needed to cancel the request or run code when it finishes |
The major function codes you should know. Each starts with IRP_MJ_:
| Code | When it is used |
|---|---|
IRP_MJ_CREATE | A program opens a file or device (CreateFile) |
IRP_MJ_CLOSE | The last handle is closed |
IRP_MJ_READ | A program reads data (ReadFile) |
IRP_MJ_WRITE | A program writes data (WriteFile) |
IRP_MJ_DEVICE_CONTROL | A program sends a special command (DeviceIoControl) |
IRP_MJ_PNP | Plug and Play events (start, stop, remove) |
IRP_MJ_POWER | Power changes (sleep, wake) |
Remember the dispatch table in the driver object (Section 4.3)? This is what it is for: one function per major code. When an IRP of type IRP_MJ_READ arrives, the driver runs the function listed for READ.
9.2 IRP Creation by the I/O Manager
What it is: The I/O Manager is a part of the kernel (Chapter 8) that sits between programs and drivers. When a program asks for I/O, the I/O Manager builds the IRP.
Step by step, for a ReadFile call:
1. Program calls ReadFile(handle, buffer, 4096)
│ (a system call: ring 3 ─► ring 0)
▼
2. I/O Manager finds the DEVICE OBJECT behind the handle
▼
3. It ALLOCATES an IRP, fills in:
- major function = IRP_MJ_READ
- length = 4096, buffer = the program's (checked) memory
- one stack location per driver in the stack
▼
4. It sends the IRP to the TOP driver of the stack
How is the buffer handled? A driver in ring 0 must be careful with memory that belongs to a program (a bad pointer would crash the PC). So devices choose one of a few buffering methods:
| Method | Idea |
|---|---|
| Buffered I/O | The I/O Manager makes a safe copy in kernel memory. Simple, a little slower |
| Direct I/O | The program's buffer is locked in RAM and described to the driver, so the device can use DMA straight into it (Section 11). Best for big data |
| Neither | The driver gets the raw program address and must check it itself. Risky and rarely used |
Because every program must go through the I/O Manager, and the I/O Manager checks access rights when the handle is opened (IRP_MJ_CREATE), this is also where security is enforced (Section 2.3).
9.3 The IRP Travels Down the Device Stack
What it is: Remember the device stack from Section 4.5? The IRP enters at the top and moves down, one driver at a time. Each driver has its own I/O stack location inside the IRP: a small slot that holds that driver's view of the request.
For each layer, a driver can do one of three things:
| Choice | What it means | Example |
|---|---|---|
| Pass it down | "Not my job, or I'm only watching." Copy/skip the stack location and send to the next driver | A filter that only counts requests |
| Handle it and complete it | "I can finish this myself." It sets the result and ends the request | A driver answering "yes, device is ready" |
| Queue it (mark pending) | "I started the work. The device needs time. I'll finish later." It returns "pending" | A disk read that takes a moment |
I/O Manager
│ IRP
▼
┌──────────────────┐
│ Upper filter │ looks at it ──► passes down
├──────────────────┤
│ Function driver │ works out the exact device command ──► passes down
├──────────────────┤
│ Bus driver │ sends it on the bus to the hardware
└────────┬─────────┘
▼
HARDWARE (works... later raises an interrupt)
For a keyboard read, the IRP stays pending (waiting) inside the keyboard driver for as long as you don't press a key. When you press a key, the driver fills in the data and completes the IRP, and the waiting program gets its answer. That is why a program can wait for input without wasting CPU.
9.4 IRP Completion Routines
What it is: When a driver finishes an IRP (success or error), it completes it. This sends the IRP back up the stack, in reverse order, to the I/O Manager. On the way up, any driver that registered a completion routine gets called.
Down: top driver ──► middle driver ──► bottom driver ──► hardware
Up: top driver ◄── middle driver ◄── bottom driver ◄── "done!"
▲ ▲
completion completion
routine runs routine runs (if registered)
What a completion routine is good for:
| Use | Example |
|---|---|
| Cleanup | Free memory the driver reserved for this request |
| Post-processing | A filter decrypts data that just came from the disk |
| Retry | If an error occurred, send it again |
| Statistics | Count how many bytes were read |
When the IRP reaches the top, the I/O Manager copies the result (status and byte count) into the program's memory, wakes the waiting program (or signals its completion event) and frees the IRP.
Where the interrupt fits in. Real hardware takes time. When a disk finishes a read, it raises an interrupt (Chapter 4, Section 3). The driver's ISR and DPC (Section 10) run, and the DPC typically completes the IRP. So the full trip is: IRP down → hardware works → interrupt → DPC → IRP completes upward.
9.5 Synchronous vs Asynchronous I/O
What it is: Two ways for a program to wait for I/O.
| Synchronous | Asynchronous (overlapped) | |
|---|---|---|
| The program... | Waits at the call until it is finished | Calls, gets control back at once, and continues |
| How it learns the result | The function returns when done | An event, a callback or a completion port tells it later |
| Simplicity | Simple to write | More work to write |
| Good for | Small, quick tasks | Servers, games, big files; doing many things at once |
| The IRP | Same kind of IRP; the I/O Manager just makes the thread wait | Same IRP; the thread carries on |
Analogy: Synchronous is standing at the counter until your coffee is ready. Asynchronous is taking a buzzer and sitting down; the buzzer rings when it is ready.
In Windows code, asynchronous I/O is chosen by opening the file with the FILE_FLAG_OVERLAPPED flag and passing an OVERLAPPED structure. Windows' high-speed servers use I/O completion ports for this.
Important: Whether the program waits or not is the program's choice. The driver just receives an IRP either way. If the driver can't finish right now, it returns pending, and the I/O Manager decides whether to make the calling thread wait.
Summary
| Term | Plain meaning |
|---|---|
| IRP | The standard "envelope" describing one I/O request |
| Major function code | The request type: CREATE, READ, WRITE, DEVICE_CONTROL, PNP, POWER... |
| I/O Manager | Kernel component that builds the IRP and sends it to the top driver |
| Stack location | One slot per driver inside the IRP |
| Pass down / complete / pending | The three things a driver can do with an IRP |
| Completion routine | Code called as the finished IRP climbs back up |
| Synchronous / asynchronous | Program waits / program carries on and is told later |