JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

Polling, Interrupts, and DMA

The last two guides showed why I/O is slow and how buses like PCIe carry it. Now the real question: how does the CPU actually drive a device without wasting its life waiting? Meet the three answers — polling, interrupts, and DMA — and watch a disk read climb each rung from a busy-waiting CPU to one that fires off the job and walks away.

Talking to a device at all

The previous guide left the CPU and a device staring at each other across an interconnect like PCIe, able in principle to exchange bytes. But a processor only knows how to do a handful of things — fetch an instruction, read a register, load and store memory. It has no native verb for read a sector from that disk. So how does software command hardware it was never taught about? The trick is to make every device pretend to be memory.

Every device hangs off a small chip called a device controller — a tiny specialist that actually knows how to spin a motor, blink an LED, or shift bits onto a wire. The controller exposes a handful of registers: a command register, a status register, a data register, sometimes an address-and-count pair. With memory-mapped I/O, the operating system maps those controller registers into ordinary physical addresses. Now a plain `store` to address 0x4000_0010 is not a write to RAM at all — it is a command to the disk controller, and a `load` from the neighbouring address reads back its status. The CPU keeps using the only verbs it has; the address bus quietly routes them to a device instead of memory.

Polling: the CPU stands and waits

The simplest way to drive a device is polling, also called busy-waiting. The CPU writes a command into the controller's command register, then sits in a tight loop reading the status register over and over — *are you done? are you done? are you done?* — until a ready bit flips. Picture standing at the microwave watching the timer count down second by second, unable to do anything else until it beeps. Polling is dead simple and has the lowest possible latency to notice a fast event, which is exactly why it survives in places that demand it.

But for a slow device, polling is a catastrophe. Recall from this rung's first guide just how slow I/O is: a disk takes milliseconds to deliver a sector, and a modern CPU runs billions of cycles in one millisecond. If the processor spins in a status loop the whole time, it burns those billions of cycles reading the same not-ready bit, doing zero useful work and heating up the chip for nothing. Each loop iteration is itself a full instruction cycle — fetch the load, execute it, fetch the branch, take it — repeated millions of times to learn one bit of news. Polling spends the CPU's most precious resource, its cycles, to buy almost nothing.

Interrupts: don't call us, we'll call you

The fix is to flip the relationship. Instead of the CPU repeatedly asking the device, let the device tell the CPU when it is ready. This is the interrupt: the device controller raises a signal line, and the hardware forces the CPU to pause whatever it was doing, jump to a special handler routine, deal with the device, and then resume the interrupted program exactly where it left off. Now the CPU can issue the disk command and go run other work — another program, the rest of the OS — and only get pulled back when the data has actually arrived. The microwave that beeps when it is done, while you go do the dishes, is the whole idea in one picture.

  1. The CPU writes the read command and target sector into the disk controller's registers, then returns to running other programs. It does not wait.
  2. Milliseconds later the controller has the data ready and asserts its interrupt line. The CPU finishes its current instruction, then automatically saves its place — at minimum the program counter — and switches to the kernel's interrupt handler.
  3. The handler reads the status register, copies the freshly-arrived bytes from the data register into memory, clears the device's ready flag, and marks the waiting program as runnable again.
  4. The handler returns; the CPU restores the saved place and resumes the interrupted program as if nothing happened. The expensive wait cost the CPU nothing — it was busy elsewhere the whole time.

Interrupts are a giant win for slow, occasional events, but they are not free. Each interrupt forces a context switch — saving and restoring registers, jumping into the kernel and back — which costs hundreds to thousands of cycles of overhead. For a device that fires events rarely (a keypress, a disk seek finishing) that overhead is negligible against the wait it saves. But for a device that delivers data in a furious stream, one interrupt per byte would drown the CPU in handler overhead. That is the gap our last mechanism closes, and it is also why high-rate devices sometimes deliberately fall back to polling: when events are nearly continuous, busy-waiting actually beats paying interrupt overhead on every one.

DMA: hand off the heavy lifting

Interrupts solved the waiting problem, but a subtler waste remains. Even with interrupts, when a sector arrives the CPU still has to copy every byte out of the controller's data register into memory, one load-store at a time. Move a 4 KB block that way and the CPU executes thousands of instructions just shovelling bytes — useful work, but mindless work a processor is overqualified for. Direct memory access (DMA) removes the CPU from the copy entirely. A small DMA engine, usually built into the device controller itself, is given a memory address and a byte count and then transfers the data directly between the device and main memory across the I/O path, no CPU involvement per byte.

Now the division of labour is beautiful. The CPU sets up one transfer — "put 4096 bytes from the disk at memory address X" — and goes back to real work. The DMA engine does the byte-by-byte slog on its own, contending with the CPU only briefly for memory-bus cycles. And when the whole transfer is finished, the controller raises a single interrupt to say "your block is in memory." One command out, one interrupt back, and the thousands of byte-copies in between cost the processor nothing. DMA plus interrupts is how every real disk, network card, and GPU moves bulk data today.

Reading 4 KB from disk -- cost to the CPU

  Polling         CPU spins ~millions of cycles reading
                  the status bit; ZERO other work done.

  Interrupts      CPU issues command, runs other work,
  (no DMA)        takes 1 interrupt -- then still copies
                  ~4096 bytes by hand (thousands of loads).

  Interrupts      CPU issues command + DMA setup, runs
  + DMA           other work, takes 1 interrupt at the end.
                  DMA engine moves all 4096 bytes itself.
                  CPU's bulk-copy cost: ~zero.

The trend: each rung hands more of the grunt work to
hardware, freeing the CPU to do what only it can do.
The same 4 KB disk read, costed three ways. Polling wastes the wait; interrupts reclaim the wait but still pay for the copy; DMA hands off both, leaving the CPU only the setup and one final interrupt.

Choosing among the three

None of the three is simply "best" — each wins in a different regime, and a real system uses all three at once. Polling is right when an event is imminent and you cannot afford the latency of an interrupt's context switch, or when events come so fast that handler overhead would dominate. Interrupts are right for slow or sporadic events, where letting the CPU do other work easily repays the per-event overhead. DMA is right whenever the payload is large, sparing the CPU from being a glorified copy loop. A modern disk read combines them: the OS programs a DMA transfer, the CPU runs other threads, and a lone interrupt fires when the engine finishes.

And here is the honest bottom line this whole rung has been circling. Even with interrupts and DMA doing their best, the device is still milliseconds slow while the CPU is nanoseconds fast — these mechanisms hide the wait but do not abolish it. For a program that reads a lot of data, the disk or the network, not the processor, sets the pace. That is why I/O is so often the real bottleneck, and why the next guide turns to the storage devices themselves — the spinning hard disk and the SSD — to understand exactly where those lost milliseconds go.