2026-08-16
Every x86 core has a Local APIC (Advanced Programmable Interrupt Controller) sitting between it and the outside world. When a device raises an interrupt, the APIC picks a vector, latches it, and asserts INTR to the core. The core acknowledges, reads the vector, and dispatches the handler. Clean pipeline — except when the interrupt disappears between assertion and acknowledgment.
This happens more than you'd think. A device deasserts its line before the CPU responds. A higher-priority interrupt preempts. The OS masks the interrupt via the Task Priority Register (TPR) after the APIC has already committed to delivery. The APIC is now stuck: the core is executing an interrupt acknowledge cycle, but there's no valid vector to hand it.
Rather than hang the bus or return garbage, the APIC delivers the Spurious Interrupt Vector (SIV), configured in the APIC's Spurious Interrupt Vector Register (offset 0xF0). The bottom 8 bits hold the vector number the APIC returns in this "we've got nothing" case. The 8th bit is the APIC software enable — clearing it disables the entire Local APIC.
The critical trick: a spurious interrupt does not trigger an EOI requirement. Normal interrupts require the handler to write to the EOI register so the APIC clears its In-Service Register bit and can accept lower-priority interrupts. Spurious interrupts skip this — the APIC never latched an ISR bit for them. If your spurious handler mistakenly writes EOI, you'll acknowledge a real pending interrupt that hasn't fired yet, corrupting the priority state.
Real-world example: Linux sets the spurious vector to 0xFF (see arch/x86/kernel/apic/apic.c:setup_local_APIC()). The handler spurious_interrupt() increments a per-CPU counter visible in /proc/interrupts as "ERR" and "SPU" lines. On a healthy server you'll see single-digit counts across months. Hundreds per second means you likely have a misconfigured device deasserting its IRQ line too quickly, or a driver disabling an interrupt source while it's in flight.
Rule of thumb: the SIV should always be a vector with the low 4 bits set (i.e., 0x_F). On the P6-era Pentiums, the APIC hardware ignored the low 4 bits and forced them to 1 — modern chips accept any value, but the convention stuck because most OSes still assume it. Vectors 0xEF and 0xFF are conventional; 0xFF is the universal default.
The SIV is essentially the APIC's confession that interrupt delivery is not a synchronous handshake — it's a best-effort race between hardware and software, and sometimes the software wins by canceling the race after the starting gun.
