The real question: what is the CPU doing while the device works?
In the previous guide you learned that a device does not talk to the CPU directly. It hides behind a controller, a little circuit that exposes a few registers — typically a status register, a control register, and a data register — which the CPU reads and writes through I/O ports or memory-mapped I/O. With that vocabulary in hand, a sharper question comes into focus, and it is the most important one in all of I/O. A keyboard, a disk, a network card — every one of them is achingly slow compared to the CPU. A modern processor executes billions of instructions per second; a disk read can take milliseconds; a key press might be seconds away. So when the CPU starts an I/O operation and the device begins its slow work, the CPU has, in effect, a long empty stretch of time. What does it do with that time? The three classic answers to that one question are polling, interrupts, and DMA.
Think of it as ordering takeout. You phone in your order — that is the CPU writing the command into the device's control register. Now the kitchen cooks, slowly, and you have a choice about how to spend the wait. You could stand at the counter staring at the cooks the entire time. You could go sit down and let them ring a bell when it is ready. Or you could send a runner to fetch it and not think about food at all until the runner sets it on your table. Those three strategies map exactly onto polling, interrupts, and DMA — and just like with dinner, the difference between them is almost entirely about how much of your own attention the wait consumes.
Programmed I/O: polling, or staring at the counter
The simplest scheme is programmed I/O with polling. Here the CPU itself moves every byte, and to know when the device is ready it keeps re-reading the status register in a tight loop. Picture the status register as having a single busy bit: while the device is working the bit is 1, and when it has finished the bit drops to 0. The CPU asks 'are you ready? are you ready? are you ready?' thousands of times until at last it sees the bit clear, then transfers one chunk of data, then loops back to ask again for the next chunk. That repeated checking is exactly the 'staring at the counter' strategy: the CPU is fully occupied doing nothing but watching.
// Polling loop: programmed I/O writing one byte
write command into the control register // place the order
loop:
read the status register // "are you ready?"
if busy bit is still 1, goto loop // ...spin, spin, spin
write the next byte into the data register // device is ready: hand it over
// ...repeat the whole loop for every bytePolling has a genuine virtue: it is dead simple and has very low latency, because the moment the device flips its bit the CPU notices almost instantly. For a device that is fast and almost always ready, that responsiveness can actually win. But the cost is brutal when the device is slow. Every cycle the CPU spends spinning in that loop is a cycle stolen from every other process on the machine. This wasteful spinning is busy-waiting, and you met its cousin back in the synchronization rung when a spinlock burned cycles waiting for a lock. Waiting for a disk by polling could waste millions of cycles per request — like standing frozen at the takeout counter for ten minutes while a whole queue of hungry people forms behind you.
Interrupts: let the device ring the doorbell
The fix is to stop staring and let the device tell us when it is done. This is interrupt-driven I/O, and the mechanism behind it is the interrupt — the doorbell of the whole computer. After the CPU starts the I/O, it does not loop; it simply goes off and runs other processes. When the device finally finishes, the controller raises an electrical signal on a dedicated line into the CPU. That signal forces the CPU to pause whatever it is doing right now, save just enough state to come back later, jump to a small kernel routine that handles the device, and then resume the interrupted work as if nothing happened. The CPU went and sat down; the kitchen rang the bell; the CPU got up only when there was actually food to collect.
Notice how this changes the economics completely. The whole stretch of time while the slow device works is no longer wasted spinning — it is given back to other processes. This is precisely why the CPU–I/O burst cycle you met in the scheduling rung even works: a process that issues an I/O can be set aside in a blocked state, the scheduler can run someone else, and the interrupt is the alarm that says 'this process's I/O is done — make it ready again.' Interrupts are the seam that stitches I/O to scheduling. Without them, a single slow read would freeze the entire machine; with them, slow I/O and useful work overlap.
DMA: send a runner so the CPU can leave the room
Interrupts solve the waiting problem, but they leave one stubborn cost untouched. Even with interrupts, in plain programmed I/O it is still the CPU that moves every single byte through the data register, one at a time. For a 4 KB disk block that is thousands of trips, and for a large file transfer it is a colossal waste of the CPU's energy on what is really just copying. Imagine the runner who fetches your food, but you insist on personally carrying each fry from the kitchen to the table by hand. That is the gap direct memory access, or DMA, closes.
A DMA controller is a small dedicated engine that can read and write main memory on its own, without the CPU steering each byte. The CPU's job shrinks to a brief setup: tell the DMA controller the source, the destination address in memory, and how many bytes — then walk away and run other processes. The DMA controller and the device then stream the whole block directly into RAM by themselves. The CPU is bothered exactly once, by a single interrupt, when the entire transfer is complete. This is the takeout runner in full: you place the order and the size, the runner does all the carrying, and you are interrupted only when the full meal lands on your table.
- The driver sets up the DMA controller with the source device, a destination address in RAM, and the byte count, then the CPU is free to run other processes.
- The DMA controller and the device move the data block directly into memory, byte after byte, with no CPU involvement.
- When the transfer finishes, the DMA controller raises a single interrupt to tell the CPU the data is now sitting in RAM.
- The CPU's interrupt handler does a tiny amount of bookkeeping — wakes the waiting process — and returns to what it was doing.
Choosing between the three
It is tempting to rank these as worst-to-best, but that is the wrong picture. Each is the right tool under different conditions. Polling shines when the device is fast and almost always ready, so the loop spins only a moment and you skip all interrupt overhead — many tiny status checks and some high-speed drivers really do poll on purpose. Interrupts shine when events are relatively rare and you want the CPU free in between, which covers most everyday devices like keyboards and mice. DMA shines whenever you must move a large block of data, where saving the CPU from copying thousands of bytes dwarfs everything else. Real systems mix all three freely: a disk read might be set up by the CPU, carried out by DMA, and announced by an interrupt, all in one operation.
The thread tying all three together is the same theme that runs through this whole field: the device is slow, the CPU is precious, and good design is about not wasting the CPU's attention. Polling spends attention to gain simplicity and low latency. Interrupts spend a small per-event overhead to win back the waiting time. DMA spends a one-time setup to win back the copying time. Keep that lens and the rest of this rung will read cleanly. The next guide pries open the interrupt itself — how the CPU knows which handler to run via the interrupt vector, and why the work is split into a fast top half and a deferred bottom half — because every one of these three styles ultimately leans on that mechanism.