Polling burns the CPU. The fix is an old friend from the process chapters: the interrupt.
36.4 Lowering CPU Overhead With Interrupts
Instead of polling, the OS issues the request, puts the process to sleep, and context-switches to other work. When the device finishes it raises a hardware interrupt interrupt A hardware signal a device raises when it finishes an operation, causing the CPU to stop its current work and jump into an OS interrupt handler. Interrupts let the OS sleep the waiting process and run other work during slow I/O โ overlapping computation and I/O โ instead of polling. Best for slow devices; for fast ones the handling/context-switch cost can outweigh the benefit. defined in ch. 36 โ open in glossary , and the CPU jumps into an OS interrupt handler interrupt handler The interrupt service routine (ISR) โ the OS code that runs when a device raises an interrupt. It finishes the request (reads data and any error code from the device) and wakes the process that was waiting on the I/O. defined in ch. 36 โ open in glossary (ISR) that completes the request and wakes the waiting process. This lets computation and I/O overlap:
Process 1 issues an I/O request; the OS puts it to sleep โ no polling needed.
Tip: Interrupts Are Not Always Better Than Polling
Interrupts win for slow devices. For a fast one, the first poll usually finds it already done โ and the cost of sleeping, handling the interrupt, and switching back outweighs the benefit, so polling is better. A flood of interrupts (e.g. a burst of network packets) can even livelock livelock A failure where threads are not blocked (not deadlocked) yet make no progress โ e.g. two threads each grab lock 1, fail to trylock lock 2, release lock 1, and retry in lockstep forever. Unlike deadlock the threads keep running; fixed by adding a random back-off delay before retrying. defined in ch. 32 โ open in glossary the system โ only ever handling interrupts, never running user code โ where polling regains control. If speed varies, a hybrid (poll briefly, then switch to interrupts) can be best. Another trick, interrupt coalescing interrupt coalescing An optimization where a device briefly delays raising an interrupt so that several requests completing around the same time can be reported in ONE interrupt delivery, lowering per-interrupt overhead โ at the cost of a little added latency. defined in ch. 36 โ open in glossary , delays an interrupt briefly so several completions batch into one delivery โ lower overhead, slightly more latency.36.5 More Efficient Data Movement With DMA
Interrupts fix polling, but PIO still wastes the CPU copying data by hand. Direct Memory Access (DMA) direct memory access DMA: a dedicated engine that orchestrates data transfers between a device and main memory without much CPU involvement. The OS programs it (where the data lives, how much, and which device), then does other work; the DMA controller raises an interrupt when the transfer is complete. Frees the CPU from copying data by hand (as in PIO). defined in ch. 36 โ open in glossary is a dedicated engine that copies data between device and memory without the CPU: the OS tells it what to move, where, and to which device, then does other work; the DMA controller raises an interrupt when done.
Process 1 wants to write to disk. With PIO the CPU would copy every word by hand โ instead it programs the DMA engine and moves on.
36.6 Methods Of Device Interaction
How does the OS actually reach a deviceโs registers? Two methods evolved โ and the second one, memory-mapped I/O memory-mapped i/o A device-communication method in which device registers appear as ordinary memory addresses; the OS reads and writes them with normal load/store instructions, and the hardware routes those accesses to the device. The alternative is explicit (port-mapped) I/O instructions such as x86's privileged in/out. Both approaches are still used. defined in ch. 36 โ open in glossary , needs no special instructions at all:
| how it works | example | |
|---|---|---|
| Explicit I/O instructions | special privileged instructions name a device port and move data to/from its registers | x86 in / out (register โ port) |
| Memory-mapped I/O | device registers appear as memory addresses; the OS reads/writes them with ordinary load/store, and the hardware routes the access to the device | load / store to a mapped address |
36.7 Fitting Into The OS: The Device Driver
Devices have wildly different interfaces; the OS wants to stay device-neutral. The fix is abstraction: the low-level device specifics live in a device driver device driver Code in the OS that knows how to deal with a specific hardware device. defined in ch. 2 โ open in glossary , and higher layers issue generic block reads/writes that get routed to the right driver:
Figure 36.4: the Linux file-system stack. A file system (and the app above) is oblivious to which disk itโs using โ it just issues block reads/writes.
The stack also exposes a raw interface, letting tools like a
disk defragmenter or file-system checker read/write blocks directly.
Abstraction has costs, though: special device features get flattened
away (SCSIโs rich errors collapse to a generic EIO). And drivers are
huge โ over 70% of Linux kernel code is device drivers, and, often
written by non-experts, theyโre a top source of kernel crashes.
Check yourself: interrupts, DMA, and drivers
1.How do interrupts improve on polling for a slow device?
2.When is polling actually BETTER than interrupts?
3.What does DMA (Direct Memory Access) offload, and how?
4.What's the difference between explicit I/O instructions and memory-mapped I/O?
5.What role does a device driver play in the OS?
6.What is interrupt coalescing, and its trade-off?