第6章: メモリ保護#
MPU(Memory Protection Unit:アクセスしてよいメモリの範囲を CPU が見張る仕組み)を使ってタスク同士のメモリを守り、暴走を検出します。
この章で分かること: アプリが落ちても、OS が落ちないのはなぜ?
PC では、アプリが 1 つ落ちても、OS やほかのアプリは動き続けます。 OS が、おかしなことをしたプログラムだけを終わらせているからです。
この章では、ゼロ除算や、存在しないアドレスへの書き込みで CPU が起こす例外 (フォールト)を OS が受け止めて、起こしたタスクだけを止めます。 ほかのタスクは何事もなかったように動き続けます。
サンプルコード#
ディレクトリ:
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
ソースコード#
この章のスケッチです。ファイル名をクリックすると開きます。コードは原稿リポジトリから自動で同期されているので、ここに出ているものが常に最新です。
src/main.cpp — 141 行
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 行
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 行
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 行
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 行
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}
実機での出力#
Arduino UNO R4 WiFi の実機で実際に取得した出力です(macOS / Windows の 両方で同じ結果を確認しています)。手元の出力と見比べてください。
d を送ってゼロ除算タスクを作ると、そのタスクだけが TERMINATED になり、Heartbeat は動き続けます。
コマンドは 1 文字です。d はゼロ除算を起こすタスク(Crash_Div)を、m は 5 秒後に
不正なアドレス(0xFFFFFFFF)へ書き込むタスク(Crash_Mem)を作ります。p はタスクの
一覧を表示します。
p でタスクを見て、d でゼロ除算を起こしたところ。fault のあとも
Heartbeat が途切れず、Crash_Div だけが TERMINATED になります#
ID NAME STATE
0 idle READY
1 LED BLOCKED
2 Heartbeat BLOCKED
3 Shell RUNNING
[Heartbeat] 2
Creating div-by-zero crash task...
[Crash] Dividing by zero in 3 seconds...
[Heartbeat] 3
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!! FAULT DETECTED !!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Task 4 (Crash_Div) crashed!
Reason: Divide by zero
PC at fault: 0x41BA
Task terminated. Other tasks continue.
[Heartbeat] 4
[Heartbeat] 5
ID NAME STATE
0 idle READY
1 LED BLOCKED
2 Heartbeat BLOCKED
3 Shell RUNNING
4 Crash_Div TERMINATED
[Heartbeat] 6
[Heartbeat] 7
[Heartbeat] 8
[Heartbeat] 9
PC at fault の PC(プログラムカウンタ)は、落ちた命令のアドレスです。
シミュレータで見る#
実機が無くても、同じファームウェアを シミュレータ で動かすとこう見えます。
d を送ると Crash_Div タスクがゼロ除算でフォールトを起こし、FAULT DETECTED と原因が出ます。そのあとも L(Heartbeat)の点滅と [Heartbeat] の出力は続きます。(シミュレータで撮影)#
壊してみる#
予想してから答えを開いてください。1 つめはコードを変えずに、
ブラウザ版の第6章で
そのまま試せます。2 つめはコードを変えてビルドし、firmware.elf を
ブラウザ版にドロップ します。
注釈
答えに載せた出力は、ブラウザ版と同じ QEMU で実際に動かして取ったものです。 実機ではまだ確かめていません。
1. 続けて 5 回落とす#
d を 5 回続けて送り、しばらく待ってから p でタスクの一覧を出します。
予想: FAULT DETECTED は何回出るでしょう。
答え
4 回しか出ません。 5 回目も 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...
[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 が 4 回)
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
タスクを入れておく表 g_tasks は MAX_TASKS = 8 個ぶんしかありません。
起動時の 4 つ(idle・LED・Heartbeat・Shell)で半分を使い、残りの 4 つを
Crash_Div が埋めます。TERMINATED になったタスクの場所は再利用されないので、
5 回目の os_create_task() は -1 を返して何もしません。シェルは戻り値を
見ていないので、「作ります」とだけ言って、失敗に気づきません。
タスクが終わるたびに場所が減っていくので、この OS は、落ちたタスクを作り直し続けるような 使い方には向きません。
2. ゼロ除算の見張りを外す#
src/os_fault.cpp の fault_init() で、ゼロ除算を例外にする設定をコメントにします。
- SCB->CCR |= SCB_CCR_DIV_0_TRP_Msk;
+ // SCB->CCR |= SCB_CCR_DIV_0_TRP_Msk;
予想: d を送ると、100 / 0 はどうなるでしょう。
答え
FAULT DETECTED が出ません。 それなのに、p で見ると Crash_Div は
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
Cortex-M の割り算命令は、この設定(CCR.DIV_0_TRP)が無いと、0 で割っても
例外を起こさず、答えを 0 にして先へ進みます
(Arm の解説「Divide and Conquer」)。
c が 0 になったまま task_crash_divzero() は最後まで走って return し、
タスクが return したときの受け皿 task_exit() が TERMINATED にしています。
エラーを出さずに、間違った答えで先へ進んでしまうわけです。
fault_init() がわざわざこの設定を入れているのは、このためです。
つまずきやすいポイント#
(読者からの質問に応じて随時追記します。質問は GitHub Issues へどうぞ)