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
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
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
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
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
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#
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.
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.)