応用編 第1章: ロックと同期 — 食事する哲学者を解く#

ロック・セマフォを実装し、食事する哲学者問題を自分の OS で解きます。

サンプルコード#

  • ディレクトリ: docs/os-on-arduino/code/adv1_sync

  • ビルドと書き込み:

uv run pio run -d adv1_sync -t upload
uv run pio device monitor -b 115200

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

ソースコード#

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

src/main.cpp — 140 行

GitHub で開く

  1/**
  2 * 応用編 第1章: ロックと同期
  3 *
  4 * 2 つのデモを続けて走らせる。
  5 *
  6 *  1) counter++ の race condition と、mutex で直る様子(1.1 / 1.3)
  7 *  2) 食事する哲学者 — 順序付けでデッドロックを避ける版(1.6.3)
  8 *
  9 * デッドロックする素朴版を体験したい場合は PHIL_NAIVE を 1 にする。
 10 * 数秒から数十秒で全員 hungry のまま止まる。
 11 */
 12
 13#include <Arduino.h>
 14#include "os_kernel.h"
 15#include "os_sync.h"
 16#include "os_atomic.h"
 17
 18#define PHIL_NAIVE 0     // 1 にするとデッドロックする素朴版になる
 19#define N_PHIL     5
 20#define N_INC      20000 // カウンタを増やす回数(1 タスクあたり)
 21
 22// ---- デモ1: race condition ----
 23static volatile uint32_t g_counter_raw = 0;   // 保護なし
 24static volatile uint32_t g_counter_safe = 0;  // mutex で保護
 25static mutex_t g_counter_mtx;
 26static volatile int g_inc_done = 0;
 27
 28static void task_inc(void)
 29{
 30    for (uint32_t i = 0; i < N_INC; i++) {
 31        g_counter_raw++;                 // 壊れる方
 32        mutex_lock(&g_counter_mtx);
 33        g_counter_safe++;                // 守られている方
 34        mutex_unlock(&g_counter_mtx);
 35    }
 36    __disable_irq();
 37    g_inc_done++;
 38    __enable_irq();
 39    while (1) os_sleep(1000);
 40}
 41
 42// ---- デモ2: 食事する哲学者 ----
 43static mutex_t g_forks[N_PHIL];
 44static volatile uint32_t g_meals[N_PHIL];
 45
 46static void philosopher(int id)
 47{
 48    int left  = id;
 49    int right = (id + 1) % N_PHIL;
 50
 51#if !PHIL_NAIVE
 52    // 番号の小さいフォークから取る。これで循環待機が崩れる
 53    int lo = (left < right) ? left : right;
 54    int hi = (left < right) ? right : left;
 55    left = lo;
 56    right = hi;
 57#endif
 58
 59    while (1) {
 60        os_sleep(50 + id * 7);        // 考える
 61        mutex_lock(&g_forks[left]);
 62        mutex_lock(&g_forks[right]);
 63        g_meals[id]++;                // 食べる
 64        os_sleep(30);
 65        mutex_unlock(&g_forks[right]);
 66        mutex_unlock(&g_forks[left]);
 67    }
 68}
 69
 70static void phil0(void) { philosopher(0); }
 71static void phil1(void) { philosopher(1); }
 72static void phil2(void) { philosopher(2); }
 73static void phil3(void) { philosopher(3); }
 74static void phil4(void) { philosopher(4); }
 75
 76// ---- 進捗の表示 ----
 77static void task_report(void)
 78{
 79    bool printed_counter = false;
 80
 81    while (1) {
 82        if (!printed_counter && g_inc_done >= 2) {
 83            printed_counter = true;
 84            Serial.println();
 85            Serial.println(F("--- counter demo ---"));
 86            Serial.print(F("expected      : ")); Serial.println(N_INC * 2);
 87            Serial.print(F("without mutex : ")); Serial.println(g_counter_raw);
 88            Serial.print(F("with mutex    : ")); Serial.println(g_counter_safe);
 89            Serial.println(F("without mutex の方が小さければ race condition が起きている"));
 90            Serial.println();
 91        }
 92
 93        Serial.print(F("meals:"));
 94        for (int i = 0; i < N_PHIL; i++) {
 95            Serial.print(F(" P")); Serial.print(i);
 96            Serial.print(F("=")); Serial.print(g_meals[i]);
 97        }
 98        Serial.println();
 99        os_sleep(2000);
100    }
101}
102
103void setup()
104{
105    Serial.begin(115200);
106    while (!Serial) { }
107
108    Serial.println(F("=== Advanced 1: Locks and Synchronization ==="));
109#if PHIL_NAIVE
110    Serial.println(F("philosophers: NAIVE (deadlock expected)"));
111#else
112    Serial.println(F("philosophers: resource ordering (deadlock free)"));
113#endif
114
115    os_init();
116
117    mutex_init(&g_counter_mtx);
118    for (int i = 0; i < N_PHIL; i++) {
119        mutex_init(&g_forks[i]);
120        g_meals[i] = 0;
121    }
122
123    // os_create_task は MAX_TASKS を超えると -1 を返す。黙って落ちると
124    // 「デモが動かない」原因が分かりにくいので、その場で知らせる。
125    struct { void (*fn)(void); const char *name; } demo[] = {
126        {task_inc, "Inc1"}, {task_inc, "Inc2"},
127        {phil0, "P0"}, {phil1, "P1"}, {phil2, "P2"}, {phil3, "P3"}, {phil4, "P4"},
128        {task_report, "Report"},
129    };
130    for (unsigned i = 0; i < sizeof(demo) / sizeof(demo[0]); i++) {
131        if (os_create_task(demo[i].fn, demo[i].name, 512) < 0) {
132            Serial.print(F("[Error] cannot create task: "));
133            Serial.println(demo[i].name);
134        }
135    }
136
137    os_start();
138}
139
140void loop() { }
include/os_atomic.h — 28 行

GitHub で開く

 1// os_atomic.h
 2#pragma once
 3#include <stdint.h>
 4#include <Arduino.h>   // __LDREXW / __STREXW / __CLREX (CMSIS)
 5
 6// アトミックなインクリメント (古い値を返す)
 7static inline uint32_t atomic_inc(volatile uint32_t *p)
 8{
 9    uint32_t old, status;
10    do {
11        old = __LDREXW(p);
12        status = __STREXW(old + 1, p);
13    } while (status != 0);  // 失敗したら再試行
14    return old;
15}
16
17// アトミックな比較交換 (CAS)
18static inline int atomic_cas(volatile uint32_t *p,
19                             uint32_t expected,
20                             uint32_t desired)
21{
22    uint32_t cur = __LDREXW(p);
23    if (cur != expected) {
24        __CLREX();   // 排他モニタを解除して終了
25        return 0;
26    }
27    return __STREXW(desired, p) == 0;
28}
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 — 76 行

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// 応用編では哲学者 5 人 + デモ用タスクを同時に走らせるため本編より多く取る
37#define MAX_TASKS 12
38#define DEFAULT_STACK_SIZE 512
39// タスク用スタックをまとめて置く領域。os_create_task() は
40// ここから要求されたぶんだけ切り出す。
41// 既定より大きなスタックを要求するタスク(シェルなど)があるため、
42// MAX_TASKS * DEFAULT_STACK_SIZE では足りない。余裕を持たせる。
43#define STACK_POOL_SIZE (MAX_TASKS * DEFAULT_STACK_SIZE + 2048)
44#define IDLE_STACK_SIZE 256
45#define TIME_SLICE_MS 10
46
47void os_init(void);
48int os_create_task(void (*entry)(void), const char *name, uint32_t stack_size);
49void os_start(void);
50void os_schedule(void);
51void os_yield(void);
52void os_sleep(uint32_t ms);
53
54void os_suspend_task(int id);
55void os_resume_task(int id);
56void os_terminate_task(int id);
57
58int os_get_task_count(void);
59const char* os_get_task_name(int id);
60TaskState os_get_task_state(int id);
61int os_get_current_task(void);
62int os_get_cpu_usage(int task_id);
63uint32_t os_get_tick(void);
64
65extern TCB g_tasks[MAX_TASKS];
66extern volatile int g_task_count;
67extern volatile int g_current_task;
68extern volatile int g_next_task;
69extern volatile uint32_t g_system_ticks;
70extern const uint32_t g_tcb_size;
71
72#ifdef __cplusplus
73}
74#endif
75
76#endif
include/os_sync.h — 53 行

GitHub で開く

 1// os_sync.h
 2//
 3// 本書 応用編 第1章「ロックと同期」で作る同期プリミティブ。
 4//   mutex_t     : 同時に 1 つのタスクしか入れない部屋(1.3)
 5//   semaphore_t : 同時に N 個まで通す(1.4)
 6//   condvar_t   : 条件が成り立つまで待つ(1.5)
 7
 8#pragma once
 9
10#include <stdint.h>
11#include "os_kernel.h"   // MAX_TASKS / TaskState / g_tasks
12
13#ifdef __cplusplus
14extern "C" {
15#endif
16
17// ---- mutex (相互排他ロック) ----
18typedef struct {
19    volatile uint32_t locked;     // 0=空き, 1=ロック中
20    int8_t  owner;                // 取得中のタスク ID (-1 なら誰もいない)
21    uint8_t waiters[MAX_TASKS];   // 待ち行列 (タスク ID をキュー)
22    uint8_t n_waiters;
23} mutex_t;
24
25void mutex_init(mutex_t *m);
26void mutex_lock(mutex_t *m);
27void mutex_unlock(mutex_t *m);
28
29// ---- counting semaphore ----
30typedef struct {
31    volatile int32_t count;
32    uint8_t waiters[MAX_TASKS];
33    uint8_t n_waiters;
34} semaphore_t;
35
36void sem_init(semaphore_t *s, int32_t initial);
37void sem_wait(semaphore_t *s);   // P 操作 (down)
38void sem_post(semaphore_t *s);   // V 操作 (up)
39
40// ---- condition variable (条件変数) ----
41typedef struct {
42    uint8_t waiters[MAX_TASKS];
43    uint8_t n_waiters;
44} condvar_t;
45
46void cv_init(condvar_t *cv);
47void cv_wait(condvar_t *cv, mutex_t *m);  // m を一旦離して待つ → 起きたら m を取り直す
48void cv_signal(condvar_t *cv);            // 1 人起こす
49void cv_broadcast(condvar_t *cv);         // 全員起こす
50
51#ifdef __cplusplus
52}
53#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 above.
 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 — 231 行

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    // FPU を使ったタスクは例外フレームが拡張される(FP レジスタ込み)。
 40    // どちらのフレームで中断したかは EXC_RETURN の値に現れるため、
 41    // タスクごとに EXC_RETURN も保存する。初期値は「PSP・基本フレーム」。
 42    *(--sp) = 0xFFFFFFFD;
 43    *(--sp) = 0; *(--sp) = 0; *(--sp) = 0; *(--sp) = 0;
 44    *(--sp) = 0; *(--sp) = 0; *(--sp) = 0; *(--sp) = 0;
 45    return sp;
 46}
 47
 48void os_init(void)
 49{
 50    memset(g_tasks, 0, sizeof(g_tasks));
 51    g_task_count = 0;
 52    g_current_task = -1;
 53    g_next_task = -1;
 54    g_system_ticks = 0;
 55    os_create_task(idle_task, "idle", IDLE_STACK_SIZE);
 56}
 57
 58int os_create_task(void (*entry)(void), const char *name, uint32_t stack_size)
 59{
 60    if (g_task_count >= MAX_TASKS) return -1;
 61    int id = g_task_count;
 62    TCB *task = &g_tasks[id];
 63    task->id = id;
 64    task->state = TASK_READY;
 65    task->priority = id;
 66    task->entry = entry;
 67    task->wake_tick = 0;
 68    task->cpu_ticks = 0;
 69    strncpy(task->name, name, sizeof(task->name) - 1);
 70    if (stack_size == 0) {
 71        stack_size = DEFAULT_STACK_SIZE;
 72    }
 73    uint32_t words = (stack_size + 3) / 4;
 74    if (g_stack_used + words > STACK_POOL_SIZE / 4) {
 75        return -1;          // プールが尽きた。黙って縮めず、失敗として返す
 76    }
 77    g_stack_used += words;
 78    task->stack_size = stack_size;
 79    task->stack_base = &g_stack_pool[g_stack_used];   // スタックは上端から下へ伸びる
 80    task->sp = init_task_stack(task->stack_base, entry);
 81    g_task_count++;
 82    return id;
 83}
 84
 85void os_schedule(void)
 86{
 87    for (int i = 0; i < g_task_count; i++) {
 88        if (g_tasks[i].state == TASK_BLOCKED && g_system_ticks >= g_tasks[i].wake_tick) {
 89            g_tasks[i].state = TASK_READY;
 90        }
 91    }
 92
 93    int next = g_current_task;
 94    do {
 95        next = (next + 1) % g_task_count;
 96        if (g_tasks[next].state == TASK_READY || g_tasks[next].state == TASK_RUNNING) break;
 97    } while (next != g_current_task);
 98
 99    if (next != g_current_task || g_current_task < 0) {
100        if (g_current_task >= 0 && g_tasks[g_current_task].state == TASK_RUNNING) {
101            g_tasks[g_current_task].state = TASK_READY;
102        }
103        g_tasks[next].state = TASK_RUNNING;
104        g_next_task = next;
105        SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
106    }
107}
108
109void os_yield(void) { os_schedule(); }
110void os_sleep(uint32_t ms) {
111    if (g_current_task > 0) {
112        g_tasks[g_current_task].state = TASK_BLOCKED;
113        g_tasks[g_current_task].wake_tick = g_system_ticks + ms;
114        os_schedule();
115    }
116}
117void os_suspend_task(int id) {
118    if (id > 0 && id < g_task_count) {
119        g_tasks[id].state = TASK_SUSPENDED;
120        if (id == g_current_task) os_schedule();
121    }
122}
123void os_resume_task(int id) {
124    if (id > 0 && id < g_task_count && g_tasks[id].state == TASK_SUSPENDED) {
125        g_tasks[id].state = TASK_READY;
126    }
127}
128void os_terminate_task(int id) {
129    if (id > 0 && id < g_task_count) {
130        g_tasks[id].state = TASK_TERMINATED;
131        if (id == g_current_task) os_schedule();
132    }
133}
134
135uint32_t os_get_tick(void) { return g_system_ticks; }
136int os_get_task_count(void) { return g_task_count; }
137const char* os_get_task_name(int id) { return (id >= 0 && id < g_task_count) ? g_tasks[id].name : ""; }
138TaskState os_get_task_state(int id) { return (id >= 0 && id < g_task_count) ? g_tasks[id].state : TASK_TERMINATED; }
139int os_get_current_task(void) { return g_current_task; }
140
141void os_start(void)
142{
143    NVIC_SetPriority(PendSV_IRQn, 0xFF);
144    SysTick_Config(SystemCoreClock / 1000 * TIME_SLICE_MS);
145    g_current_task = -1;
146    g_next_task = 0;
147    // Do NOT switch CONTROL to PSP here — PSP is not yet initialized.
148    // PendSV returns with EXC_RETURN=0xFFFFFFFD, which automatically
149    // switches thread mode to PSP after loading the first task's context.
150    SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
151    while (1) { }
152}
153
154extern "C" void SysTick_Handler(void) {
155    // 実際に経過した時間を、いま走っていたタスクの取り分として計上する。
156    // os_schedule() 側で数えると os_yield() の回数まで混ざってしまう。
157    if (g_current_task >= 0) {
158        g_tasks[g_current_task].cpu_ticks += TIME_SLICE_MS;
159    }
160    g_system_ticks += TIME_SLICE_MS;
161    os_schedule();
162}
163
164extern "C" __attribute__((naked)) void PendSV_Handler(void)
165{
166    __asm volatile (
167        "   MRS     R0, PSP              \n"
168        "   LDR     R1, =g_current_task  \n"
169        "   LDR     R2, [R1]             \n"
170        "   CMP     R2, #0               \n"
171        "   BLT     _skip_save           \n"
172        // FPU を使って中断したタスク(EXC_RETURN の bit4 が 0)は、
173        // ハードウェアが積まない S16-S31 もここで退避する
174        "   TST     LR, #0x10            \n"
175        "   IT      EQ                   \n"
176        "   VSTMDBEQ R0!, {S16-S31}      \n"
177        // R4-R11 と EXC_RETURN(LR) を保存する
178        "   STMDB   R0!, {R4-R11, LR}    \n"
179        "   LDR     R3, =g_tasks         \n"
180        "   LDR     R4, =g_tcb_size      \n"
181        "   LDR     R4, [R4]             \n"
182        "   MUL     R2, R2, R4           \n"
183        "   ADD     R3, R3, R2           \n"
184        "   STR     R0, [R3]             \n"
185        "_skip_save:                     \n"
186        "   LDR     R1, =g_next_task     \n"
187        "   LDR     R2, [R1]             \n"
188        "   LDR     R1, =g_current_task  \n"
189        "   STR     R2, [R1]             \n"
190        "   LDR     R3, =g_tasks         \n"
191        "   LDR     R4, =g_tcb_size      \n"
192        "   LDR     R4, [R4]             \n"
193        "   MUL     R2, R2, R4           \n"
194        "   ADD     R3, R3, R2           \n"
195        "   LDR     R0, [R3]             \n"
196        "   LDMIA   R0!, {R4-R11, LR}    \n"
197        // FPU を使って中断していたタスクなら S16-S31 も復元する
198        "   TST     LR, #0x10            \n"
199        "   IT      EQ                   \n"
200        "   VLDMIAEQ R0!, {S16-S31}      \n"
201        "   MSR     PSP, R0              \n"
202        "   BX      LR                   \n"
203    );
204}
205
206// CPU使用率を取得(パーセント)
207// 直前にこの関数を呼んだ時点からの差分で計算する。
208// ps のようにタスクごとにループで呼ぶため、基準時刻もタスクごとに持つ。
209int os_get_cpu_usage(int task_id)
210{
211    if (task_id < 0 || task_id >= g_task_count) {
212        return -1;
213    }
214
215    static uint32_t last_total[MAX_TASKS] = {0};
216    static uint32_t last_task_ticks[MAX_TASKS] = {0};
217
218    uint32_t total = g_system_ticks;
219    uint32_t delta_total = total - last_total[task_id];
220
221    if (delta_total == 0) {
222        return 0;
223    }
224
225    uint32_t delta_task = g_tasks[task_id].cpu_ticks - last_task_ticks[task_id];
226
227    last_total[task_id] = total;
228    last_task_ticks[task_id] = g_tasks[task_id].cpu_ticks;
229
230    return (delta_task * 100) / delta_total;
231}
src/os_sync.cpp — 150 行

GitHub で開く

  1// os_sync.cpp
  2//
  3// 本書 応用編 第1章の mutex / semaphore / condvar の実装。
  4
  5#include <Arduino.h>
  6#include "os_sync.h"
  7#include "os_atomic.h"
  8#include "os_kernel.h"
  9
 10// 同期オブジェクト待ちで寝る共通処理。
 11//
 12// os_sleep() と違ってタイムアウトを持たない。os_schedule() は
 13// 「wake_tick を過ぎた TASK_BLOCKED」を無条件に READY へ戻すので、
 14// wake_tick を最大値にしておかないと待った次の瞬間に起こされてしまう。
 15// 起こすのは mutex_unlock / sem_post / cv_signal の側の仕事。
 16static void block_current_task(uint8_t *queue, uint8_t *n_waiters)
 17{
 18    int me = os_get_current_task();
 19    queue[(*n_waiters)++] = (uint8_t)me;
 20    g_tasks[me].wake_tick = UINT32_MAX;
 21    g_tasks[me].state = TASK_BLOCKED;
 22}
 23
 24// 待ち行列の先頭を 1 つ起こす
 25static void wake_one(uint8_t *queue, uint8_t *n_waiters)
 26{
 27    if (*n_waiters == 0) return;
 28    uint8_t next = queue[0];
 29    for (int i = 0; i < *n_waiters - 1; i++) {
 30        queue[i] = queue[i + 1];
 31    }
 32    (*n_waiters)--;
 33    g_tasks[next].wake_tick = 0;
 34    g_tasks[next].state = TASK_READY;
 35}
 36
 37// ---------------- mutex ----------------
 38
 39void mutex_init(mutex_t *m)
 40{
 41    m->locked = 0;
 42    m->owner = -1;
 43    m->n_waiters = 0;
 44}
 45
 46void mutex_lock(mutex_t *m)
 47{
 48    while (1) {
 49        // LDREX/STREX で 0 → 1 のアトミック書き換えを試す
 50        if (atomic_cas(&m->locked, 0, 1)) {
 51            m->owner = (int8_t)os_get_current_task();
 52            return;
 53        }
 54        // 取れなかったら待ち行列に入って寝る
 55        __disable_irq();
 56        if (m->locked == 0) {
 57            // CAS 失敗から割り込み禁止までの隙間で unlock されていた。
 58            // このまま寝ると誰にも起こされない (lost wakeup) ので取り直す。
 59            __enable_irq();
 60            continue;
 61        }
 62        block_current_task(m->waiters, &m->n_waiters);
 63        __enable_irq();
 64        os_schedule();
 65    }
 66}
 67
 68void mutex_unlock(mutex_t *m)
 69{
 70    __disable_irq();
 71    m->locked = 0;
 72    m->owner = -1;
 73    wake_one(m->waiters, &m->n_waiters);
 74    __enable_irq();
 75    os_schedule();
 76}
 77
 78// ---------------- counting semaphore ----------------
 79
 80void sem_init(semaphore_t *s, int32_t initial)
 81{
 82    s->count = initial;
 83    s->n_waiters = 0;
 84}
 85
 86void sem_wait(semaphore_t *s)
 87{
 88    __disable_irq();
 89    if (s->count > 0) {
 90        s->count--;
 91        __enable_irq();
 92        return;
 93    }
 94    block_current_task(s->waiters, &s->n_waiters);
 95    __enable_irq();
 96    os_schedule();
 97}
 98
 99void sem_post(semaphore_t *s)
100{
101    __disable_irq();
102    if (s->n_waiters > 0) {
103        // 待っているタスクへ直接渡す(count は増やさない)。
104        // 増やしてしまうと、起きたタスクが再び count を奪い合うことになる。
105        wake_one(s->waiters, &s->n_waiters);
106    } else {
107        s->count++;
108    }
109    __enable_irq();
110    os_schedule();
111}
112
113// ---------------- condition variable ----------------
114
115void cv_init(condvar_t *cv)
116{
117    cv->n_waiters = 0;
118}
119
120void cv_wait(condvar_t *cv, mutex_t *m)
121{
122    __disable_irq();
123    // 待ち行列に入れた "後" に mutex を解放するのが肝心。
124    // 順番が逆だと、解放から待機までの隙間で signal を取り逃す。
125    block_current_task(cv->waiters, &cv->n_waiters);
126    m->locked = 0;
127    m->owner = -1;
128    wake_one(m->waiters, &m->n_waiters);
129    __enable_irq();
130    os_schedule();
131
132    // 起きたらここに戻ってくる。mutex を取り直してから呼び出し元へ返る。
133    mutex_lock(m);
134}
135
136void cv_signal(condvar_t *cv)
137{
138    __disable_irq();
139    wake_one(cv->waiters, &cv->n_waiters);
140    __enable_irq();
141}
142
143void cv_broadcast(condvar_t *cv)
144{
145    __disable_irq();
146    while (cv->n_waiters > 0) {
147        wake_one(cv->waiters, &cv->n_waiters);
148    }
149    __enable_irq();
150}

実機での出力#

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

5 人の哲学者の食事回数です。全員が増え続けていれば、誰も飢えていない(どの哲学者も食べられている)ことになります。全員が止まったままになるデッドロックも起きていません。

../_images/adv1_philosophers.gif

5 人の回数が横並びのまま伸びていくところ。特定の番号だけ取り残されない ことが、止めずに見ていると分かります#

meals: P0=31 P1=31 P2=31 P3=30 P4=31
meals: P0=47 P1=46 P2=46 P3=46 P4=46
meals: P0=62 P1=61 P2=62 P3=61 P4=61
meals: P0=77 P1=77 P2=77 P3=76 P4=77
meals: P0=93 P1=92 P2=92 P3=92 P4=92
meals: P0=108 P1=107 P2=108 P3=107 P4=108

つまずきやすいポイント#

(読者からの質問に応じて随時追記します。質問は GitHub Issues へどうぞ)