EmbeddedRelated.com

Context Switch

Category: Rtos | Also known as: context switching, context switches

A context switch is the act of saving the CPU state of a currently running task (or thread) and restoring the previously saved state of another task, allowing the scheduler to interleave execution across multiple tasks on a single processor core.

In practice

In an RTOS, a context switch is triggered either preemptively (a higher-priority task becomes ready, or a periodic tick fires) or cooperatively (a task explicitly yields or blocks on a semaphore, queue, or delay call). The scheduler selects the next task to run, the port-level context-switch routine saves the current task's CPU registers to its stack, updates the current task pointer, and restores the new task's registers from its stack before returning to the new task's execution point.

The exact register set saved depends on the architecture. On ARM Cortex-M cores, the hardware automatically stacks eight registers (PC, xPSR, R0-R3, R12, LR) on exception entry; the RTOS software context-switch routine then saves and restores the remaining caller-saved and callee-saved registers (R4-R11, and S0-S31/FPSCR if the FPU is active). On architectures such as AVR or PIC, where the hardware saves little or nothing automatically, the RTOS port must push and pop in software all registers required to preserve task state, which in practice is often most or all of the general-purpose register file but may vary by port and architecture variant, making context switches comparatively more expensive in cycle count.

Context switch overhead matters in timing-sensitive applications. A switch involves at minimum a register save/restore, a stack pointer swap, and often a pipeline flush. On a Cortex-M4 running FreeRTOS at 168 MHz, a context switch typically takes on the order of a few hundred nanoseconds, though the exact figure is highly configuration-dependent and can vary significantly with FPU use, compiler settings, cache or wait-state configuration, and how the measurement is taken; on a slower 8-bit MCU at 8 MHz it can be several microseconds. If tasks switch far more frequently than their work justifies, the cumulative overhead becomes a measurable fraction of CPU time. Profiling with a logic analyzer toggling a GPIO at task entry and exit is a straightforward way to measure this in practice.

A subtle but common pitfall involves floating-point context. On Cortex-M4/M7 cores with an FPU, many RTOS ports use lazy FPU stacking by default: the FPU registers are not saved until a task actually uses the FPU. If FPU state is not saved correctly (for example, because the RTOS port's FPU support is disabled while tasks do use floating-point), data corruption can occur that is difficult to reproduce. Always verify that the RTOS port configuration matches whether your tasks use floating-point instructions.

Frequently asked

What is the difference between a preemptive and a cooperative context switch?
In a preemptive RTOS, the scheduler can interrupt a running task at any tick boundary or when a higher-priority task is unblocked, forcing a switch without the running task doing anything. In a cooperative (non-preemptive) scheme, a switch only happens when the running task explicitly yields or calls a blocking API. Cooperative switching eliminates many race conditions but means a misbehaving or busy-looping task can starve all others. Most production RTOSes (FreeRTOS, Zephyr, ThreadX) support preemptive scheduling, often with an optional cooperative mode.
How does a context switch interact with interrupt service routines?
On most RTOS implementations, a context switch does not happen inside an ISR mid-execution. Instead, if an ISR unblocks a higher-priority task (for example, by posting to a semaphore), the RTOS sets a flag and performs the switch after the ISR returns, typically using a dedicated low-priority software interrupt or the PendSV mechanism on Cortex-M. This keeps ISRs short and avoids re-entrant scheduler state.
Why does context switching require saving registers to the task's own stack rather than a central area?
Each task's context is saved onto its own stack so that all tasks can be in-flight simultaneously with fully independent state. A central save area would require either a single-task-at-a-time model or complex indexing. Using per-task stacks also means the saved context is naturally scoped to the task's lifetime and can be inspected in a debugger by examining the stack of any task.
Can context switches happen in a bare-metal system without an RTOS?
Not automatically, but the concept still applies. Cooperative patterns like coroutines or state-machine schedulers manually save and restore execution state, which is a simplified form of context switching. The 'Coroutines in one page of C' article demonstrates this: a coroutine saves its resumption point (typically via setjmp/longjmp or a saved stack pointer) and restores it on re-entry, with no RTOS involved.
What stack size should I allocate for each task to accommodate context saving?
The context save frame is relatively small (for example, 17 words on a Cortex-M4 with FPU), but the stack must also hold all nested function call frames and local variables during that task's deepest call path. The context overhead itself is rarely the binding constraint; deep call chains, large local buffers, or printf-style formatting functions are more likely culprits. Most RTOS implementations provide a high-water mark API (for example, uxTaskGetStackHighWaterMark in FreeRTOS) that reports the minimum free stack observed at runtime, which is the standard tool for right-sizing task stacks.

Differentiators vs similar concepts

Context switch is sometimes conflated with interrupt handling, but they are distinct. An interrupt suspends the current execution context to run an ISR and then resumes the same context; no scheduler is involved and no task switch necessarily occurs. A context switch is a scheduler-driven operation that changes which task owns the CPU, typically persisting until the newly running task blocks or is preempted. On Cortex-M parts, both mechanisms share some hardware machinery (for example, exception entry stacking), and many Cortex-M RTOS ports use PendSV for deferred context switching specifically because of its low priority among exceptions; however, this is a port-level design choice rather than a universal property of all Cortex-M implementations, which adds to the confusion.