bigonotes
CS ConceptsOS Fundamentals

Context Switching

What the OS saves and restores when it swaps tasks, and why a process switch costs far more than a thread switch.

5 min readLast updated OS FundamentalsListen · Watch on NotebookLM

A context switch is the OS pausing one running task and resuming another on the same core. It is what lets a few cores juggle hundreds of threads. Switching between threads of one process is cheap; switching between processes is much more expensive, and most of that cost is hidden in cold caches afterward. This explains why thread pools and async I/O are preferred over spawning lots of threads or processes.

What triggers a switch#

A switch happens either because the running task gives up the CPU or because the OS takes it away.

Voluntary (task gives up the CPU) Involuntary (OS takes it)
sleep() Timer interrupt: time slice expired (often ~5–10 ms)
Blocking I/O: read, recv, a DB call Higher-priority task becomes ready (preemption)
wait() or yield() Hardware interrupt: disk done, packet arrived
Failing to acquire a mutex

Every trigger follows the same flow:

  1. An interrupt or system call puts the CPU in kernel mode.
  2. The OS saves the current task's state to its TCB/PCB.
  3. The scheduler picks the next task.
  4. The OS loads the next task's state.
  5. The CPU returns to user mode.
  6. The next task resumes.
Timeline of a context switch: task A runs, the kernel saves A, picks the next task and loads B, then B runs slowly at first while caches warm up

The user → kernel → user mode switch itself adds overhead: the CPU changes privilege level, saves extra state and checks permissions. Even a thread switch pays this.

Thread switch (same process)#

A thread switch only swaps CPU registers. These are saved to Thread 1's TCB, then loaded from Thread 2's:

Register state What it tracks
Program counter (PC) Where in the code
Stack pointer (SP) Where the stack is
General-purpose registers (RAX, RBX, RCX, RDX, ...) Intermediate values
Floating-point / vector registers Math in progress
Status/flags register Comparison results, carry
Thread state Running → Ready

Unchanged, because both threads live in the same process:

Stays the same Why
Page table Same memory mapping
TLB Stays warm, no flush
Heap, code, files Shared
Process PCB Same

This is a few hundred bytes of register state, roughly 1–10 µs including kernel entry and scheduling.

Process switch (different processes)#

A process switch does everything above, plus a change of address space:

  1. Save Process A's registers.
  2. Save A's memory context (the page table base register, CR3 on x86).
  3. Load Process B's page table base into CR3. Every virtual-to-physical translation changes.
  4. Invalidate the TLB entries that belonged to A. This is the expensive part.
  5. Load B's registers.
  6. Resume B.

Where the cost comes from#

Most of the cost is not the switch itself but what happens right after it.

  • Registers (~1 µs): fast; CPUs dump and reload register files quickly.

  • Page table switch (trivial): a single register write.

  • TLB misses (the real cost): the TLB caches recent address translations. After a switch it holds nothing useful for B, so B's first accesses must walk the page table in RAM.

    A TLB hit costs ~1 ns; a miss costs ~100 ns (walk the page table, then cache the result). If B touches 200 distinct pages before the TLB is warm, that is 200 × 100 ns = 20 µs just on TLB misses.

  • Cache pollution: L1/L2/L3 caches are full of A's data. B misses until the caches warm up.

    L1 hit L2 hit L3 hit RAM
    ~1 ns ~5 ns ~20 ns ~100 ns

Many modern CPUs tag TLB entries with an address-space ID (PCID on x86, ASID on ARM), so the OS doesn't always have to flush the whole TLB on a process switch. Entries for B still start cold, though, so the warm-up cost remains.

Thread vs process switch at a glance#

The numbers below are ballpark figures; they vary a lot by hardware and workload.

Thread switch Process switch
Registers, SP, PC Save and load Save and load
Page table No change Swap (CR3)
TLB Stays warm Flushed or cold for the new process
CPU caches Little impact (shared data) Cold-start penalty
Typical cost ~1–10 µs ~50–100 µs including warm-up
Bottleneck Register swap + scheduler TLB and cache warming

A process switch can be roughly 5–50× more expensive than a thread switch, and most of that is not the switch itself but the slow period afterward.

Why this matters when you design a server#

Compare two ways to serve many connections on a 4-core machine:

1,000 threads on 4 cores Async I/O with 4 threads
Threads waiting ~996 at any moment Each thread multiplexes many connections
Switching Constant Few switches between requests
Effect A real share of CPU time goes to switching instead of working A thread only yields when it is really waiting on I/O

This is the reasoning behind thread pools (bound the number of threads) and async I/O (avoid one thread per connection). It also shapes CPU scheduling: a time slice that is too short spends most of its time switching.

Key takeaways#

  • A context switch saves the current task's PC, SP and registers, then loads the next task's saved state.
  • Thread switches inside one process keep the page table and TLB, so they are cheap (~1–10 µs).
  • Process switches also change the page table, which leaves the TLB and caches cold; that warm-up dominates the cost.
  • Every switch also pays a user → kernel → user mode transition.
  • Fewer threads (pools) and non-blocking I/O (async) are the standard ways to cut switching overhead.

Found a mistake? Tell me on LinkedIn ↗

Further reading#