{"id":107,"date":"2025-09-23T23:56:40","date_gmt":"2025-09-23T21:56:40","guid":{"rendered":"https:\/\/microsproject.dev\/?p=107"},"modified":"2025-09-25T14:09:09","modified_gmt":"2025-09-25T12:09:09","slug":"context-switching-in-micros-from-bare-metal-to-multitasking","status":"publish","type":"post","link":"https:\/\/microsproject.dev\/index.php\/2025\/09\/23\/context-switching-in-micros-from-bare-metal-to-multitasking\/","title":{"rendered":"\ud83d\udd04 Context Switching in MicrOS \u2014 From Bare Metal to Multitasking"},"content":{"rendered":"\n<p>With the <strong>LM3S8965EVB QEMU target<\/strong>, MicrOS has taken its first big step into becoming a <em>real operating system<\/em>:<br>we now support <strong>context switching<\/strong> and a <strong>simple task API<\/strong>.<\/p>\n\n\n\n<p>In this post, I\u2019ll explain what a context switch is, why it matters, and how MicrOS makes it work on ARM Cortex-M (LM3S).<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\udde0 What is Context Switching?<\/h2>\n\n\n\n<p>At its core, a <strong>context switch<\/strong> is just:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Saving the state (registers, stack pointer, program counter) of the <strong>currently running task<\/strong>.<\/li>\n\n\n\n<li>Restoring the state of the <strong>next task to run<\/strong>.<\/li>\n<\/ul>\n\n\n\n<p>That\u2019s it. But this simple mechanism is the magic that allows an OS to create the <em>illusion of concurrency<\/em> on a single CPU.<\/p>\n\n\n\n<p>Without context switching, you just have one infinite <code>while(1)<\/code> loop. With it, you can have multiple tasks, each getting their slice of time.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udee0 Cortex-M and the LM3S Target<\/h2>\n\n\n\n<p>MicrOS runs on the ARM Cortex-M3 (LM3S6965 in QEMU). The Cortex-M core gives us two important features:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Hardware stacking<\/strong> \u2014 on an exception (like PendSV), the CPU automatically pushes registers (R0-R3, R12, LR, PC, xPSR) onto the current task\u2019s stack.<\/li>\n\n\n\n<li><strong>Exception return<\/strong> \u2014 when the handler finishes, the CPU automatically restores them, resuming execution.<\/li>\n<\/ol>\n\n\n\n<p>This means our OS doesn\u2019t need to manually save everything \u2014 we just manage the stack pointers and schedule tasks.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udd39 The MicrOS Kernel API<\/h2>\n\n\n\n<p>With the new API, tasks look like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>int k_task_create(void (*task_func)(void*),\n                  void* arg,\n                  uint32_t* stack_top,\n                  size_t stack_size);\n\nvoid k_scheduler_start(void);\nvoid k_task_yield(void);\nvoid k_delay_ms(uint32_t ms);\n<\/code><\/pre>\n\n\n\n<p>This gives us:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Task creation with its own stack.<\/li>\n\n\n\n<li>Cooperative yielding.<\/li>\n\n\n\n<li>Millisecond delays (using SysTick).<\/li>\n\n\n\n<li>Scheduler start \u2014 which triggers the first context switch.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u26a1 How Context Switching Works in MicrOS<\/h2>\n\n\n\n<p>Here\u2019s the step-by-step flow:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. <strong>Task Creation<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>When you call <code>k_task_create()<\/code>, MicrOS prepares a fresh stack for the new task.<\/li>\n\n\n\n<li>It pushes a <strong>fake stack frame<\/strong> onto it (simulating what the CPU would push on exception entry).<\/li>\n\n\n\n<li>This ensures that when the task is \u201crestored\u201d for the first time, it jumps straight into <code>task_func(arg)<\/code>.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2. <strong>Triggering a Context Switch<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Context switches happen in two ways:\n<ol class=\"wp-block-list\">\n<li>Voluntarily \u2192 <code>k_task_yield()<\/code> or <code>k_delay_ms()<\/code>.<\/li>\n\n\n\n<li>Involuntarily \u2192 via SysTick interrupt (preemptive scheduling planned).<\/li>\n<\/ol>\n<\/li>\n\n\n\n<li>In both cases, we trigger the <strong>PendSV exception<\/strong>, which is the standard ARM way to do context switching.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">3. <strong>PendSV Handler<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The PendSV ISR saves the current task\u2019s stack pointer into its Task Control Block (TCB).<\/li>\n\n\n\n<li>Then it picks the next task from the ready queue.<\/li>\n\n\n\n<li>It loads that task\u2019s saved stack pointer into PSP (Process Stack Pointer).<\/li>\n\n\n\n<li>On return from exception, the CPU automatically restores registers and resumes that task.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">4. <strong>Back to User Code<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>From the task\u2019s perspective, it just called <code>k_delay_ms()<\/code> and suddenly resumed after some time.<\/li>\n\n\n\n<li>Meanwhile, other tasks had their chance to run.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udcca Cortex-M Context Switch Stack Diagram<\/h2>\n\n\n\n<p>When PendSV fires, the Cortex-M automatically <strong>pushes registers<\/strong> to the current task\u2019s stack.<br>MicrOS then swaps the stack pointer to another task\u2019s stack. On exception return, the CPU <strong>pops them back<\/strong>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>            \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n            \u2502         Task Stack          \u2502\n            \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n   High \u2192   \u2502 xPSR   (Program Status Reg) \u2502   \u2190 auto-pushed by hardware\n            \u2502 PC     (Return Address)     \u2502\n            \u2502 LR     (Link Register)      \u2502\n            \u2502 R12                         \u2502\n            \u2502 R3                          \u2502\n            \u2502 R2                          \u2502\n            \u2502 R1                          \u2502\n            \u2502 R0   (Function arg pointer) \u2502\n            \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n            \u2502 ... (saved by OS, R4\u2013R11)   \u2502   \u2190 saved\/restored in PendSV      \n            \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n   Low  \u2192   \u2502       Task Stack Bottom     \u2502\n            \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Key:<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Hardware-stacked (R0\u2013R3, R12, LR, PC, xPSR)<\/strong><br>\u2192 Cortex-M does this automatically on exception entry.<\/li>\n\n\n\n<li><strong>Software-stacked (R4\u2013R11)<\/strong><br>\u2192 MicrOS saves\/restores these in the PendSV handler.<\/li>\n\n\n\n<li><strong>Stack pointer (PSP)<\/strong><br>\u2192 Updated by MicrOS to point to the <em>next task<\/em>\u2019s stack.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\udde9 Example: Hello World Multitasking<\/h2>\n\n\n\n<p>Here\u2019s the demo running in QEMU:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>k_task_create(task_function, &amp;(task_info_t){\"Task 1 --&gt;\", 333}, stack1, sizeof(stack1));\nk_task_create(task_function, &amp;(task_info_t){\"Task 2 &lt;--\", 500}, stack2, sizeof(stack2));\n\nk_scheduler_start();\n<\/code><\/pre>\n\n\n\n<p>Each task is just:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>while (1) {\n    printf(\"%s cnt=%d\\n\", info-&gt;name, cnt++);\n    k_delay_ms(info-&gt;delay_ms);\n}\n<\/code><\/pre>\n\n\n\n<p>And the output looks like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Hello, World!\nThis is MicrOS running on SOME hardware platform.\nTask 1 --&gt; cnt=0\nTask 2 &lt;-- cnt=0\nTask 1 --&gt; cnt=1\nTask 1 --&gt; cnt=2\nTask 2 &lt;-- cnt=1\n...\n<\/code><\/pre>\n\n\n\n<p>Notice how the two tasks alternate, even though there\u2019s only one CPU. That\u2019s the context switch in action.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udd0d Why This Matters<\/h2>\n\n\n\n<p>Adding context switching transforms MicrOS from a \u201cbare-metal hello world\u201d into a <strong>real RTOS kernel<\/strong>.<br>It enables:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Cooperative multitasking.<\/li>\n\n\n\n<li>Independent task stacks.<\/li>\n\n\n\n<li>The foundation for drivers and system services.<\/li>\n<\/ul>\n\n\n\n<p>Without context switching, an OS can\u2019t multitask \u2014 with it, the possibilities expand dramatically.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83c\udfaf Next Steps<\/h2>\n\n\n\n<p>Now that MicrOS can switch between tasks, the roadmap looks like this:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Implement a <strong>ready queue<\/strong> and priorities.<\/li>\n\n\n\n<li>Add more drivers (UART, GPIO, timers).<\/li>\n\n\n\n<li>Build a multitasking demo (e.g. LED blink + UART echo).<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udca1 Takeaway<\/h2>\n\n\n\n<p>Context switching might sound abstract, but on Cortex-M it\u2019s beautifully simple: save the stack pointer, switch it, and let hardware do the rest.<\/p>\n\n\n\n<p>This simplicity is exactly why MicrOS can stay tiny yet educational \u2014 you can <strong>read the whole context switch code in one sitting<\/strong> and understand how an RTOS works.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udcda References<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><a href=\"https:\/\/www.ti.com\/lit\/ds\/symlink\/lm3s6965.pdf\" target=\"_blank\" rel=\"noreferrer noopener\">LM36965 Datasheet<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/documentation-service.arm.com\/static\/5e8e107f88295d1e18d34714?token=\" target=\"_blank\" rel=\"noreferrer noopener\">ARM Cortex-M3 Programming Manual<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.qemu.org\/docs\/master\/system\/arm\/stellaris.html\" target=\"_blank\" rel=\"noreferrer noopener\">QEMU documentation for LM3S8965EVB<\/a><\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><strong>MicrOS \u2014 simple, open, embedded.<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>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\u2019ll explain what a context switch is, why it matters, and how MicrOS makes it work on ARM Cortex-M (LM3S). \ud83e\udde0 What is Context Switching? [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8,6],"tags":[],"class_list":["post-107","post","type-post","status-publish","format-standard","hentry","category-context-switch","category-development"],"blocksy_meta":[],"_links":{"self":[{"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/posts\/107","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/comments?post=107"}],"version-history":[{"count":4,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/posts\/107\/revisions"}],"predecessor-version":[{"id":127,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/posts\/107\/revisions\/127"}],"wp:attachment":[{"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/media?parent=107"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/categories?post=107"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/tags?post=107"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}