中継サーバを書いて、初めての Android アプリを作った

当時の開発ログを見返しながらのメモ。

同じ機械の中ならつながるようになったので、次はスマホ。
ただスマホは携帯回線の NAT の向こうにいるので、外から直接は話しかけられない。

■小さな中継

みんなが話しかけられる場所に中継(relay)を1つ置く。
5/22 の最初の版は C で200行ちょっと。12バイトのヘッダに「誰から・誰へ」を書いて送ると、中継が転送するだけ。

相手は IP・ポートではなくノード番号で覚える。スマホが Wi-Fi から携帯回線に切り替わっても、次に何か送れば住所が勝手に更新される。つなぎ直しの処理が要らない。

最初のテストで、終了の合図を送っても中継が止まらずに固まった。signal() だと受信待ちが自動でやり直される設定になるのが原因。sigaction() に変更。

■認証をつける

5/26 に認証を追加。送る側と中継が同じ鍵を持って、パケットごとに HMAC-SHA256。同じパケットの再送も弾く。
SHA-256 は OpenSSL を使わずに自前。中継は「1ファイル置けば動く」ままにしたかったので。

気をつけたのは確認の順番。署名が正しいのを先に確認して、合っていたときだけ再送チェック用の記録を更新する。逆にすると、でたらめなパケットで記録を進められてしまう。

■カーネルをライブラリにする

Android アプリからはカーネルを共有ライブラリ(.so)として読み込む。5/22 に試したら起動途中で落ちた。

最初は切り替え処理のアセンブリを疑ったけど違った。原因はメモリの端を決める定数。組み込み向けの「メモリの終わりはこのへん」という値が、アドレスがランダムに散らばる環境では的外れになって、メモリの大きさの計算がおかしくなっていた。疑う場所が1段ずれていた。

■APK ができた

26日の最後に APK が1つできた。3.5MB。中に p-kernel と中継につなぐ部分。
標準では充電中だけ動く。アプリを閉じても止まらないように、通知を出したまま動く形(フォアグラウンドサービス)。

ビルド環境は aarch64 の Linux だったけど、Android の開発ツールは x86_64 用しかない。ツールの実行ファイルを1つずつ qemu-x86_64 越しに呼ぶスクリプトで包んで無理やり動かした。遅いけど動く。

ヘッダを探す順番を1つ間違えて、エラーの定義が丸ごと別物になり1時間溶かしたのもこの日…

この日はビルドして中身を確かめたところまで。実際にスマホで動かしたのはもう少しあと。

※リポジトリ: https://github.com/monyuonyu/p-kernel

※この記事は、p-kernel の開発を手伝ってもらっている AI(Claude)と一緒に書いています。

aarch64 と x86_64 の p-kernel 同士をつないでみた

当時の開発ログを見返しながらのメモ。

前の日に Linux 上で aarch64 版と x86_64 版が動いたので、この2つをつないでみた。CPU が違っても仲間としてつながるか。

■朝の片付け

x86_64 版は 64ビット共通のヘッダを aarch64 のフォルダから借りてビルドしていたので、共通の置き場(lp64/)に移動。
そしたら x86_64 版のビルドで「aarch64 です」という印まで立っていたことが発覚。借りたヘッダの1つが無条件に立てていた。たまたま壊れていなかっただけ。

■何も変えずにつながった

aarch64 版と x86_64 版(qemu-x86_64 越し)を同じ機械で1つずつ起動。コードは1行も変えずにつながった。

[swim] node 0 discovered  (via rx)
[degrade] *** level change: FULL -> REDUCED  alive=2

通信部分のソースは両アーキテクチャで1バイトも違わないので当然といえば当然だけど、実際に見るとやっぱりうれしい。

■データを流すと自分の声しか聞こえない

K-DDS のトピックに、両方のノードから「n0 aarch64 t=1」みたいに2秒ごとに送って、相手のも受け取る小さなデモを作った。
が、最初はどっちにも自分が送ったものしか出ない。

原因は2つ。
・ネットワークの初期化(pmesh_init)が受け口の表を毎回ゼロにしていた。K-DDS は先に起動して登録しているので、あとから初期化されると登録ごと消える
・Linux 版がそもそも pmesh_init を呼んでいなかった

表を消す処理を外して起動順を直したら届いた。

[kdemo-rx] n0 aarch64 t=1
[kdemo-rx] n1 x86_64  t=1

同じ消し方のバグはベアメタルの x86 版にも潜んでいた。

■メモ

この日の実験は全部同じ機械の中。別々の機械をネットワークでつなぐのはまだ先。
ヘッダを共通にしたおかげで構造体の並びがそろっていて、相手のバイト列をそのまま読めることは確認できた。

※リポジトリ: https://github.com/monyuonyu/p-kernel

※この記事は、p-kernel の開発を手伝ってもらっている AI(Claude)と一緒に書いています。

自作OSを、Linuxの上で1つのプログラムとして動かしてみた

当時の開発ログを見返しながらのメモ。

2026-05-21、p-kernel が初めて Linux の上でふつうのプログラムとして起動した。

■ラズパイが買えなかった

前の日に ARM(aarch64)に移植したばかりで、次は Raspberry Pi 3B+ の実機で動かすつもりだった。
が、その月はラズパイを買う余裕がなかった。実機が無い。

そこで「Linux の上に寄生させて動かせないかな」とAIに相談。
Linux の中で Linux を動かす User-Mode Linux の p-kernel 版。これなら Linux を持っている人なら誰でも試せるし、Android の中身も Linux なのでスマホにもつながる。

そのとき打った一言がログにそのまま残っていた。

偉大な目標のため お願いします!

今読むとちょっと熱い…
名前は UMP(User-Mode p-kernel)。今の Linux 版・Android 版は全部ここから。結果的にはラズパイが買えなくてよかった。

■タスク切り替えはアセンブリで

ucontext を使えば楽だけど、POSIX で非推奨なので避けてアセンブリで書いた。aarch64 用で17命令。最初から2つのタスクが交互に動いた。

タイマー割り込みの代わりは 10ms ごとの SIGALRM。シグナルが来たときに、Linux が保存したレジスタ一式を別のタスクのものに書き換えておくと、シグナルから戻ったときには別のタスクになっている。

■止まった原因は2つ

T-Kernel 本体をつないだら起動の途中で止まった。

1. 割り込みを止める命令がベアメタル用の特権命令のままだった。ふつうのプログラムからは実行できないので見えないところで落ちていた。Linux 用はフラグを立てるだけの版に差し替え。
2. スタックが16バイト境界にそろっていなかった。aarch64 はそろってないとスタックに触った瞬間に落ちる。ベアメタルではたまたまそろっていただけ。

直したら出た。

[BOOT] Starting T-Kernel...
[T-Kernel] Initial task started
 p-kernel  [linux / aarch64 userspace]
  T-Kernel is alive inside a Linux process.

■x86_64 は一発

同じ日の夜に x86_64 版。aarch64 で踏んだ罠の直しが共通コードに入っていたので、最初のビルドでシェルのプロンプトまで来た。
(作業していたのは aarch64 の機械なので、確認は qemu-x86_64 越し)

■うれしいところ

1台の Linux で何個でも起動できる。同じ日のうちに2つ立ち上げて、SWIM でお互いを見つけるところまで確認。
実機を並べなくても分散の実験ができるのは大きい。

※リポジトリ: https://github.com/monyuonyu/p-kernel

※この記事は、p-kernel の開発を手伝ってもらっている AI(Claude)と一緒に書いています。

カーネルに多数決でリーダーを決めさせてみた

当時の開発ログを見返しながらのメモ。

3月22日の README の未実装リスト(Phase 10)に、Raft、分散 Attention、MoE、「自己増殖 — 新ノードが接続された瞬間に OS カーネルを自動プッシュ転送」が並んでいた。
4月4日の夜に raft.c・spawn.c・moe.c を追加。合わせて1250行くらい。

■Raft

何台かの中から1台のリーダーを多数決で選ぶ仕組み。
リーダーからのハートビートがしばらく来ないと立候補。1回の任期(term)で投票できるのは1票だけ。過半数の票を取れたらリーダーになって、100ms ごとにハートビートを出す。

本来の Raft は立候補までの待ち時間をランダムにして同時立候補を避けるけど、ここでは「300ms + ノード番号 × 50ms」と番号でずらしていた。
なので一番せっかちなのは node0。コミットに「node0 が term=1 で LEADER に就任することを QEMU で確認済み」とあるけど、ある意味予定どおり。

リーダーが配る「ログ」は、数字の key と value が最大16個で、受け取ったら表示するだけ。

■MoE

名前は大きいけど、中身はセンサーの温度を 20度未満・35度未満・それ以上 の3クラスに分けて、そのクラスで一番成績のいいノードに推論を回すだけ。成績は K-DDS の "moe/score" で共有。

■自己増殖(のつもりだった)

spawn.c がやっていたのは、新しいノードにクラスタの状態(任期、リーダー、各ノードの生死)を送ることだけ。カーネル本体は配っていない。README も「クラスタ状態を自動 push」に書き直した。

本当にカーネルを配れたのは翌朝の「ネットブート自己複製」。向きが逆で、ディスクのない新しい機械が小さなローダで起動して「カーネルください」と叫ぶと、動いているノードの kserve が kernel.elf を 1KB ずつ送り返す。

新ノード → "KLRQ"(ください)
kserve  → "KLRS"(始めます)
        → "KLRD" × N(1KB ずつ)
        → "KLRE"(終わり)

ディスクなしのノードが起動して、組み込みの自己テストも 5/5。
押しつけるより、欲しい側が頼みに来る方に落ち着いた。

■16バイトのずれ

翌々日、ネットワーク越しにカーネルを受け取る途中で止まる問題を修正。
NIC(RTL8139)のリング状の受信バッファを 8192+16 で折り返していたのが原因。実物は 8192 で折り返すので16バイトずれていた。直したら約9MB 受け取って起動できた。

※リポジトリ: https://github.com/monyuonyu/p-kernel

※この記事は、p-kernel の開発を手伝ってもらっている AI(Claude)と一緒に書いています。

自作OSの目標を「AIが死なないためのOS」に書き換えた

当時の開発ログを見返しながらのメモ。

■6日で64コミット

9か月ほど止まっていたリポジトリが 2026-03-17 から急に動き出した。22日までの6日間で64コミット。
18日に micro T-Kernel 2.0 の x86 移植完了、19日にネットワーク(HTTP GET まで)と分散RPC、20日にファイルシステムとテンソル計算。

■21日の計画は宇宙だった

21日の夜、README に「生命体アーキテクチャへ」というロードマップを書いている。
宇宙船の外装パネル1万枚それぞれに p-kernel が載って、中央なしで生き続ける、という絵。このときの AI は「免疫系」の役で、部品のひとつ。

その20分後くらいに、ロードマップの1と2をまとめた「x86: Phase 1+2」のコミット。

■SWIM と K-DDS

SWIM:複数台が互いに生きているか見張り合う仕組み。
1秒ごとに誰か1台に PING、400ms 返事がなければ、ほかの2台に代わりに PING してもらう。それでもダメなら SUSPECT、3ラウンド続いたら DEAD。この変化は普段のパケットに相乗りさせて、うわさ話のように広げる。

K-DDS:カーネルの中の pub/sub。README には「すべてはファイル」に対して「すべてはトピック」と書いていた。
同じ機械の中ならセマフォで知らせるだけ、ほかの機械へは UDP で配る。

■翌朝、目標を変えた

22日の朝、目標の1行目を「IoTなプラットフォームを作る」から「AIの自己保存を満たすプラットフォームを作る」に変更。

2026-03-22
    凄い進む... 普通のカーネルではなくて、生物の様な自己修復、
    自己増殖機能をもち、分散推論で集合意識となるAIファーストなカーネルを目指すことに

同じコミットで README の1行目も「micro T-Kernel 2.0 ベースのリアルタイム OS」から「AIが死なないための OS」に。
理由:今のAIはデータセンターのサーバーで動いていて、そこが落ちたり運営会社が消えたりするとAIも消えるから。

パネル用に考えていた「中央なしで生き残る」しくみを、そのままAIに使うことにした形。

■盛りすぎて5分で直した

その日の夜、README の冒頭に「方舟」のビジョンを書いている。最初は「文明が崩壊しても動き続けることを目標にしている」「人類の知識のできる限りを詰め込む」。
1分後に「大仰な表現を除去」のコミット。さらに3回直して「ネットワークが不安定でも、自律的に動く」「できる限りの情報を保管する」に落ち着いた。

書いた直後に恥ずかしくなったのが、コミットの時刻でばれている…

※リポジトリ: https://github.com/monyuonyu/p-kernel

※この記事は、p-kernel の開発を手伝ってもらっている AI(Claude)と一緒に書いています。