The Big Picture: Bookmarks

You are reading a book. A friend asks you to help with a puzzle. Before you leave, you put a bookmark in the book, and note where you were. When you come back, you open the book at the bookmark and carry on as if nothing happened.

A context switch is exactly that, done by the CPU. It stops thread A, saves where it was, loads where thread B was, and carries on with B. You met the idea in Chapter 2. Here we look at it from the thread's point of view.

 core 0 time ──►
 ┌──────────────┬──────┬──────────────┬──────┬──────────────┐
 │  Thread A    │switch│  Thread B    │switch│  Thread A    │
 │  running     │      │  running     │      │  running     │
 └──────────────┴──────┴──────────────┴──────┴──────────────┘
                   ▲                      ▲
         save A's context        save B's context
         load B's context        load A's context

The "switch" slices are pure overhead. No useful program work happens in them.

6.1 What Gets Saved and Restored

What it is: The context of a thread is everything the CPU needs to continue it exactly where it stopped. When a thread is paused, Windows saves its context into the thread's kernel data. When the thread runs again, Windows loads it back.

What is in a thread's context:

PartWhy it is saved
Instruction pointer (also called program counter)Which instruction to run next
Stack pointerWhere the thread's stack currently ends
General-purpose registers (RAX, RBX, ... on x64)The thread's working values
Flags registerResults of recent comparisons (zero, carry...)
Floating-point and vector registers (XMM, YMM and so on)For decimal maths and SIMD instructions. Saved when the thread used them, since they are large
Segment and control detailsPer-thread settings, including a pointer to the thread's own data block (the TEB)

Where it is saved: on the thread's kernel stack, plus fields in the thread's kernel object. Each thread has a small kernel stack for exactly this reason (and for running kernel code on its behalf).

What is NOT saved: the thread's memory contents. The stack and heap stay right where they are in RAM. Only the CPU's tiny working state moves.

Same process or different process? The kind of switch changes the cost:

Switch typeWhat changesCost
Thread → thread, same processRegisters and stack only. The address space stays the sameCheaper
Thread → thread, different processRegisters and stack plus the page-table pointer (CR3, which tells the CPU which process's memory map to use)More expensive (Section 6.4)

This is one real reason threads are "lighter" than processes.

6.2 Register State

What it is: Registers are the CPU's own tiny storage slots (Chapter 2). They are the fastest memory in the machine and hold what the thread is working on this instant.

The x64 register set, in plain groups:

GroupExamplesCount (approx.)Use
General-purposeRAX, RBX, RCX, RDX, RSI, RDI, R8–R1516 (counting RSP, RBP)Numbers and addresses
Instruction pointerRIP1Next instruction
FlagsRFLAGS1Status bits
Vector / floating-pointXMM0–15 (or YMM, ZMM on newer CPUs)16 or more, each 16–64 bytes wideDecimal and SIMD maths

A thread never knows it was switched out. The registers come back with the same values, so its code carries on as if nothing happened.

Quick example with an addition:

 Thread A is in the middle of: total = total + 5
   RAX = 100 (its total so far)

 ── switch to B ── (A's RAX = 100 saved in memory) ──
 B uses RAX for its own work; RAX becomes 777.
 ── switch back to A ── (RAX is set to 100 again) ──

 A continues: adds 5 → 105. Correct.

Lazy saving of heavy state. The vector and floating-point registers are big. Many threads never touch them. Modern CPUs and Windows therefore avoid saving or loading them unless needed.

6.3 Stack Pointer

What it is: The stack pointer (RSP on x64) is a register that holds the memory address of the top of the current thread's stack. A stack (Chapter 3) is the thread's notepad for function calls: each call pushes a new "frame" holding its local variables and where to return.

Each thread has its own stack, so each thread has its own call history. This is what lets thread A be deep inside ten nested function calls while thread B is just starting.

  Thread A's stack            Thread B's stack
  (in the process's memory)   (in the process's memory)
  ┌────────────────┐          ┌────────────────┐
  │ main()         │          │ WorkerMain()   │
  │ LoadFile()     │          │ Compress()     │
  │ Parse()  ◄─RSP A          │ Crunch() ◄─RSP B
  └────────────────┘          └────────────────┘

On a context switch the OS simply changes the stack pointer from A's stack to B's stack. After this one change, every "return" and every local variable the CPU touches belongs to B. That is why the stack pointer is the single most important item to swap.

Two stacks per thread. A Windows thread actually has:

StackUsed whenSize
User-mode stackThe program's code runsDefault reserve is about 1 MB per thread (set in the program)
Kernel-mode stackWindows code runs for the thread (a system call, an interrupt)Small and fixed (about 24 KB on 64-bit Windows)

The kernel stack is protected, so a program cannot use it to attack the kernel. Context switches happen in kernel mode, so the saved registers live on the kernel stack.

Stack overflow: If a thread uses more stack than reserved (for example, a function that calls itself forever), the stack runs out and Windows ends the process with a stack overflow error. Each thread's stack is separate, so deep recursion in one thread cannot corrupt another's stack, although it can crash the whole process.

6.4 Context Switch Cost and Overhead

What it is: A switch is not free. The direct cost is small. The hidden cost is larger.

Direct cost (what the kernel does):

  1. Enter the kernel (timer interrupt or a wait call).
  2. Decide the next thread (scheduler code).
  3. Save registers; load the new ones.
  4. Switch stacks.
  5. Return to user mode.

This takes roughly a few microseconds (millionths of a second), or a few thousand CPU cycles. The exact figure varies a lot with the CPU and Windows version, so treat it as "about".

Hidden costs:

CostWhat happens
Cold CPU cachesThe new thread's data is not in the L1/L2 cache (Chapter 2). The first memory reads are slow until the cache refills. The old thread's data may have been pushed out
TLB flushThe TLB is a small cache of recent address translations (Chapter 3). When switching to a thread of a different process, the old translations are invalid. Modern CPUs tag entries (PCID) to avoid a full flush, but the cost is not zero
Branch predictor disruptionThe CPU's guesses about where code will go next were learned for the old thread
Security protectionsSome defences against CPU side-channel attacks add extra work around switches
Kernel timeAll that time is "kernel time", not your program's useful work
  A useful 20 ms slice           After a switch, the next slice starts slow
  ┌───────────────────┐          ┌─────────────────────┐
  │███████████████████│          │░░░▒▒▒▓▓▓████████████│
  └───────────────────┘          └─────────────────────┘
   caches warm, full speed        caches cold ► warming up ► full speed

What this means in practice:

  • More threads than cores means more switching, and each switch wastes a little time. If you run 200 busy threads on 8 cores, a lot of time can be lost.
  • Short quantum means more switches. Long quantum means fewer switches but less fair.
  • Blocking often (many tiny waits) causes many voluntary switches.
  • Good design: use about as many busy threads as you have logical processors, and let other work wait in a queue (thread pools, Section 9).

Measuring it: Performance Monitor has counters:

CounterMeaning
\System\Context Switches/secTotal switches per second across the whole PC
\Thread(*)\Context Switches/secPer thread
\Processor(_Total)\% Privileged TimeHow much CPU time is spent in kernel mode

A calm idle desktop often shows thousands of context switches per second. A busy server can show tens of thousands or more. There is no single "bad" number: compare against your own normal and watch for sudden jumps together with high CPU in kernel time.

Voluntary vs involuntary:

TypeCause
VoluntaryThe thread chose to wait (I/O, lock, Sleep)
InvoluntaryThe OS forced it (quantum over, preempted by a higher priority)

Many involuntary switches mean the CPU is overloaded. Many voluntary ones mean the program is waiting a lot.

Summary

TermPlain meaning
ContextThe CPU state a thread needs to continue: registers, stack pointer, instruction pointer
Context switchSave one thread's context, load another's
Saved whereThe thread's kernel stack and kernel object
Not savedThe thread's stack and heap contents (they stay in RAM)
Stack pointerSwapped so the CPU uses the new thread's stack
Same-process switchCheaper; no change of address space
Cross-process switchAlso changes the page-table pointer; more cost
Direct costA few microseconds of kernel work
Hidden costCold caches, TLB and predictor effects
\System\Context Switches/secCounter to watch the rate