πŸ”„ Context Switching in MicrOS β€” From Bare Metal to Multitasking

With the LM3S8965EVB QEMU target, MicrOS has taken its first big step into becoming a real operating system:
we now support context switching and a simple task API.

In this post, I’ll explain what a context switch is, why it matters, and how MicrOS makes it work on ARM Cortex-M (LM3S).


🧠 What is Context Switching?

At its core, a context switch is just:

  • Saving the state (registers, stack pointer, program counter) of the currently running task.
  • Restoring the state of the next task to run.

That’s it. But this simple mechanism is the magic that allows an OS to create the illusion of concurrency on a single CPU.

Without context switching, you just have one infinite while(1) loop. With it, you can have multiple tasks, each getting their slice of time.


πŸ›  Cortex-M and the LM3S Target

MicrOS runs on the ARM Cortex-M3 (LM3S6965 in QEMU). The Cortex-M core gives us two important features:

  1. Hardware stacking β€” on an exception (like PendSV), the CPU automatically pushes registers (R0-R3, R12, LR, PC, xPSR) onto the current task’s stack.
  2. Exception return β€” when the handler finishes, the CPU automatically restores them, resuming execution.

This means our OS doesn’t need to manually save everything β€” we just manage the stack pointers and schedule tasks.


πŸ”Ή The MicrOS Kernel API

With the new API, tasks look like this:

int k_task_create(void (*task_func)(void*),
                  void* arg,
                  uint32_t* stack_top,
                  size_t stack_size);

void k_scheduler_start(void);
void k_task_yield(void);
void k_delay_ms(uint32_t ms);

This gives us:

  • Task creation with its own stack.
  • Cooperative yielding.
  • Millisecond delays (using SysTick).
  • Scheduler start β€” which triggers the first context switch.

⚑ How Context Switching Works in MicrOS

Here’s the step-by-step flow:

1. Task Creation

  • When you call k_task_create(), MicrOS prepares a fresh stack for the new task.
  • It pushes a fake stack frame onto it (simulating what the CPU would push on exception entry).
  • This ensures that when the task is β€œrestored” for the first time, it jumps straight into task_func(arg).

2. Triggering a Context Switch

  • Context switches happen in two ways:
    1. Voluntarily β†’ k_task_yield() or k_delay_ms().
    2. Involuntarily β†’ via SysTick interrupt (preemptive scheduling planned).
  • In both cases, we trigger the PendSV exception, which is the standard ARM way to do context switching.

3. PendSV Handler

  • The PendSV ISR saves the current task’s stack pointer into its Task Control Block (TCB).
  • Then it picks the next task from the ready queue.
  • It loads that task’s saved stack pointer into PSP (Process Stack Pointer).
  • On return from exception, the CPU automatically restores registers and resumes that task.

4. Back to User Code

  • From the task’s perspective, it just called k_delay_ms() and suddenly resumed after some time.
  • Meanwhile, other tasks had their chance to run.

πŸ“Š Cortex-M Context Switch Stack Diagram

When PendSV fires, the Cortex-M automatically pushes registers to the current task’s stack.
MicrOS then swaps the stack pointer to another task’s stack. On exception return, the CPU pops them back.

            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
            β”‚         Task Stack          β”‚
            β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
   High β†’   β”‚ xPSR   (Program Status Reg) β”‚   ← auto-pushed by hardware
            β”‚ PC     (Return Address)     β”‚
            β”‚ LR     (Link Register)      β”‚
            β”‚ R12                         β”‚
            β”‚ R3                          β”‚
            β”‚ R2                          β”‚
            β”‚ R1                          β”‚
            β”‚ R0   (Function arg pointer) β”‚
            β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
            β”‚ ... (saved by OS, R4–R11)   β”‚   ← saved/restored in PendSV      
            β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
   Low  β†’   β”‚       Task Stack Bottom     β”‚
            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key:

  • Hardware-stacked (R0–R3, R12, LR, PC, xPSR)
    β†’ Cortex-M does this automatically on exception entry.
  • Software-stacked (R4–R11)
    β†’ MicrOS saves/restores these in the PendSV handler.
  • Stack pointer (PSP)
    β†’ Updated by MicrOS to point to the next task’s stack.

🧩 Example: Hello World Multitasking

Here’s the demo running in QEMU:

k_task_create(task_function, &(task_info_t){"Task 1 -->", 333}, stack1, sizeof(stack1));
k_task_create(task_function, &(task_info_t){"Task 2 <--", 500}, stack2, sizeof(stack2));

k_scheduler_start();

Each task is just:

while (1) {
    printf("%s cnt=%d\n", info->name, cnt++);
    k_delay_ms(info->delay_ms);
}

And the output looks like:

Hello, World!
This is MicrOS running on SOME hardware platform.
Task 1 --> cnt=0
Task 2 <-- cnt=0
Task 1 --> cnt=1
Task 1 --> cnt=2
Task 2 <-- cnt=1
...

Notice how the two tasks alternate, even though there’s only one CPU. That’s the context switch in action.


πŸ” Why This Matters

Adding context switching transforms MicrOS from a β€œbare-metal hello world” into a real RTOS kernel.
It enables:

  • Cooperative multitasking.
  • Independent task stacks.
  • The foundation for drivers and system services.

Without context switching, an OS can’t multitask β€” with it, the possibilities expand dramatically.


🎯 Next Steps

Now that MicrOS can switch between tasks, the roadmap looks like this:

  • Implement a ready queue and priorities.
  • Add more drivers (UART, GPIO, timers).
  • Build a multitasking demo (e.g. LED blink + UART echo).

πŸ’‘ Takeaway

Context switching might sound abstract, but on Cortex-M it’s beautifully simple: save the stack pointer, switch it, and let hardware do the rest.

This simplicity is exactly why MicrOS can stay tiny yet educational β€” you can read the whole context switch code in one sitting and understand how an RTOS works.

πŸ“š References

  1. LM36965 Datasheet
  2. ARM Cortex-M3 Programming Manual
  3. QEMU documentation for LM3S8965EVB

MicrOS β€” simple, open, embedded.

Grzegorz GrzΔ™da
Grzegorz GrzΔ™da

At G2Labs, Grzegorz GrzΔ™da develops IoT and embedded platforms while building MicrOS in public and sharing hands-on tutorials.

Articles: 10

Newsletter Updates

Enter your email address below and subscribe to our newsletter