Frontend Labs
⋯

Chapter 9 — Interrupts, context switches, and why they're not free

In one sentence

Every time the CPU stops running one thing to run another — because a hardware device needs attention, or the scheduler decided it's someone else's turn — it has to save and restore a full set of state, and that switch costs real, measurable time that adds up at scale.

Why this matters

This is the concrete reason "fewer, bigger operations beat many, small ones" shows up everywhere in performance engineering: from batching database writes to why NGINX's event loop beats thread-per-connection at high concurrency.

How it actually works

   AN INTERRUPT is a signal from HARDWARE (a network card just received
   a packet, a disk finished a read, a timer fired) telling the CPU
   "stop what you're doing and handle this NOW." The CPU pauses whatever
   it was running, jumps to a small piece of kernel code (an interrupt
   handler) to deal with it, then resumes.

   A CONTEXT SWITCH is the broader mechanism: the kernel saves the FULL
   state of whatever was running (every CPU register, the program
   counter, memory-mapping information) into that process/thread's
   saved state, and loads a DIFFERENT one's saved state instead so IT
   can run. This happens:
     - when the scheduler (Chapter 8) decides it's someone else's turn
     - when a process makes a blocking syscall (Chapter 2) and has to
       wait
     - when a hardware interrupt needs kernel attention

   WHY IT'S NOT FREE:
     - saving/restoring register state takes real CPU cycles
     - it very likely INVALIDATES THE CPU CACHE (0.4's memory ladder) --
       the new process's data isn't in the fast, nearby cache, so its
       first few memory accesses are slow, cache-miss reads from RAM
     - on modern CPUs, it can also flush speculative-execution and
       branch-prediction state, adding further real overhead

   A rough, often-cited order of magnitude: a context switch costs low
   MICROSECONDS -- a few thousand CPU cycles' worth of pure overhead,
   before the newly-scheduled task does a single second of useful work.
   That sounds tiny, until you're doing it hundreds of thousands of
   times per second under heavy load.

Real-world example

   This is precisely the problem NGINX was engineered to sidestep
   (Chapter 4's C10K story, revisited from this chapter's specific
   angle): a thread- or process-per-connection server handling 10,000
   simultaneous slow/idle connections pays a CONTEXT-SWITCH COST every
   time the scheduler cycles between them, even though almost all of
   them are just sitting there waiting for I/O with nothing to do.
   NGINX's event-driven design uses a small, fixed pool of worker
   processes, each handling MANY connections via non-blocking I/O and a
   single event loop -- collapsing what would be thousands of
   context switches per second down to a small, predictable number,
   which is a major, well-documented reason event-driven servers
   dramatically outperform thread-per-connection designs specifically
   at HIGH CONCURRENCY (not necessarily at low concurrency, where the
   simpler thread-per-connection model can be perfectly fine and easier
   to reason about).

Try it yourself

# See the total number of context switches your system has done, and
# the rate right now
vmstat 1 5     # the "cs" column = context switches per second

# Compare context-switch rate: idle system vs many competing processes
vmstat 1 3                          # baseline
for i in $(seq 1 20); do yes > /dev/null & done   # spawn 20 CPU hogs
vmstat 1 3                          # watch "cs" jump
kill $(jobs -p)                     # clean up

Common mistakes

  • Thinking interrupts and context switches are the same thing. An interrupt is one specific TRIGGER for kernel attention; a context switch is the broader mechanism of swapping what's running, which can happen for several reasons.
  • Assuming "more threads/processes" scales linearly. Beyond some point, the context-switch overhead of managing MORE runnable things starts eating into the actual work getting done — this is a real, measurable ceiling, not a theoretical one.
  • Ignoring cache effects. The "hidden" cost of a context switch — the cold CPU cache afterward — is often bigger than the raw save/restore cost itself, and doesn't show up as directly in simple benchmarks.

Check yourself

  1. What is an interrupt, and what is a context switch — how do they relate?
  2. Name two reasons a context switch is more expensive than its raw save/restore time suggests.
  3. Why does an event-driven server (like NGINX) generate far fewer context switches than a thread-per-connection server at high concurrency?

Further reading

  • Book (free): OSTEP, "Limited Direct Execution" chapter — explains the trap/context-switch mechanism precisely.
  • Article: "Context Switching, or Why Servers Aren't Cars" and similar engineering-blog explanations of context-switch overhead in production systems — search for recent, concrete-numbers writeups.
  • Reference: man 8 vmstat — the tool used above, and what each column means.

Generated from real-life-os-guide.md · 8 Oct 2026 16:36