はじめに:働きながら「OS自作」という壁に挑んだ記録
「いつかRTOSやカーネルみたいな低レイヤを、ちゃんと手を動かして理解したい」。
そう思いながらも、仕事と学びの両立のなかで腰が引けてしまう……そんな経験はありませんか?
低レイヤ開発は、ブート、割り込み、コンテキストスイッチと、最初の一歩のハードルがとにかく高い領域です。一人で全部抱えると、環境構築だけで心が折れそうになりますよね。
そこで私が選んだのが、AIエージェント(Codex / Claude Code)と仕様駆動開発(SDD)を「自己拡張(self-extension)」の相棒にするという挑戦でした。AIに丸投げして代わりに作ってもらう(proxy)のではなく、自分の思考と時間のレバレッジを広げ、低レイヤという険しい山を段階的に登り切るための装備として使い倒す、という発想です。
この記事は、その連載「CodexとSDDでμITRON風RTOSを作る」の全15章(各3〜4節)+特別編2本を、ひとつのロードマップとして再構成した「軌跡」のまとめです。各回の核心となった知見と、つまずきの乗り越え方を一気に振り返ります。
連載のスタート地点(第1章1.1)はこちら 👉 https://zenn.dev/pekopugu/articles/ch1-1-project-concept
ここで作ったのは、厳密なμITRON準拠RTOSではなく、学習・実験用の「μITRON風」RTOSです。「AIエージェントとSDDで、低レイヤをどこまで設計・実装・検証できるか」を検証する挑戦として読んでください。
ゴール:何を目指して走ったのか
最初に立てたゴールはシンプルです。
μITRON風APIを持つ、マルチアーキテクチャ対応のRTOSを、x86_64 + QEMU上で動かす
それを支える「3つの装備」がこちらです。
- 構想設計:ChatGPT(壁打ちで方向性を整理)
-
仕様駆動開発(SDD):
spec.md→plan.md→tasks.mdに落とす - 実装支援:Codex(編集・ビルド・QEMU実行・修正ループ)
設計を「ひとつの作業」と捉えず、構想設計 → 仕様設計 → 実装設計の3層に分けたのが、この挑戦を最後まで回し続けられた一番の工夫でした。
全体俯瞰:v1.0までのロードマップ
15章を「フェーズ」でくくると、登った山の全体像が見えてきます。
【準備フェーズ】 第1章 開発準備(構想・環境・公開方針)
【起動フェーズ】 第2章 QEMU起動・シリアル・HAL境界
【骨格フェーズ】 第3章 TCB・スケジューラ・currentタスク
第4章 タスク実行モデル(entryの扱い)
【心臓フェーズ】 第5章 スタック・レジスタ保存・最小コンテキストスイッチ
【部品フェーズ】 第6章 セマフォ・タイマ・プリエンプション基盤
【割り込みフェーズ】第7章 IDT/GDT・PIC・タイマ割り込み入口・観測モデル
第8章 タイマIRQからtick/プリエンプション判定/dispatch pending
【接続フェーズ】 第9章 タスク間コンテキストスイッチ(dispatcher境界)
第10章 協調API(yield_tsk)
第11章 プリエンプティブスケジューリング
【同期フェーズ】 第12章 セマフォ実行統合(WAITING・wait queue)
第13章 遅延・タイムアウト(delay queue)
【API化フェーズ】 第14章 μITRON風API層・共通エラーコード体系
【総括フェーズ】 第15章 README/記事整理・Doxygen導線・v1.0確定
【特別編】 SP1 Claude Codeで設計レビュー
SP2 Codexでレビュー指摘を修正
到達点を一行でいうと——
「kernel_mainに着いてログを出すだけの小さなカーネル」から、「起動・タスク・スケジューラ・割り込み・セマフォ・遅延・μITRON風API層まで一通り追える学習用RTOS v1.0」へ。
各ステップの要点総括
ここからは各章の「核心となった知見」と「つまずきの乗り越え方」を凝縮してお届けします。細かいコードは連載本編に譲り、判断の軌跡にフォーカスします。
第1章:準備フェーズ — いきなり実装しない
- 核心:構想設計(ChatGPT)/仕様設計(SDD)/実装設計(Codex)の3層分担と、HAL導入・QEMU統一・ブート方式はアーキ別に分離、という方針を固めた回。
- つまずき → 乗り越え:「AIでRTOSを作りたい」と大ざっぱに投げると検討範囲が爆発しました。そこで「まずx86_64 + QEMUで最小カーネル起動」と前提を絞り、AIの提案を確認しながら足りない観点を一緒に埋める進め方に切り替え。あわせて、SPDX対応や公開方針(機密・無断転記の排除)も事前に決めておきました。
第2章:起動フェーズ — 最初の「動いた!」
-
核心:QEMUで
kernel_mainに到達し、シリアル出力APIとHAL境界(kernel → HAL → arch → serialの依存方向)を整えた回。 -
つまずき → 乗り越え:いちばんの壁は、素直に作った64-bit ELFを
qemu-system-x86_64 -kernelに渡せなかったこと。ブート形式の制約に正面からぶつかり、ここで「ブートはアーキごとに個別、カーネル本体は共通」という第1章の方針が効いてきます。HAL境界を最初に切ったことが、後半のIRQ実装まで効き続けました。
第3章:骨格フェーズ — タスクという概念を持たせる
-
核心:TCB(タスク制御ブロック)と静的
task_table、簡易優先度スケジューラ、currentタスクとRUNNING状態を導入。RTOSの「骨格」が立ち上がった回。 - 乗り越えの軸:スケジューラは「READYから選ぶだけ」、状態はTCBが持つ、と責務の所有者を最初に決めておいたことが、後の設計レビュー(特別編)で効いてきます。
第4章:タスク実行モデル — entry returnを「終了」にしない
- 核心:currentタスクのentry関数を呼び、協調的に繰り返す実行モデルを構築。
- つまずき → 乗り越え:entry関数から戻ってきたとき、それを安易に「タスク終了」とみなすと状態遷移が破綻します。「entry returnは終了ではない」と明確に切り分け、観測しながら扱いを確定させました。
第5章:心臓フェーズ — 最小コンテキストスイッチ
- 核心:タスクごとのスタックとレジスタ保存領域をTCBに持たせ、最小のコンテキストスイッチを実現。RTOSの心臓部です。
-
つまずき → 乗り越え:ここでもQEMUの
-kernelとELF形式に再び足を取られました。低レイヤでは「アーキ依存の地雷」を都度踏みながら、HAL/archに切り分けて前進する——この泥臭さこそ成長のスパイスでした。
第6章:部品フェーズ — 同期とタイマの土台
- 核心:セマフォ基盤とWAITING状態、system tickを刻むタイマ基盤、プリエンプション判断基盤を用意。まだ「判断するだけ」で実切替には繋がない段階。
- 乗り越えの軸:機能を一気に繋がず、「土台だけ作って観測する」段階を挟むことで、後の統合がぐっと安全になりました。
第7章:割り込みフェーズ — ログをどこまで信じるか
- 核心:IDT/GDTと例外ハンドラ、PIC初期化とIRQ remap、vector 32にタイマ割り込み入口を作成。
-
つまずき → 乗り越え:この章の白眉は7.4の「割り込み中serial logの制約を明文化」した回。
[timer-irq] entry reachedが見えても、それは「ハンドラに到達した」以上のことを保証しません。「ログが見えた=正しい、ではない」という観測モデルの線引きを、ここで言語化できたのが大きな収穫でした。
第8章:割り込みとディスパッチ保留 — 実切替の手前まで
-
核心:ハードウェアタイマIRQから
timer_tick()を呼び、プリエンプション判定→dispatch pendingへ接続。割り込みの入口/出口の責務も整理。 - 乗り越えの軸:「切り替えたい」という要求(dispatch pending)をまず保留として観測可能にすることで、実切替(第9章)への橋渡しを安全に行えました。
第9章:接続フェーズ — タスク間コンテキストスイッチ
-
核心:起動時smokeをタスク間切替へ拡張し、
dispatcher_switch_to()相当の切替境界を作成。RUNNING/READY遷移を実切替に接続。 - つまずき → 乗り越え:9.3で「dispatcher境界でswitch元を一度READYへ戻す」設計にしたため、9.4のentry return時にタスクがRUNNINGに見えない、という状態遷移の矛盾が発生。DORMANT化APIがRUNNINGだけを受け付けると衝突するため、設計どうしの整合を取りながらタスク終了状態を確定させました。
第10章:協調API — yield_tskで主導権を渡す
-
核心:
yield_tsk風APIの入口を作り、実行中タスクをREADYへ戻し、次タスクを選び、コンテキストスイッチへ接続。 - 乗り越えの軸:「入口を作る → READYへ戻す → 次を選ぶ → 切替へ繋ぐ」と1節1ステップで分解したことで、協調スケジューリングを破綻なく組み上げられました。
第11章:プリエンプティブスケジューリング — 横取りの実現
- 核心:タイマIRQ後に高優先度READYを検出し、割り込み後段でpendingを消費して切り替え。
- 乗り越えの軸:あえて「同一優先度はまだタイムスライス対象外」と割り切り、スコープを絞ったのが賢明でした。最後にプリエンプション発生ログを安定化させ、観測の再現性を確保。
第12章:同期フェーズ — セマフォを実行に統合
-
核心:
wai_semでRUNNINGをWAITINGへ、sig_semで待ちタスクをREADYへ、wait queueを導入、wakeup後のプリエンプション判定まで接続。 - 乗り越えの軸:状態遷移(RUNNING↔WAITING↔READY)とwait queueの所有を丁寧に分けたことで、同期プリミティブが「ただ動く」から「筋の通った状態機械」へ近づきました。
第13章:遅延・タイムアウト — 時間で起こす
-
核心:
dly_tsk風API、sleep/delay queue、timeout付きtwai_sem、tick到達によるREADY復帰を実装。 - 乗り越えの軸:delay queueという「時間で待つ仕組み」を、セマフォ待ちと同じ状態遷移の語彙に乗せたことで、APIが一貫した世界観になりました。
第14章:API化フェーズ — μITRON風の顔を与える
-
核心:ここまで積んだ内部部品を、
cre_tsk/sta_tsk、slp_tsk/wup_tsk、wai_sem/sig_sem/pol_sem/twai_semとして整理。戻り値をE_OK/E_ID/E_PAR/E_OBJ/E_TMOUTなどの共通エラーコード体系へ寄せた回。 - 乗り越えの軸:失敗理由をログから追える形に統一したことで、「なぜ失敗したか」が一目で分かるRTOSになりました。
第15章:総括フェーズ — v1.0として区切る
- 核心:READMEとZenn記事一覧で到達点を整理(15.1)、Doxygen生成導線を追加(15.2)、そしてv1.0を確定(15.3)。
- 乗り越えの軸:一番気をつけたのは、「v1.0という名前を完成品に見せない」こと。READMEに「Scope(できること)」だけでなく「Non-Goals(まだやらないこと:完全互換/SMP/mutex/event flag等)」を明記し、過剰な期待を生まないようにしました。
特別編:AIに「実装」と「レビュー」を分担させる
走り切った後、実装役(Codex)とレビュー役(Claude Code)を意図的に分けるという挑戦をしました。同じAIで実装とレビューを続けると、都合のいい仮定を引きずってしまうからです。
特別編1:Claude Codeで設計レビュー
- 良かった点:HAL境界の遵守、TCB状態所有権をtask moduleへ集約する方針、段階的実装+ログ観測モデルの一貫性。
- 見つかった重要課題(抜粋):
- C-2:ブロッキング待ちからの復帰コンテキストが未保存(RTOSの根幹だが変更範囲が巨大)
- C-1:dispatcherがTCB stateを直接書き換えている(責務の二重化)
- C-3/M-6:task APIに割り込み保護がなく、dispatch pendingが競合し得る
- 最大の学び:「動くログ」と「健全な設計」は別物。状態の所有者(TCB stateは誰が変えるか等)を決めることがRTOSでは決定的に重要だと再認識しました。
特別編2:Codexで指摘を修正
レビュー指摘を全部一気に直さない判断がキモでした。
| 指摘 | 扱い | 理由 |
|---|---|---|
| M-4(READYからのWAITING許可) | 修正 | 局所的に直せる状態遷移の不整合 |
| C-1(TCB直接書き換え) | 修正 | Dispatcherとtask moduleの責務を整理 |
| C-3/M-6(pending競合) | 段階対応 | 由来情報を足して消失リスクを局所的に潰す |
| C-2(復帰コンテキスト保存) | 見送り | 変更範囲が大きすぎる → v2.0で設計してから |
修正後はClaude Codeで再レビューし、3件OK・退行なしを確認。新たな懸念(stale pendingがIRQ pendingをブロックし得る等)はv2.0課題として記録しました。
AIは回転を速くする。けれど「何を採用し、何を次へ回すか」は人間が決める。 これがこの挑戦の結論です。
まとめ:走りきって得られた「時間と心のゆとり」
15章+特別編2本を駆け抜けて、手元に残ったものを振り返ります。
🛠 成果物として
- x86_64 + QEMUで起動し、タスク・スケジューラ・割り込み・セマフォ・遅延・μITRON風API層まで一通り追える学習用RTOS v1.0
- 「できること」と「やらないこと」を明示した、嘘のないドキュメント
🧠 進め方として得た「ゆとり」
- 3層設計(構想→仕様→実装)で、低レイヤでも迷子にならずに済んだ
- 1節1ステップ+ログ観測で、毎回「小さく動かして確かめる」が習慣になり、心理的負荷が激減した
- 実装役とレビュー役のAIを分けることで、自分の盲点を外から突いてもらえた
一番伝えたいのは、AIを「代わりにやってもらう道具(proxy)」ではなく「自分の挑戦を一段上まで運んでくれる自己拡張(self-extension)」として使うと、低レイヤのような重い山でも、仕事と両立しながら一歩ずつ確実に登れる、ということです。
「動いた」で満足せず「正しいか?」を問い続ける——その余白(ゆとり)こそ、AIと組むことで生まれた最大の収穫でした。次はv2.0、C-2のブロッキング待ち復帰へ。挑戦は続きます。
連載の入口(第1章1.1)👉 https://zenn.dev/pekopugu/articles/ch1-1-project-concept