第5章: 対話型シェル#
シリアル越しに OS と会話するための小さなシェルを作ります。
サンプルコード#
ディレクトリ:
docs/os-on-arduino/code/05_shellビルドと書き込み:
uv run pio run -d 05_shell -t upload
uv run pio device monitor -b 115200
コードを見る: docs/os-on-arduino/code/05_shell
ソースコード#
この章のスケッチです。ファイル名をクリックすると開きます。コードは原稿リポジトリから自動で同期されているので、ここに出ているものが常に最新です。
src/main.cpp — 96 行
1/**
2 * Chapter 5: Interactive Shell Demo
3 *
4 * Features:
5 * - ps: List all tasks with state and CPU usage
6 * - kill N: Suspend task N
7 * - exec N: Resume suspended task N
8 * - info: Show system information
9 * - reboot: Reboot the system
10 */
11
12#include <Arduino.h>
13#include "os_kernel.h"
14#include "shell.h"
15#include "Arduino_LED_Matrix.h"
16
17// Arduino UNO R4 WiFi は基板上に "L" と印字された LED を持っています。
18// 追加部品なしで動かせるよう、これを主 LED として使います。
19#define LED1_PIN LED_BUILTIN
20
21// 2個目の LED も外付けせず、基板内蔵の 12×8 LED マトリクスで代用します。
22// 左半分を LED2 として丸ごと点灯・消灯します。
23// マトリクス制御の仕組みは第7章で学びます。ここではライブラリに任せます。
24static ArduinoLEDMatrix matrix;
25static uint8_t matrix_frame[8][12];
26
27static void matrix_led(int led, bool on)
28{
29 int col_start = (led == 3) ? 6 : 0;
30 for (int row = 0; row < 8; row++) {
31 for (int col = col_start; col < col_start + 6; col++) {
32 matrix_frame[row][col] = on ? 1 : 0;
33 }
34 }
35 matrix.renderBitmap(matrix_frame, 8, 12);
36}
37
38void task_led1(void)
39{
40 pinMode(LED1_PIN, OUTPUT);
41 while (1) {
42 digitalWrite(LED1_PIN, HIGH);
43 os_sleep(200);
44 digitalWrite(LED1_PIN, LOW);
45 os_sleep(200);
46 }
47}
48
49void task_led2(void)
50{
51 while (1) {
52 matrix_led(2, true);
53 os_sleep(500);
54 matrix_led(2, false);
55 os_sleep(500);
56 }
57}
58
59void task_heartbeat(void)
60{
61 int count = 0;
62 while (1) {
63 // Silent heartbeat - just keeps running
64 count++;
65 os_sleep(1000);
66 }
67}
68
69void setup()
70{
71 Serial.begin(115200);
72 while (!Serial) { }
73
74 Serial.println("=== Interactive Shell Demo ===");
75 Serial.println();
76
77 // LEDマトリクスを初期化(LED2 の代用)
78 matrix.begin();
79
80 os_init();
81
82 os_create_task(task_led1, "LED1", 512);
83 os_create_task(task_led2, "LED2", 512);
84 os_create_task(task_heartbeat, "Heartbeat", 256);
85 os_create_task(shell_task, "Shell", 1024);
86
87 Serial.println("Tasks created. Starting OS...");
88 Serial.println();
89
90 os_start();
91}
92
93void loop()
94{
95 // Never reached
96}
include/os_kernel.h — 79 行
1/**
2 * os_kernel.h - Mini OS Kernel Header
3 * Chapter 5: Interactive Shell
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 1024
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
46// Kernel API
47void os_init(void);
48int os_create_task(void (*entry)(void), const char *name, uint32_t stack_size);
49void os_start(void);
50void os_yield(void);
51void os_sleep(uint32_t ms);
52
53// Task control API (for shell)
54void os_suspend_task(int id);
55void os_resume_task(int id);
56void os_terminate_task(int id);
57
58// Task info API
59int os_get_task_count(void);
60const char* os_get_task_name(int id);
61TaskState os_get_task_state(int id);
62int os_get_current_task(void);
63int os_get_cpu_usage(int task_id);
64uint32_t os_get_tick(void);
65uint32_t os_get_cpu_ticks(int id);
66uint32_t os_get_stack_used(int id);
67
68extern TCB g_tasks[MAX_TASKS];
69extern volatile int g_task_count;
70extern volatile int g_current_task;
71extern volatile int g_next_task;
72extern volatile uint32_t g_system_ticks;
73extern const uint32_t g_tcb_size;
74
75#ifdef __cplusplus
76}
77#endif
78
79#endif
include/shell.h — 23 行
1/**
2 * shell.h - Interactive Shell Header
3 * Chapter 5: Interactive Shell
4 */
5
6#ifndef SHELL_H
7#define SHELL_H
8
9#ifdef __cplusplus
10extern "C" {
11#endif
12
13// Shell task entry point
14void shell_task(void);
15
16// Process a single command
17void shell_process_command(char *cmd);
18
19#ifdef __cplusplus
20}
21#endif
22
23#endif
src/os_kernel.cpp — 281 行
1/**
2 * os_kernel.cpp - Mini OS Kernel Implementation
3 * Chapter 5: Interactive Shell
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
16// TCB size for PendSV assembly (compiler may use -fshort-enums, changing sizeof)
17const uint32_t g_tcb_size = sizeof(TCB);
18
19static uint32_t g_stack_pool[STACK_POOL_SIZE / 4];
20static uint32_t g_stack_used; // 切り出し済みのワード数
21
22static void idle_task(void)
23{
24 while (1) {
25 __WFI();
26 }
27}
28
29// タスク関数が return したときの受け皿。例外フレームの LR スロットは
30// ここを指す (第3章 3.4)。EXC_RETURN を置く場所ではない。
31static void task_exit(void)
32{
33 g_tasks[g_current_task].state = TASK_TERMINATED;
34 os_yield();
35 while (1) { } // ここには到達しない
36}
37
38static uint32_t* init_task_stack(uint32_t *stack_top, void (*entry)(void))
39{
40 uint32_t *sp = stack_top;
41
42 *(--sp) = 0x01000000;
43 *(--sp) = (uint32_t)entry;
44 *(--sp) = (uint32_t)task_exit; // LR: タスクが return したときの戻り先
45 *(--sp) = 0;
46 *(--sp) = 0;
47 *(--sp) = 0;
48 *(--sp) = 0;
49 *(--sp) = 0;
50
51 *(--sp) = 0;
52 *(--sp) = 0;
53 *(--sp) = 0;
54 *(--sp) = 0;
55 *(--sp) = 0;
56 *(--sp) = 0;
57 *(--sp) = 0;
58 *(--sp) = 0;
59
60 return sp;
61}
62
63void os_init(void)
64{
65 memset(g_tasks, 0, sizeof(g_tasks));
66 g_task_count = 0;
67 g_current_task = -1;
68 g_next_task = -1;
69 g_system_ticks = 0;
70
71 os_create_task(idle_task, "idle", IDLE_STACK_SIZE);
72}
73
74int os_create_task(void (*entry)(void), const char *name, uint32_t stack_size)
75{
76 if (g_task_count >= MAX_TASKS) return -1;
77
78 int id = g_task_count;
79 TCB *task = &g_tasks[id];
80
81 task->id = id;
82 task->state = TASK_READY;
83 task->priority = id;
84 task->entry = entry;
85 task->wake_tick = 0;
86 task->cpu_ticks = 0;
87 strncpy(task->name, name, sizeof(task->name) - 1);
88
89 if (stack_size == 0) {
90 stack_size = DEFAULT_STACK_SIZE;
91 }
92 uint32_t words = (stack_size + 3) / 4;
93 if (g_stack_used + words > STACK_POOL_SIZE / 4) {
94 return -1; // プールが尽きた。黙って縮めず、失敗として返す
95 }
96 g_stack_used += words;
97 task->stack_size = stack_size;
98 task->stack_base = &g_stack_pool[g_stack_used]; // スタックは上端から下へ伸びる
99 task->sp = init_task_stack(task->stack_base, entry);
100
101 g_task_count++;
102 return id;
103}
104
105void os_schedule(void)
106{
107 for (int i = 0; i < g_task_count; i++) {
108 if (g_tasks[i].state == TASK_BLOCKED) {
109 if (g_system_ticks >= g_tasks[i].wake_tick) {
110 g_tasks[i].state = TASK_READY;
111 }
112 }
113 }
114
115 int next = g_current_task;
116 do {
117 next = (next + 1) % g_task_count;
118 if (g_tasks[next].state == TASK_READY ||
119 g_tasks[next].state == TASK_RUNNING) {
120 break;
121 }
122 } while (next != g_current_task);
123
124 if (next != g_current_task || g_current_task < 0) {
125 if (g_current_task >= 0) {
126 if (g_tasks[g_current_task].state == TASK_RUNNING) {
127 g_tasks[g_current_task].state = TASK_READY;
128 }
129 }
130 g_tasks[next].state = TASK_RUNNING;
131 g_next_task = next;
132 SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
133 }
134}
135
136void os_yield(void) { os_schedule(); }
137
138void os_sleep(uint32_t ms)
139{
140 if (g_current_task > 0) {
141 g_tasks[g_current_task].state = TASK_BLOCKED;
142 g_tasks[g_current_task].wake_tick = g_system_ticks + ms;
143 os_schedule();
144 }
145}
146
147void os_suspend_task(int id)
148{
149 if (id > 0 && id < g_task_count) {
150 g_tasks[id].state = TASK_SUSPENDED;
151 if (id == g_current_task) {
152 os_schedule();
153 }
154 }
155}
156
157void os_resume_task(int id)
158{
159 if (id > 0 && id < g_task_count) {
160 if (g_tasks[id].state == TASK_SUSPENDED) {
161 g_tasks[id].state = TASK_READY;
162 }
163 }
164}
165
166void os_terminate_task(int id)
167{
168 if (id > 0 && id < g_task_count) {
169 g_tasks[id].state = TASK_TERMINATED;
170 if (id == g_current_task) {
171 os_schedule();
172 }
173 }
174}
175
176uint32_t os_get_tick(void) { return g_system_ticks; }
177int os_get_task_count(void) { return g_task_count; }
178const char* os_get_task_name(int id) {
179 if (id < 0 || id >= g_task_count) return "";
180 return g_tasks[id].name;
181}
182TaskState os_get_task_state(int id) {
183 if (id < 0 || id >= g_task_count) return TASK_TERMINATED;
184 return g_tasks[id].state;
185}
186int os_get_current_task(void) { return g_current_task; }
187uint32_t os_get_cpu_ticks(int id) {
188 if (id < 0 || id >= g_task_count) return 0;
189 return g_tasks[id].cpu_ticks;
190}
191uint32_t os_get_stack_used(int id) {
192 if (id < 0 || id >= g_task_count) return 0;
193 uint32_t *base = g_tasks[id].stack_base - (g_tasks[id].stack_size / 4);
194 uint32_t *sp = g_tasks[id].sp;
195 return (g_tasks[id].stack_base - sp) * 4;
196}
197
198void os_start(void)
199{
200 NVIC_SetPriority(PendSV_IRQn, 0xFF);
201 SysTick_Config(SystemCoreClock / 1000 * TIME_SLICE_MS);
202
203 g_current_task = -1;
204 g_next_task = 0;
205
206 SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
207
208 while (1) { }
209}
210
211extern "C" void SysTick_Handler(void)
212{
213 // 実際に経過した時間を、いま走っていたタスクの取り分として計上する。
214 // os_schedule() 側で数えると os_yield() の回数まで混ざってしまう。
215 if (g_current_task >= 0) {
216 g_tasks[g_current_task].cpu_ticks += TIME_SLICE_MS;
217 }
218 g_system_ticks += TIME_SLICE_MS;
219 os_schedule();
220}
221
222extern "C" __attribute__((naked)) void PendSV_Handler(void)
223{
224 __asm volatile (
225 " MRS R0, PSP \n"
226 " LDR R1, =g_current_task \n"
227 " LDR R2, [R1] \n"
228 " CMP R2, #0 \n"
229 " BLT _skip_save \n"
230 // Save R4-R11 only when there is a valid current task
231 " STMDB R0!, {R4-R11} \n"
232 " LDR R3, =g_tasks \n"
233 " LDR R4, =g_tcb_size \n"
234 " LDR R4, [R4] \n"
235 " MUL R2, R2, R4 \n"
236 " ADD R3, R3, R2 \n"
237 " STR R0, [R3] \n"
238 "_skip_save: \n"
239 " LDR R1, =g_next_task \n"
240 " LDR R2, [R1] \n"
241 " LDR R1, =g_current_task \n"
242 " STR R2, [R1] \n"
243 " LDR R3, =g_tasks \n"
244 " LDR R4, =g_tcb_size \n"
245 " LDR R4, [R4] \n"
246 " MUL R2, R2, R4 \n"
247 " ADD R3, R3, R2 \n"
248 " LDR R0, [R3] \n"
249 " LDMIA R0!, {R4-R11} \n"
250 " MSR PSP, R0 \n"
251 " LDR LR, =0xFFFFFFFD \n"
252 " BX LR \n"
253 );
254}
255
256// CPU使用率を取得(パーセント)
257// 直前にこの関数を呼んだ時点からの差分で計算する。
258// ps のようにタスクごとにループで呼ぶため、基準時刻もタスクごとに持つ。
259int os_get_cpu_usage(int task_id)
260{
261 if (task_id < 0 || task_id >= g_task_count) {
262 return -1;
263 }
264
265 static uint32_t last_total[MAX_TASKS] = {0};
266 static uint32_t last_task_ticks[MAX_TASKS] = {0};
267
268 uint32_t total = g_system_ticks;
269 uint32_t delta_total = total - last_total[task_id];
270
271 if (delta_total == 0) {
272 return 0;
273 }
274
275 uint32_t delta_task = g_tasks[task_id].cpu_ticks - last_task_ticks[task_id];
276
277 last_total[task_id] = total;
278 last_task_ticks[task_id] = g_tasks[task_id].cpu_ticks;
279
280 return (delta_task * 100) / delta_total;
281}
src/shell.cpp — 186 行
1/**
2 * shell.cpp - Interactive Shell Implementation
3 * Chapter 5: Interactive Shell
4 */
5
6#include "shell.h"
7#include "os_kernel.h"
8#include <Arduino.h>
9#include <string.h>
10
11#define CMD_BUF_SIZE 64
12
13static char cmd_buf[CMD_BUF_SIZE];
14static int cmd_pos = 0;
15
16static const char* state_to_str(TaskState state)
17{
18 switch (state) {
19 case TASK_READY: return "READY";
20 case TASK_RUNNING: return "RUNNING";
21 case TASK_BLOCKED: return "BLOCKED";
22 case TASK_SUSPENDED: return "SUSPEND";
23 case TASK_TERMINATED: return "TERM";
24 default: return "?";
25 }
26}
27
28static void cmd_help(void)
29{
30 Serial.println("Available commands:");
31 Serial.println(" ps - List all tasks");
32 Serial.println(" kill N - Suspend task N");
33 Serial.println(" exec N - Resume task N");
34 Serial.println(" info - System information");
35 Serial.println(" reboot - Reboot system");
36 Serial.println(" help - Show this help");
37}
38
39static void cmd_ps(void)
40{
41 Serial.println();
42 Serial.println("ID NAME STATE CPU%");
43 Serial.println("-- ---- ----- ----");
44
45 for (int i = 0; i < os_get_task_count(); i++) {
46 Serial.print(i);
47 Serial.print(" ");
48
49 const char *name = os_get_task_name(i);
50 Serial.print(name);
51 for (int j = strlen(name); j < 15; j++) Serial.print(" ");
52
53 Serial.print(state_to_str(os_get_task_state(i)));
54 for (int j = strlen(state_to_str(os_get_task_state(i))); j < 10; j++) Serial.print(" ");
55
56 Serial.print(os_get_cpu_usage(i));
57 Serial.println("%");
58 }
59 Serial.println();
60}
61
62static void cmd_kill(int id)
63{
64 if (id <= 0) {
65 Serial.println("Error: Cannot kill idle task (ID 0)");
66 return;
67 }
68 if (id >= os_get_task_count()) {
69 Serial.println("Error: Invalid task ID");
70 return;
71 }
72
73 os_suspend_task(id);
74 Serial.print("Task ");
75 Serial.print(id);
76 Serial.print(" (");
77 Serial.print(os_get_task_name(id));
78 Serial.println(") suspended.");
79}
80
81static void cmd_exec(int id)
82{
83 if (id <= 0 || id >= os_get_task_count()) {
84 Serial.println("Error: Invalid task ID");
85 return;
86 }
87
88 os_resume_task(id);
89 Serial.print("Task ");
90 Serial.print(id);
91 Serial.print(" (");
92 Serial.print(os_get_task_name(id));
93 Serial.println(") resumed.");
94}
95
96static void cmd_info(void)
97{
98 Serial.println();
99 Serial.println("=== System Information ===");
100 Serial.print("System Ticks: ");
101 Serial.println(os_get_tick());
102 Serial.print("Uptime: ");
103 Serial.print(os_get_tick() / 1000);
104 Serial.println(" s");
105 Serial.print("Task Count: ");
106 Serial.println(os_get_task_count());
107 Serial.print("Time Slice: ");
108 Serial.print(TIME_SLICE_MS);
109 Serial.println(" ms");
110 Serial.println();
111}
112
113static void cmd_reboot(void)
114{
115 Serial.println("Rebooting...");
116 delay(100);
117 NVIC_SystemReset();
118}
119
120void shell_process_command(char *cmd)
121{
122 // Trim whitespace
123 while (*cmd == ' ') cmd++;
124 char *end = cmd + strlen(cmd) - 1;
125 while (end > cmd && *end == ' ') *end-- = '\0';
126
127 if (strlen(cmd) == 0) return;
128
129 // Parse command
130 if (strcmp(cmd, "help") == 0 || strcmp(cmd, "?") == 0) {
131 cmd_help();
132 } else if (strcmp(cmd, "ps") == 0) {
133 cmd_ps();
134 } else if (strncmp(cmd, "kill ", 5) == 0) {
135 int id = atoi(cmd + 5);
136 cmd_kill(id);
137 } else if (strncmp(cmd, "exec ", 5) == 0) {
138 int id = atoi(cmd + 5);
139 cmd_exec(id);
140 } else if (strcmp(cmd, "info") == 0) {
141 cmd_info();
142 } else if (strcmp(cmd, "reboot") == 0) {
143 cmd_reboot();
144 } else {
145 Serial.print("Unknown command: ");
146 Serial.println(cmd);
147 Serial.println("Type 'help' for available commands.");
148 }
149}
150
151void shell_task(void)
152{
153 Serial.println();
154 Serial.println("Mini OS Shell");
155 Serial.println("Type 'help' for commands.");
156
157 while (1) {
158 Serial.print("> ");
159
160 cmd_pos = 0;
161 memset(cmd_buf, 0, CMD_BUF_SIZE);
162
163 while (1) {
164 if (Serial.available()) {
165 char ch = Serial.read();
166
167 if (ch == '\r' || ch == '\n') {
168 Serial.println();
169 break;
170 } else if (ch == '\b' || ch == 127) {
171 if (cmd_pos > 0) {
172 cmd_pos--;
173 cmd_buf[cmd_pos] = '\0';
174 Serial.print("\b \b");
175 }
176 } else if (cmd_pos < CMD_BUF_SIZE - 1) {
177 cmd_buf[cmd_pos++] = ch;
178 Serial.print(ch);
179 }
180 }
181 os_sleep(10);
182 }
183
184 shell_process_command(cmd_buf);
185 }
186}
実機での出力#
Arduino UNO R4 WiFi の実機で実際に取得した出力です(macOS / Windows の 両方で同じ結果を確認しています)。手元の出力と見比べてください。
help と ps を送ったところです。CPU% の合計がほぼ 100% になるのが、計測が正しく動いている目印です。
help
Available commands:
ps - List all tasks
kill N - Suspend task N
exec N - Resume task N
info - System information
reboot - Reboot system
help - Show this help
> ps
ID NAME STATE CPU%
-- ---- ----- ----
0 idle READY 99%
1 LED1 BLOCKED 0%
2 LED2 BLOCKED 0%
3 Heartbeat BLOCKED 0%
4 Shell RUNNING 0%
>
実機ではこう光ります。
kill 1 で LED1 タスクを止めると L LED の点滅が止まり(止めたときの状態のまま)、exec 1 で戻ります。マトリクス左半分(LED2)はその間も 0.5 秒ごとに点滅を続けます。(UNO R4 WiFi を Web カメラで 30 fps で撮影)シミュレータで見る#
実機が無くても、同じファームウェアを シミュレータ で動かすとこう見えます。
L(LED1)とマトリクス左半分(LED2)が点滅する中で、シェルから kill 1 を送ると L だけが止まり、exec 1 でまた点滅し始めます。左半分は止まりません。(シミュレータで撮影)#
つまずきやすいポイント#
(読者からの質問に応じて随時追記します。質問は GitHub Issues へどうぞ)