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:
- Hardware stacking β on an exception (like PendSV), the CPU automatically pushes registers (R0-R3, R12, LR, PC, xPSR) onto the current taskβs stack.
- 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:
- Voluntarily β
k_task_yield()ork_delay_ms(). - Involuntarily β via SysTick interrupt (preemptive scheduling planned).
- Voluntarily β
- 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
MicrOS β simple, open, embedded.