Skip to content
Interrupts & I/O
step 1/5

Reading — step 1 of 5

Read

~3 min readSystem Calls & I/O

Interrupts & I/O

A disk read takes milliseconds. A CPU executes billions of instructions per second. If the kernel waited — spun in a loop asking "done yet?" — it would burn millions of instructions per I/O doing nothing. Interrupts invert the relationship: the CPU goes off and does real work, and the hardware taps it on the shoulder when there's news. Everything responsive about your computer is built on that tap.

The delivery path

When a device has something to say (key pressed, disk block ready, packet arrived, timer expired):

device → interrupt controller (PIC/APIC) → CPU pin
       → CPU finishes current instruction
       → saves where it was
       → looks up IDT[vector]           ← the table you built in chapter 1
       → runs the handler in ring 0
       → handler sends EOI ("end of interrupt")
       → iret: back to whatever was running

The interrupt controller is the traffic cop: devices raise numbered IRQ lines, it prioritizes and translates them into vectors — indexes into your IDT. The interrupted program never knows. It might be your shell, another process, or the kernel itself; between two of its instructions, a keystroke was handled.

The remapping story (a rite of passage)

Every x86 kernel author hits this: the legacy PIC powers on mapping IRQ 0–15 to vectors 8–15 and 0x70+ — and vectors 0–31 are reserved for CPU exceptions. Result: the timer (IRQ 0) arrives on the same vector as the double-fault exception. Your kernel cannot tell "clock tick" from "catastrophe." So one of the first things every kernel does is remap the PIC, conventionally putting IRQ 0–15 at vectors 32–47. Timer = vector 32, keyboard = vector 33. Modern systems use the APIC (more vectors, multi-core routing), but the principle — you assign device vectors, above 31 — is unchanged.

Your exercise is the dispatch half of this machinery: install handlers into a 256-entry vector table, route incoming vectors to the right handler, and deal with the vectors nobody claimed. That last case is real: hardware glitches produce spurious interrupts, and a kernel with a null entry in the IDT triple-faults. Real kernels install a default "unhandled" handler in every slot first, then overwrite the ones they mean. Build that instinct now.

Handler discipline: get in, get out

Interrupt handlers run in a strange, privileged limbo — interrupt context: no process is "current," you may hold the CPU that a lock-holder needs, and while you run, other interrupts (same line, at least) are masked. Two iron rules follow:

  1. Never block. There's no one to yield to; sleeping in a handler deadlocks the machine.
  2. Do almost nothing. Acknowledge the device, grab the urgent bytes, schedule the real work for later, leave.

That split has a name — top half / bottom half: the top half runs at interrupt time (microseconds, minimal), the bottom half runs later in normal kernel context where blocking and long work are legal. The keyboard top half reads the scancode and queues it; the line-editing, the tty layer, the wakeup of your shell — all bottom half.

Moving the actual data

Interrupts say "ready"; they don't move bulk data. Two mechanisms do:

  • MMIO (memory-mapped I/O): device registers appear at physical addresses; the kernel reads/writes them like memory (mapped uncacheable — a device register is not a place for stale cache lines).
  • DMA: the kernel hands the device a physical address, the device writes the data there itself, and raises an interrupt when done. The CPU never touches the bytes in transit. Disk and network throughput exist because of DMA.

Put together: program the disk via MMIO, it DMAs the block into RAM, interrupt on vector N fires, your table dispatches it, the top half acks, the bottom half wakes the blocked process from the scheduling chapter. Every layer of this course, one I/O.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…