第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

コードを見る: docs/os-on-arduino/code/06_memory_protection

ソースコード#

この章のスケッチです。ファイル名をクリックすると開きます。コードは原稿リポジトリから自動で同期されているので、ここに出ているものが常に最新です。

src/main.cpp — 141 行

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 行

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 行

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 行

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 行

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}

実機での出力#

Arduino UNO R4 WiFi の実機で実際に取得した出力です(macOS / Windows の 両方で同じ結果を確認しています)。手元の出力と見比べてください。

d を送ってゼロ除算タスクを作ると、そのタスクだけが TERMINATED になり、Heartbeat は動き続けます。

コマンドは 1 文字です。d はゼロ除算を起こすタスク(Crash_Div)を、m は 5 秒後に 不正なアドレス(0xFFFFFFFF)へ書き込むタスク(Crash_Mem)を作ります。p はタスクの 一覧を表示します。

../_images/ch06_fault.gif

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(プログラムカウンタ)は、落ちた命令のアドレスです。

シミュレータで見る#

実機が無くても、同じファームウェアを シミュレータ で動かすとこう見えます。

../_images/sim_ch06_fault.gif

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 へどうぞ)