Chapter 6: Memory Protection#

What this chapter answers: why does the OS survive when an app crashes?

On a PC, when one app crashes, the OS and the other apps keep running. The OS ends only the program that misbehaved.

In this chapter the OS catches the exceptions (faults) the CPU raises on a division by zero or a write to an address that does not exist, and stops only the task that caused it. The other tasks carry on as if nothing happened.

Sample code#

  • Directory: docs/os-on-arduino/code/06_memory_protection

uv run pio run -d 06_memory_protection -t upload
uv run pio device monitor -b 115200

Source code#

The sketch for this chapter. Click a file name to expand. The code is mirrored automatically from the manuscript repository, so what you see here is always current.

src/main.cpp — 141 lines

Open on GitHub

  1/**
  2 * Chapter 6: Memory Protection Demo
  3 *
  4 * Demonstrates HardFault handling:
  5 * - Divide by zero detection
  6 * - Invalid memory access detection
  7 * - Crashed task termination (other tasks continue)
  8 */
  9
 10#include <Arduino.h>
 11#include "os_kernel.h"
 12#include "os_fault.h"
 13
 14// Arduino UNO R4 WiFi は基板上に "L" と印字された LED を持っています。
 15// 追加部品なしで動かせるよう、これを主 LED として使います。
 16#define LED_PIN LED_BUILTIN
 17
 18// Normal task: blinks LED
 19void task_led(void)
 20{
 21    pinMode(LED_PIN, OUTPUT);
 22    while (1) {
 23        digitalWrite(LED_PIN, HIGH);
 24        os_sleep(500);
 25        digitalWrite(LED_PIN, LOW);
 26        os_sleep(500);
 27    }
 28}
 29
 30// Normal task: periodic output
 31void task_heartbeat(void)
 32{
 33    int count = 0;
 34    while (1) {
 35        Serial.print("[Heartbeat] ");
 36        Serial.println(count++);
 37        os_sleep(2000);
 38    }
 39}
 40
 41// Crash task: divide by zero
 42void task_crash_divzero(void)
 43{
 44    Serial.println("[Crash] Dividing by zero in 3 seconds...");
 45    Serial.flush();
 46    os_sleep(3000);
 47
 48    // Ensure Serial is idle before crashing
 49    Serial.flush();
 50    os_sleep(100);
 51
 52    volatile int a = 100;
 53    volatile int b = 0;
 54    volatile int c = a / b;  // This will trigger HardFault!
 55    (void)c;
 56}
 57
 58// Crash task: invalid memory access
 59void task_crash_memory(void)
 60{
 61    Serial.println("[Crash] Invalid memory access in 5 seconds...");
 62    os_sleep(5000);
 63
 64    volatile uint32_t *bad_ptr = (uint32_t *)0xFFFFFFFF;
 65    *bad_ptr = 0x12345678;  // This will trigger HardFault!
 66}
 67
 68// Shell-like task for control
 69void task_shell(void)
 70{
 71    Serial.println();
 72    Serial.println("Commands: 'd' = div by zero, 'm' = bad memory, 'p' = ps");
 73
 74    while (1) {
 75        // Print any pending fault messages (deferred from ISR context)
 76        fault_print_pending();
 77
 78        if (Serial.available()) {
 79            char c = Serial.read();
 80
 81            if (c == 'd') {
 82                Serial.println("Creating div-by-zero crash task...");
 83                os_create_task(task_crash_divzero, "Crash_Div", 512);
 84            } else if (c == 'm') {
 85                Serial.println("Creating bad-memory crash task...");
 86                os_create_task(task_crash_memory, "Crash_Mem", 512);
 87            } else if (c == 'p') {
 88                Serial.println();
 89                Serial.println("ID  NAME           STATE");
 90                for (int i = 0; i < os_get_task_count(); i++) {
 91                    Serial.print(i);
 92                    Serial.print("   ");
 93                    Serial.print(os_get_task_name(i));
 94                    Serial.print("     ");
 95                    TaskState s = os_get_task_state(i);
 96                    Serial.println(s == TASK_READY ? "READY" :
 97                                   s == TASK_RUNNING ? "RUNNING" :
 98                                   s == TASK_BLOCKED ? "BLOCKED" :
 99                                   s == TASK_TERMINATED ? "TERMINATED" : "?");
100                }
101            }
102        }
103        os_sleep(100);
104    }
105}
106
107void setup()
108{
109    Serial.begin(115200);
110    while (!Serial) { }
111
112    Serial.println("=== Memory Protection Demo ===");
113    Serial.println();
114    Serial.println("This demo shows fault handling.");
115    Serial.println("When a task crashes, only that task is terminated.");
116    Serial.println("Other tasks continue running.");
117    Serial.println();
118
119    // Initialize fault handling
120    fault_init();
121
122    // Initialize OS
123    os_init();
124
125    // Create tasks
126    os_create_task(task_led, "LED", 512);
127    os_create_task(task_heartbeat, "Heartbeat", 512);
128    os_create_task(task_shell, "Shell", 1024);
129
130    Serial.println("Press 'd' for divide-by-zero crash");
131    Serial.println("Press 'm' for bad memory access crash");
132    Serial.println("Press 'p' to show task list");
133    Serial.println();
134
135    os_start();
136}
137
138void loop()
139{
140    // Never reached
141}
include/os_fault.h — 46 lines

Open on GitHub

 1/**
 2 * os_fault.h - Fault Handler Header
 3 * Chapter 6: Memory Protection
 4 */
 5
 6#ifndef OS_FAULT_H
 7#define OS_FAULT_H
 8
 9#include <stdint.h>
10
11#ifdef __cplusplus
12extern "C" {
13#endif
14
15// Fault information structure
16typedef struct {
17    uint32_t r0;
18    uint32_t r1;
19    uint32_t r2;
20    uint32_t r3;
21    uint32_t r12;
22    uint32_t lr;
23    uint32_t pc;
24    uint32_t xpsr;
25} FaultFrame;
26
27// Initialize fault handling
28void fault_init(void);
29
30// Get fault reason string
31const char* fault_get_reason(void);
32
33// Check if a fault occurred
34int fault_occurred(void);
35
36// Clear fault flag
37void fault_clear(void);
38
39// Print pending fault info (call from task context, not from ISR)
40void fault_print_pending(void);
41
42#ifdef __cplusplus
43}
44#endif
45
46#endif
include/os_kernel.h — 74 lines

Open on GitHub

 1/**
 2 * os_kernel.h - Mini OS Kernel Header
 3 * Chapter 6: Memory Protection with HardFault Handler
 4 */
 5
 6#ifndef OS_KERNEL_H
 7#define OS_KERNEL_H
 8
 9#include <stdint.h>
10
11#ifdef __cplusplus
12extern "C" {
13#endif
14
15typedef enum {
16    TASK_READY,
17    TASK_RUNNING,
18    TASK_BLOCKED,
19    TASK_SUSPENDED,
20    TASK_TERMINATED
21} TaskState;
22
23typedef struct {
24    uint32_t *sp;
25    uint32_t *stack_base;
26    uint32_t stack_size;
27    TaskState state;
28    uint8_t id;
29    uint8_t priority;
30    char name[16];
31    void (*entry)(void);
32    uint32_t wake_tick;
33    uint32_t cpu_ticks;
34} TCB;
35
36#define MAX_TASKS 8
37#define DEFAULT_STACK_SIZE 512
38// タスク用スタックをまとめて置く領域。os_create_task() は
39// ここから要求されたぶんだけ切り出す。
40// 既定より大きなスタックを要求するタスク(シェルなど)があるため、
41// MAX_TASKS * DEFAULT_STACK_SIZE では足りない。余裕を持たせる。
42#define STACK_POOL_SIZE (MAX_TASKS * DEFAULT_STACK_SIZE + 2048)
43#define IDLE_STACK_SIZE 256
44#define TIME_SLICE_MS 10
45
46void os_init(void);
47int os_create_task(void (*entry)(void), const char *name, uint32_t stack_size);
48void os_start(void);
49void os_yield(void);
50void os_sleep(uint32_t ms);
51
52void os_suspend_task(int id);
53void os_resume_task(int id);
54void os_terminate_task(int id);
55
56int os_get_task_count(void);
57const char* os_get_task_name(int id);
58TaskState os_get_task_state(int id);
59int os_get_current_task(void);
60int os_get_cpu_usage(int task_id);
61uint32_t os_get_tick(void);
62
63extern TCB g_tasks[MAX_TASKS];
64extern volatile int g_task_count;
65extern volatile int g_current_task;
66extern volatile int g_next_task;
67extern volatile uint32_t g_system_ticks;
68extern const uint32_t g_tcb_size;
69
70#ifdef __cplusplus
71}
72#endif
73
74#endif
src/os_fault.cpp — 170 lines

Open on GitHub

  1/**
  2 * os_fault.cpp - Fault Handler Implementation
  3 * Chapter 6: Memory Protection
  4 */
  5
  6#include "os_fault.h"
  7#include "os_kernel.h"
  8#include <Arduino.h>
  9
 10static volatile int g_fault_occurred = 0;
 11static volatile const char* g_fault_reason = "";
 12static volatile int g_faulted_task = -1;
 13
 14// Forward declarations for vector table override
 15extern "C" __attribute__((naked)) void HardFault_Handler(void);
 16extern "C" void PendSV_Handler(void);
 17extern "C" void SysTick_Handler(void);
 18
 19void fault_init(void)
 20{
 21    // Enable divide by zero trap
 22    SCB->CCR |= SCB_CCR_DIV_0_TRP_Msk;
 23
 24    // Do NOT enable separate UsageFault/BusFault/MemFault handlers.
 25    // By leaving them disabled, all faults escalate to HardFault,
 26    // which we handle via the VTOR override below.
 27
 28    // The Arduino framework copies the vector table to RAM (VTOR points to RAM).
 29    // We must overwrite the HardFault entry in the RAM vector table directly.
 30    // Also override PendSV and SysTick entries for the OS scheduler.
 31    uint32_t *vtor = (uint32_t *)SCB->VTOR;
 32    vtor[3] = (uint32_t)HardFault_Handler;   // Vector #3 = HardFault
 33    vtor[14] = (uint32_t)PendSV_Handler;     // Vector #14 = PendSV
 34    vtor[15] = (uint32_t)SysTick_Handler;    // Vector #15 = SysTick
 35
 36    g_fault_occurred = 0;
 37    g_fault_reason = "";
 38}
 39
 40const char* fault_get_reason(void)
 41{
 42    return (const char*)g_fault_reason;
 43}
 44
 45int fault_occurred(void)
 46{
 47    return g_fault_occurred;
 48}
 49
 50void fault_clear(void)
 51{
 52    g_fault_occurred = 0;
 53    g_fault_reason = "";
 54}
 55
 56static void analyze_fault(void)
 57{
 58    uint32_t cfsr = SCB->CFSR;
 59
 60    // Check Usage Fault Status Register (UFSR)
 61    if (cfsr & SCB_CFSR_DIVBYZERO_Msk) {
 62        g_fault_reason = "Divide by zero";
 63    } else if (cfsr & SCB_CFSR_UNALIGNED_Msk) {
 64        g_fault_reason = "Unaligned access";
 65    } else if (cfsr & SCB_CFSR_UNDEFINSTR_Msk) {
 66        g_fault_reason = "Undefined instruction";
 67    } else if (cfsr & SCB_CFSR_INVSTATE_Msk) {
 68        g_fault_reason = "Invalid state (Thumb bit)";
 69    } else if (cfsr & SCB_CFSR_INVPC_Msk) {
 70        g_fault_reason = "Invalid PC";
 71    }
 72    // Check Bus Fault Status Register (BFSR)
 73    else if (cfsr & SCB_CFSR_PRECISERR_Msk) {
 74        g_fault_reason = "Precise data bus error";
 75    } else if (cfsr & SCB_CFSR_IMPRECISERR_Msk) {
 76        g_fault_reason = "Imprecise data bus error";
 77    } else if (cfsr & SCB_CFSR_IBUSERR_Msk) {
 78        g_fault_reason = "Instruction bus error";
 79    }
 80    // Check Memory Management Fault Status Register (MMFSR)
 81    else if (cfsr & SCB_CFSR_DACCVIOL_Msk) {
 82        g_fault_reason = "Data access violation";
 83    } else if (cfsr & SCB_CFSR_IACCVIOL_Msk) {
 84        g_fault_reason = "Instruction access violation";
 85    }
 86    else {
 87        g_fault_reason = "Unknown fault";
 88    }
 89
 90    // Clear fault flags
 91    SCB->CFSR = cfsr;
 92}
 93
 94// Safe landing pad for terminated tasks after fault recovery
 95extern "C" void _fault_task_sink(void)
 96{
 97    while (1) {
 98        __WFI();
 99    }
100}
101
102// Flag to defer fault message printing to a safe context
103static volatile int g_fault_pending_print = 0;
104static volatile uint32_t g_fault_pc = 0;
105
106extern "C" void HardFault_Handler_C(FaultFrame *frame)
107{
108    g_fault_occurred = 1;
109    g_faulted_task = g_current_task;
110    g_fault_pc = frame->pc;
111
112    analyze_fault();
113
114    // Terminate the faulted task and redirect PC to safe sink
115    if (g_faulted_task > 0) {
116        os_terminate_task(g_faulted_task);
117
118        // Rewrite the stacked PC to a safe infinite loop so that
119        // returning from the fault doesn't re-execute the faulting
120        // instruction. The SysTick scheduler will switch away from
121        // this terminated task on the next tick.
122        frame->pc = (uint32_t)_fault_task_sink;
123        frame->xpsr = 0x01000000;  // Thumb bit set
124
125        // Defer printing to next safe context
126        g_fault_pending_print = 1;
127        return;
128    }
129
130    // If idle task or no task crashed, halt
131    while (1) { }
132}
133
134// Call this from a task (e.g., shell) to print deferred fault info
135void fault_print_pending(void)
136{
137    if (!g_fault_pending_print) return;
138    g_fault_pending_print = 0;
139
140    Serial.println();
141    Serial.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
142    Serial.println("!!!       FAULT DETECTED      !!!");
143    Serial.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
144    Serial.println();
145    Serial.print("Task ");
146    Serial.print(g_faulted_task);
147    Serial.print(" (");
148    Serial.print(os_get_task_name(g_faulted_task));
149    Serial.println(") crashed!");
150    Serial.print("Reason: ");
151    Serial.println((const char*)g_fault_reason);
152    Serial.print("PC at fault: 0x");
153    Serial.println(g_fault_pc, HEX);
154    Serial.println("Task terminated. Other tasks continue.");
155    Serial.println();
156}
157
158extern "C" __attribute__((naked)) void HardFault_Handler(void)
159{
160    __asm volatile (
161        "   TST     LR, #4              \n"  // Check which stack was used
162        "   ITE     EQ                  \n"
163        "   MRSEQ   R0, MSP             \n"  // MSP if LR bit 2 is 0
164        "   MRSNE   R0, PSP             \n"  // PSP if LR bit 2 is 1
165        "   PUSH    {LR}                \n"  // Save EXC_RETURN
166        "   BL      HardFault_Handler_C \n"  // Call C handler (preserves LR on stack)
167        "   POP     {LR}                \n"  // Restore EXC_RETURN
168        "   BX      LR                  \n"  // Return from exception properly
169    );
170}
src/os_kernel.cpp — 219 lines

Open on GitHub

  1/**
  2 * os_kernel.cpp - Mini OS Kernel Implementation
  3 * Chapter 6: Memory Protection
  4 */
  5
  6#include "os_kernel.h"
  7#include <Arduino.h>
  8#include <string.h>
  9
 10TCB g_tasks[MAX_TASKS];
 11volatile int g_task_count = 0;
 12volatile int g_current_task = -1;
 13volatile int g_next_task = -1;
 14volatile uint32_t g_system_ticks = 0;
 15
 16const uint32_t g_tcb_size = sizeof(TCB);
 17
 18static uint32_t g_stack_pool[STACK_POOL_SIZE / 4];
 19static uint32_t g_stack_used;   // 切り出し済みのワード数
 20
 21static void idle_task(void) { while (1) __WFI(); }
 22
 23// タスク関数が return したときの受け皿。例外フレームの LR スロットは
 24// ここを指す (第3章 3.4)。EXC_RETURN を置く場所ではない。
 25static void task_exit(void)
 26{
 27    g_tasks[g_current_task].state = TASK_TERMINATED;
 28    os_yield();
 29    while (1) { }  // ここには到達しない
 30}
 31
 32static uint32_t* init_task_stack(uint32_t *stack_top, void (*entry)(void))
 33{
 34    uint32_t *sp = stack_top;
 35    *(--sp) = 0x01000000;
 36    *(--sp) = (uint32_t)entry;
 37    *(--sp) = (uint32_t)task_exit;  // LR: タスクが return したときの戻り先
 38    *(--sp) = 0; *(--sp) = 0; *(--sp) = 0; *(--sp) = 0; *(--sp) = 0;
 39    *(--sp) = 0; *(--sp) = 0; *(--sp) = 0; *(--sp) = 0;
 40    *(--sp) = 0; *(--sp) = 0; *(--sp) = 0; *(--sp) = 0;
 41    return sp;
 42}
 43
 44void os_init(void)
 45{
 46    memset(g_tasks, 0, sizeof(g_tasks));
 47    g_task_count = 0;
 48    g_current_task = -1;
 49    g_next_task = -1;
 50    g_system_ticks = 0;
 51    os_create_task(idle_task, "idle", IDLE_STACK_SIZE);
 52}
 53
 54int os_create_task(void (*entry)(void), const char *name, uint32_t stack_size)
 55{
 56    if (g_task_count >= MAX_TASKS) return -1;
 57    int id = g_task_count;
 58    TCB *task = &g_tasks[id];
 59    task->id = id;
 60    task->state = TASK_READY;
 61    task->priority = id;
 62    task->entry = entry;
 63    task->wake_tick = 0;
 64    task->cpu_ticks = 0;
 65    strncpy(task->name, name, sizeof(task->name) - 1);
 66    if (stack_size == 0) {
 67        stack_size = DEFAULT_STACK_SIZE;
 68    }
 69    uint32_t words = (stack_size + 3) / 4;
 70    if (g_stack_used + words > STACK_POOL_SIZE / 4) {
 71        return -1;          // プールが尽きた。黙って縮めず、失敗として返す
 72    }
 73    g_stack_used += words;
 74    task->stack_size = stack_size;
 75    task->stack_base = &g_stack_pool[g_stack_used];   // スタックは上端から下へ伸びる
 76    task->sp = init_task_stack(task->stack_base, entry);
 77    g_task_count++;
 78    return id;
 79}
 80
 81void os_schedule(void)
 82{
 83    for (int i = 0; i < g_task_count; i++) {
 84        if (g_tasks[i].state == TASK_BLOCKED && g_system_ticks >= g_tasks[i].wake_tick) {
 85            g_tasks[i].state = TASK_READY;
 86        }
 87    }
 88
 89    int next = g_current_task;
 90    do {
 91        next = (next + 1) % g_task_count;
 92        if (g_tasks[next].state == TASK_READY || g_tasks[next].state == TASK_RUNNING) break;
 93    } while (next != g_current_task);
 94
 95    if (next != g_current_task || g_current_task < 0) {
 96        if (g_current_task >= 0 && g_tasks[g_current_task].state == TASK_RUNNING) {
 97            g_tasks[g_current_task].state = TASK_READY;
 98        }
 99        g_tasks[next].state = TASK_RUNNING;
100        g_next_task = next;
101        SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
102    }
103}
104
105void os_yield(void) { os_schedule(); }
106void os_sleep(uint32_t ms) {
107    if (g_current_task > 0) {
108        g_tasks[g_current_task].state = TASK_BLOCKED;
109        g_tasks[g_current_task].wake_tick = g_system_ticks + ms;
110        os_schedule();
111    }
112}
113void os_suspend_task(int id) {
114    if (id > 0 && id < g_task_count) {
115        g_tasks[id].state = TASK_SUSPENDED;
116        if (id == g_current_task) os_schedule();
117    }
118}
119void os_resume_task(int id) {
120    if (id > 0 && id < g_task_count && g_tasks[id].state == TASK_SUSPENDED) {
121        g_tasks[id].state = TASK_READY;
122    }
123}
124void os_terminate_task(int id) {
125    if (id > 0 && id < g_task_count) {
126        g_tasks[id].state = TASK_TERMINATED;
127        if (id == g_current_task) os_schedule();
128    }
129}
130
131uint32_t os_get_tick(void) { return g_system_ticks; }
132int os_get_task_count(void) { return g_task_count; }
133const char* os_get_task_name(int id) { return (id >= 0 && id < g_task_count) ? g_tasks[id].name : ""; }
134TaskState os_get_task_state(int id) { return (id >= 0 && id < g_task_count) ? g_tasks[id].state : TASK_TERMINATED; }
135int os_get_current_task(void) { return g_current_task; }
136
137void os_start(void)
138{
139    NVIC_SetPriority(PendSV_IRQn, 0xFF);
140    SysTick_Config(SystemCoreClock / 1000 * TIME_SLICE_MS);
141    g_current_task = -1;
142    g_next_task = 0;
143    // Do NOT switch CONTROL to PSP here — PSP is not yet initialized.
144    // PendSV returns with EXC_RETURN=0xFFFFFFFD, which automatically
145    // switches thread mode to PSP after loading the first task's context.
146    SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
147    while (1) { }
148}
149
150extern "C" void SysTick_Handler(void) {
151    // 実際に経過した時間を、いま走っていたタスクの取り分として計上する。
152    // os_schedule() 側で数えると os_yield() の回数まで混ざってしまう。
153    if (g_current_task >= 0) {
154        g_tasks[g_current_task].cpu_ticks += TIME_SLICE_MS;
155    }
156    g_system_ticks += TIME_SLICE_MS;
157    os_schedule();
158}
159
160extern "C" __attribute__((naked)) void PendSV_Handler(void)
161{
162    __asm volatile (
163        "   MRS     R0, PSP              \n"
164        "   LDR     R1, =g_current_task  \n"
165        "   LDR     R2, [R1]             \n"
166        "   CMP     R2, #0               \n"
167        "   BLT     _skip_save           \n"
168        // Save R4-R11 only when there is a valid current task
169        "   STMDB   R0!, {R4-R11}        \n"
170        "   LDR     R3, =g_tasks         \n"
171        "   LDR     R4, =g_tcb_size      \n"
172        "   LDR     R4, [R4]             \n"
173        "   MUL     R2, R2, R4           \n"
174        "   ADD     R3, R3, R2           \n"
175        "   STR     R0, [R3]             \n"
176        "_skip_save:                     \n"
177        "   LDR     R1, =g_next_task     \n"
178        "   LDR     R2, [R1]             \n"
179        "   LDR     R1, =g_current_task  \n"
180        "   STR     R2, [R1]             \n"
181        "   LDR     R3, =g_tasks         \n"
182        "   LDR     R4, =g_tcb_size      \n"
183        "   LDR     R4, [R4]             \n"
184        "   MUL     R2, R2, R4           \n"
185        "   ADD     R3, R3, R2           \n"
186        "   LDR     R0, [R3]             \n"
187        "   LDMIA   R0!, {R4-R11}        \n"
188        "   MSR     PSP, R0              \n"
189        "   LDR     LR, =0xFFFFFFFD      \n"
190        "   BX      LR                   \n"
191    );
192}
193
194// CPU使用率を取得(パーセント)
195// 直前にこの関数を呼んだ時点からの差分で計算する。
196// ps のようにタスクごとにループで呼ぶため、基準時刻もタスクごとに持つ。
197int os_get_cpu_usage(int task_id)
198{
199    if (task_id < 0 || task_id >= g_task_count) {
200        return -1;
201    }
202
203    static uint32_t last_total[MAX_TASKS] = {0};
204    static uint32_t last_task_ticks[MAX_TASKS] = {0};
205
206    uint32_t total = g_system_ticks;
207    uint32_t delta_total = total - last_total[task_id];
208
209    if (delta_total == 0) {
210        return 0;
211    }
212
213    uint32_t delta_task = g_tasks[task_id].cpu_ticks - last_task_ticks[task_id];
214
215    last_total[task_id] = total;
216    last_task_ticks[task_id] = g_tasks[task_id].cpu_ticks;
217
218    return (delta_task * 100) / delta_total;
219}

On the board#

../_images/ch06_fault.gif

Commands are single letters: p lists the tasks, d creates Crash_Div (a division by zero), and m creates Crash_Mem, which writes to an invalid address (0xFFFFFFFF) after 5 s. Here p lists the tasks and d triggers a division by zero. After the fault Heartbeat keeps going, and only Crash_Div becomes TERMINATED.#

In the simulator#

Without the board, the same firmware running in the simulator looks like this.

../_images/sim_ch06_fault.gif

Sending d makes the Crash_Div task fault on a division by zero, and FAULT DETECTED with the cause is printed. L (Heartbeat) and the [Heartbeat] lines keep going. (Recorded in the simulator.)#

Break it#

Predict before you open each answer. The first one needs no code change: try it directly in Chapter 6 in the browser version. For the second, change the code, build it, and drop firmware.elf into the browser version.

Note

The outputs in the answers were captured by actually running the code in QEMU, the same emulator the browser version uses. They have not been checked on the real board yet.

1. Crash five times in a row#

Send d five times in a row, wait a little, then list the tasks with p.

Predict: how many times does FAULT DETECTED appear?

Answer

Only four times. The fifth d still prints Creating div-by-zero crash task..., but the [Crash] Dividing by zero in 3 seconds... line that should follow never appears.

Creating div-by-zero crash task...
[Crash] Dividing by zero in 3 seconds...
Creating div-by-zero crash task...
[Crash] Dividing by zero in 3 seconds...
[Heartbeat] 1
Creating div-by-zero crash task...
[Crash] Dividing by zero in 3 seconds...
Creating div-by-zero crash task...
[Crash] Dividing by zero in 3 seconds...
Creating div-by-zero crash task...
[Heartbeat] 2
(FAULT DETECTED four times)

ID  NAME           STATE
0   idle     READY
1   LED     BLOCKED
2   Heartbeat     BLOCKED
3   Shell     RUNNING
4   Crash_Div     TERMINATED
5   Crash_Div     TERMINATED
6   Crash_Div     TERMINATED
7   Crash_Div     TERMINATED

The task table g_tasks has room for only MAX_TASKS = 8 entries. The four created at boot (idle, LED, Heartbeat, Shell) use half, and Crash_Div fills the other four. A TERMINATED task’s slot is never reused, so the fifth os_create_task() returns -1 and does nothing. The shell does not check that return value, so it announces the new task and never notices the failure.

Each finished task permanently uses up a slot, so this OS is not suited to recreating crashed tasks over and over.

2. Turn off the divide-by-zero trap#

In fault_init() in src/os_fault.cpp, comment out the setting that makes a division by zero raise an exception.

-    SCB->CCR |= SCB_CCR_DIV_0_TRP_Msk;
+    // SCB->CCR |= SCB_CCR_DIV_0_TRP_Msk;

Predict: when you send d, what does 100 / 0 do?

Answer

FAULT DETECTED never appears. Yet p shows Crash_Div as TERMINATED.

Creating div-by-zero crash task...
[Crash] Dividing by zero in 3 seconds...
[Heartbeat] 1
[Heartbeat] 2
[Heartbeat] 3

ID  NAME           STATE
0   idle     READY
1   LED     BLOCKED
2   Heartbeat     BLOCKED
3   Shell     RUNNING
4   Crash_Div     TERMINATED

Without this setting (CCR.DIV_0_TRP), the Cortex-M divide instruction does not raise an exception on division by zero; it returns 0 and carries on (Arm’s “Divide and Conquer”). c stays 0, task_crash_divzero() runs to the end and returns, and task_exit(), which catches returning tasks, marks it TERMINATED.

So the program carries on with a wrong answer and no error. That is why fault_init() turns this setting on.

Pitfalls#

(Collected from reader questions — ask via GitHub Issues.)