応用編 第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
ソースコード#
この章のスケッチです。ファイル名をクリックすると開きます。コードは原稿リポジトリから自動で同期されているので、ここに出ているものが常に最新です。
src/main.cpp — 140 行
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 行
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 行
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 行
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 行
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 行
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 行
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 行
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 人の哲学者の食事回数です。全員が増え続けていれば、誰も飢えていない(どの哲学者も食べられている)ことになります。全員が止まったままになるデッドロックも起きていません。
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 へどうぞ)