Interrupt-based I/O lets I/O devices alert the processor with a hardware signal when they’re ready, instead of forcing the processor to poll in a loop. When an interrupt fires, the processor pauses what it’s doing, runs an interrupt service routine (ISR) for the requesting device, then resumes the original program.
The win: while waiting for slow I/O, the processor can do useful work — running other programs, computing other values, or even idling and saving power. Polling wastes those cycles entirely.
How interrupts work
Three components:
- The device has an interrupt request output (IRQ line) it raises when it needs attention.
- The processor has an interrupt input that, when asserted, causes the current instruction stream to pause and a special handler to run.
- A control mechanism (interrupt-enable bits at multiple levels) decides whether the interrupt is currently allowed to fire.
When an interrupt occurs:
- The processor finishes the current instruction.
- It saves enough state to resume later — at minimum, the PC and processor status register.
- It clears the global interrupt-enable bit (so the ISR isn’t immediately re-interrupted by the same source).
- It jumps to the address of the appropriate ISR.
- The ISR runs, eventually executing a “return-from-interrupt” instruction.
- Return-from-interrupt restores PC and PS, and execution resumes at the interrupted instruction.
The whole pause-and-resume is invisible to the original program — it never knows it was interrupted.
ISR vs. subroutine
An ISR looks similar to a Subroutine but differs in important ways:
- Not invoked by Call. The hardware triggers the ISR asynchronously.
- Can occur at any time. Including in the middle of unrelated code.
- Must save and restore everything it touches. The interrupted code has no idea what’s about to happen, so the ISR can’t assume any registers are free to clobber.
- Returns via “return-from-interrupt”, not regular
ret. The return-from-interrupt instruction also restores the processor status register, re-enabling interrupts.
Enable bits at three levels (Nios II model)
In the Nios II / Hamacher textbook model, interrupts are controlled by enable bits at three levels — all three must be on for an interrupt to fire:
- Processor-level: the IE bit in the Processor Status (PS) register. Master switch for all interrupts.
- Device-level (within processor): a bit per device or per interrupt source in an
IENABLEregister, letting the processor selectively block specific interrupt lines. - Device-level (within device): a bit in the device’s CONTROL register, letting each device individually choose whether to assert its IRQ line.
To enable a keyboard interrupt: set the bit in the keyboard’s KBD_CONT (device control), set the bit for keyboard IRQ in the processor’s IENABLE, set the IE bit in PS. Three writes.
The three-level structure isn’t universal. Many architectures put the per-device gating into a separate interrupt controller chip (or on-die block) rather than into the processor itself: ARM uses the GIC (Generic Interrupt Controller), x86 uses the local APIC and I/O APIC. There the per-device enable lives in the controller’s registers, not in a processor IENABLE. The principle is the same — interrupts can be selectively masked at the device, at the controller, and at the processor — but the names and locations of the enable bits depend on the architecture.
Multiple devices
Several devices share the interrupt input. When an interrupt fires, the ISR has to figure out which device caused it. Two options:
-
Polling within the ISR: the ISR reads each device’s STATUS register in turn to find which one has its IRQ bit set. Slow but simple.
-
Vectored interrupts: each device sends a code identifying itself when it asserts the interrupt. The processor uses this code to look up the address of the device’s specific ISR in an Interrupt vector table. Fast — no polling needed inside the ISR.
Most modern systems use vectored interrupts.
Setup example
To enable keyboard interrupts:
START:
Move R2, #LINE
Store R2, PNTR ; Initialize buffer pointer
Clear R2
Store R2, EOL ; Clear end-of-line indicator
# Enable Interrupts in Keyboard Interface
Move R2, #2
StoreByte R2, KBD_CONT
# Enable Keyboard Interrupts in Processor
MoveControl R2, IENABLE
Or R2, R2, #2 ; Set specific bit for KBD
MoveControl IENABLE, R2
# Globally Enable Interrupts (IE bit in PS)
MoveControl R2, PS
Or R2, R2, #1
MoveControl PS, R2
# Continue with main computation...
After this setup, the main program can do whatever it wants. When a key is pressed, the keyboard ISR fires, processes the character, and returns. The main program never has to check the keyboard.
When interrupts are right
- The device is much slower than the processor (typical for human-input devices, network, disk).
- The processor has other work to do.
- Latency requirements are loose (interrupt overhead is non-trivial — saving/restoring state takes cycles).
For very high-bandwidth, predictable transfers (e.g., a sound buffer playing continuously), polling or DMA is sometimes preferred over interrupts.