Visual walkthrough — RTOS concepts — task, scheduler, preemption, context switch
We build on the parent RTOS concepts note but assume you know nothing. Every word — register, stack, TCB — is earned before it is used.
Step 0 — What is "where the CPU is"? (the thing we must save)
WHAT. Before we can save a task's state, we must know what "state" even is. A CPU is a tiny machine that does one thing: read a number from memory, do arithmetic, write it back — over and over. To do that it keeps a handful of small scratch boxes inside itself called registers.
WHY. Everything the task is "thinking about right now" lives in those boxes. If we froze the CPU and photographed every register, that photo is the task. Nothing else about the task's current instant lives anywhere else the CPU can lose track of.
PICTURE. Three registers matter most for us:
- The program counter (PC) — a pointer that says "the next instruction I will run is here." Think of a finger on a line of a recipe.
- The stack pointer (SP) — a pointer to the top of this task's private scratch-paper pile (its stack).
- The general registers (r0–r12) and the status flags — the actual numbers being juggled.

Step 1 — Two tasks, one CPU, two private stacks
WHAT. We set the scene. Task A (low priority) and Task B (high priority) each get their own stack — a private stretch of RAM used like a stack of plates: you push a plate on top, you pop the top plate off.
WHY. Each task must have somewhere to dump its registers that no other task will touch. That somewhere is its own stack. If two tasks shared one stack, saving A's photo would scribble over B's — instant corruption.
PICTURE. Two separate paper piles. The CPU's single SP register currently points into A's pile, because A is the one Running.

- — the stack pointer, the one SP the CPU physically owns.
- "top of Task A's stack" — where A's next push would land.
- — only one task can hold the live SP at a time; that task is Running.
Step 2 — The trigger: something says "re-check who's most urgent"
WHAT. At some instant a trigger fires. It could be the periodic tick (a timer interrupt that fires, say, 1000 times a second) or an event like a button ISR that makes B Ready.
WHY. The scheduler doesn't run all the time — that would waste the CPU. It only wakes up when something might have changed the answer to "who is the highest-priority Ready task?" A tick or an unblocking event is exactly such a moment. (For how the interrupt itself arrives, see Interrupts and ISR latency.)
PICTURE. A lightning bolt hits the running CPU. B, previously Blocked, flips to Ready and — because B's priority (3) beats A's (1) — the scheduler decides a switch is due.

Step 3 — Hardware auto-saves half the photo
WHAT. On a Cortex-M CPU, the moment an exception (interrupt) begins, the hardware itself automatically pushes eight registers onto the current stack: r0, r1, r2, r3, r12, LR, PC, xPSR.
WHY. These eight are the ones an interrupt handler is allowed to clobber by the calling convention, so the CPU protects them for free, in a few nanoseconds, before any of our code runs. Half the photo is taken for us.
PICTURE. Eight plates drop onto A's stack; SP slides down to sit on top of them. This automatic block is called the exception stack frame.

- The braces list the exact eight registers the hardware saves.
- inside — this is the crucial one: it records where A was interrupted, so A can resume there later.
- — the status/flags register (was the last result zero? negative? carry?), needed so A's arithmetic keeps making sense.
Step 4 — Software saves the other half (into the PendSV handler)
WHAT. The CPU jumps into a special handler called PendSV. Our switch code there manually pushes the remaining registers r4–r11 onto A's stack.
WHY two halves? Hardware only saved the "caller-saved" eight. Registers r4–r11 are "callee-saved" — the hardware assumes someone else protects them, so we must. Push them and A's photo is now complete: every live register is on A's stack.
Why PendSV specifically? PendSV is configured to run at the lowest interrupt priority. So it only fires after every real, urgent ISR has finished. We never switch tasks in the middle of servicing hardware — that would be a tangled mess.
PICTURE. Eight more plates (r4–r11) drop onto A's pile. A's full context is now frozen on paper.

- Left set — the eight we push in code.
- Right set — the eight hardware already pushed in Step 3.
- Their union = all the volatile CPU state. Nothing about A is left unsaved.
Step 5 — Anchor the frozen photo with ONE number: the SP → TCB
WHAT. We take A's final SP (which now points at the very top of A's complete saved frame) and write it into A's Task Control Block (TCB).
WHY. A stack of 16 saved plates is useless if we forget where the pile is. The single SP value is the handle to the entire frame — one number that lets us find all 16 registers again. The TCB is the per-task filing card the RTOS keeps: priority, state, and this saved SP. (More on the Stack memory and TCB.)
PICTURE. A little index card labelled TCB_A gets one field filled in: saved_SP = 0x2000_0F40 (a pointer arrow to the top plate).

- — the slot on A's filing card.
- — the live stack pointer, now pointing at A's fully-saved frame.
- The arrow means "copy the value in": one number now unlocks A's whole context forever.
Step 6 — Scheduler picks B, loads B's SP
WHAT. The scheduler scans the TCBs, finds the highest-priority Ready task (that's B), and loads TCB_B.savedSP back into the live SP register.
WHY. Now the CPU's SP points into B's pile instead of A's. From this instant on, every push/pop touches B's memory. We have swapped which "self" the CPU is wearing. The rule the scheduler uses — always the highest-priority Ready task — underlies Rate Monotonic Scheduling.
PICTURE. The SP arrow lifts off A's pile and lands on top of B's saved pile (B was switched out earlier, so B's pile already holds B's 16 plates).

- We overwrite the live SP with the number we filed away for B last time it slept.
- One assignment flips the CPU's identity from A to B.
Step 7 — Restore B's photo and return into B
WHAT. Mirror of Steps 4 and 3, run backwards. Our PendSV code pops r4–r11 off B's stack. Then a special "exception return" makes the hardware pop the other eight — including B's PC. The CPU's finger lands exactly on the instruction B was about to run when it was last paused.
WHY. Restoring is un-saving. Because B's PC and flags are restored bit-for-bit, B carries on as if no time passed — it cannot even tell it was asleep for 2 ms. That seamlessness is the whole point.
PICTURE. Plates lift off B's pile back into the registers; the PC finger snaps onto B's next line. B is now Running.

- We reverse the union from Step 5: pop everything back into the boxes.
- — the last plate restored redirects the CPU into B's code.
Step 8 — Edge & degenerate cases (never leave the reader stranded)
WHAT. Three corners that break the naive story — each drawn.
Case (a): the trigger fires but the highest Ready task is still A. Then in Step 6 the scheduler picks A again — no switch happens, PendSV isn't even pended, and we save the whole cost. Why: switching is only worth doing if the winner actually changed.
Case (b): equal priority (A and B both priority 2). Priority alone can't break the tie, so preemption does NOT fire on the event. Instead they take turns at tick boundaries — round-robin time-slicing. Why: preemption is strictly higher-priority-driven; equals share time, they don't shove each other.
Case (c): the very first switch — B's stack is "empty." B has never run, so there's no real saved photo. At task creation the RTOS hand-fakes an exception frame on B's fresh stack: a PC pointing at B's entry function, a clean xPSR, and dummy r0–r12. Step 7 then "restores" this fake frame and B starts at its top. Why: it lets creating a task and resuming a task use the exact same restore code.

The one-picture summary

The whole dance compressed: A running → trigger → save A (hardware 8 + software 8) → file A's SP in TCB → load B's SP → restore B (software 8 + hardware 8) → B running. The two "8"s are the two halves of the photo; the TCB's single SP is the string that ties each photo to its owner.
Recall Feynman retelling — say it like a story
Imagine one artist (the CPU) who can only hold one painting at a time. Task A hands the artist a half-finished canvas. A bell rings (trigger) saying "urgent customer B is here, and B outranks A." So the artist quickly photographs A's canvas from two angles — the camera snaps eight things by itself (hardware push), the artist snaps eight more by hand (software push) — and staples the photo to A's locker with a single sticky note giving its shelf number (SP into TCB). The artist reads B's locker note, walks to B's shelf, un-staples B's old photo, and paints exactly where B left off. B never knows it waited. If instead B had the same rank as A, no shoving — they'd just swap on a kitchen timer (round-robin). And the first time B ever shows up, the artist quietly staples a blank template photo to B's locker so the "resume" ritual still works. That's a context switch — and is the tiny tax you pay each time the bell rings.
Recall Test yourself
Why does a context switch save two separate groups of registers? ::: Hardware auto-pushes r0–r3, r12, LR, PC, xPSR on exception entry; software (PendSV) must push the callee-saved r4–r11. Union = full context. What single value anchors a whole saved frame, and where is it stored? ::: The stack pointer (SP), stored in that task's TCB (savedSP field). Why is PendSV set to the lowest interrupt priority? ::: So the switch only runs after all real ISRs finish — no switching in the middle of urgent hardware servicing. Two equal-priority tasks: does an event preempt one for the other? ::: No — preemption needs strictly higher priority. Equals share via round-robin time-slicing at ticks. How does a brand-new task get switched in if it has no saved context? ::: The RTOS hand-crafts a fake exception frame (PC = entry function, clean xPSR, dummy regs) at creation, so the normal restore code works.
Related: Priority Inversion and Mutexes · Semaphores and Queues · Bare-metal super-loop vs RTOS