Step 1 of 5 · Reading · ~3 min
Read
Processes & Scheduling
Context Switch
Switching from one process to another looks like magic: one function call returns into a different program. It is actually a few stores and loads on a stack. In this lesson your program is the CPU executing xv6's swtch routine on simulated kernel stacks. You push and pop real 32-bit words, and at the end eip and esp belong to a different process than when you started.
What a "context" is
The state that makes a thread of execution resumable is small: its registers and its stack. If the stack holds everything else, the saved context only has to hold the registers that the calling convention promises survive a function call. The x86 32-bit convention splits them:
| Kind | Registers | Who preserves them |
|---|---|---|
| caller-saved | eax, ecx, edx | the caller, before the call, if it needs them |
| callee-saved | ebx, esi, edi, ebp | the callee, which must restore them before returning |
swtch is called as an ordinary C function, so the compiler has already dealt with the caller-saved registers around the call. All swtch has to save is the callee-saved four plus the return address (eip). xv6's struct context is exactly those five words, lowest address first: edi, esi, ebx, ebp, eip.
The trick: change the stack, then return
The routine does four things:
- The
callinstruction has already pushed the return address. This is the savedeip. - Push
ebp,ebx,esi,edi, in that order. The stack now holds a complete five-word context, andediis at the lowest address. - Store the current
espinto the old process'scontextpointer, then loadespwith the new process's saved context pointer. From this instruction on you are running on the other process's kernel stack. - Pop
edi,esi,ebx,ebp, then executeret, which popseip. The CPU continues where the new process last calledswtch.
The two halves are mirror images, so every process that has ever been switched out has the same shape on its stack. That symmetry is what makes a brand-new process work: the kernel builds a fake context on its fresh stack, four zero words and an eip pointing at the function that should start running (xv6 points it at forkret). The first time the scheduler switches to it, ret "returns" into that function.
A worked trace
BOOT sched 8000 (the scheduler runs with esp = 0x8000), PROC a 4000 1000 (a fresh context at 0x4000 - 20 = 0x3FEC whose last word is 0x1000), then the scheduler sets ebx = 0x11 and switches to a from the return address 0x7777:
| Step | Effect |
|---|---|
| call | esp = 0x7FFC, word 0x7777 stored there |
push ebp, ebx, esi, edi | words at 0x7FF8, 0x7FF4 (holds 0x11), 0x7FF0, 0x7FEC |
| save | sched's context pointer = 0x7FEC |
| load | esp = 0x3FEC |
| pops, ret | edi, esi, ebx, ebp = 0, eip = 0x1000, esp = 0x4000 |
Later a switches back; the scheduler's ebx = 0x11 comes back out of the words at 0x7FF4, untouched by anything a did.
What this does not include
Switching to a process with a different address space also loads CR3, and the CPU flushes the TLB entries of the old space. That is invisible here but dominates real cost: the register work is tens of nanoseconds, the refill of a cold TLB and cache is microseconds. It is why switching between threads of one process is cheaper than between processes.
Common mistakes
- Saving the caller-saved registers too. The compiler already did, and the context would grow for nothing.
- Pushing in the wrong order. Pop order must mirror it exactly, or
ebxandesiswap. - Forgetting that
retpopseip, leavingespfour bytes off. - Updating the CPU registers but not recording the old context pointer, so the process can never be resumed.
Your exercise
The starter keeps the processes, memory and registers, and prints results. You write swtch, the push, save, load, pop, return sequence.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…