当時の開発ログを見返しながらのメモ。
2026年6月、x86 のベアメタル版がたまに落ちるようになっていた。
■症状
AI の推論を担当する小さなプログラムを kill して再起動させる、を繰り返していると、たまにカーネル本体がページフォルトで止まる。
=== KERNEL EXCEPTION === Page Fault err=0x00000002 EIP=knl_wait_release_tmout+0x11
落ちる場所は、タイマで待っているタスクを起こす処理。
■仮説1:古いタイマ
kill したタスクのタイマが残っていて発火しているのでは、と考えて 6/10 に KILL-TIMER-RACE として課題表に登録。
でも落ちた瞬間のタイマの待ち行列はいつもきれい。「壊れた関数ポインタ」に見えた値も、8バイトの時刻を4バイトのポインタとして読み違えていただけ。ハズレ。
■原因その1:TSS.RSP0
CPU がユーザ側からカーネル側に入るとき、どのスタックにレジスタを積むかを覚えておく場所。
ディスパッチャはスタックは切り替えているのに、RSP0 は起動時に設定したきり一度も更新していなかった。kill されたタスクのスタックが解放されたあとも RSP0 はそこを指したまま。次の割り込みで CPU が解放済みのメモリに書き込んで、別の誰かの戻り先を壊す。
切り替えのたびに RSP0 を更新するようにした(アセンブラで21行)。
直す前は2〜6回の kill で落ちていたのが、20回続けても大丈夫に。
■でもまだ落ちる
頻度は10分の1くらいになったけど、ゼロにならない。
・「割り込みでスタックがあふれている」説 → スタックを 2KB・8KB・16KB と変えても落ち方が同じなので違う
・6/12 の「アドレス空間の切り替え漏れが根本原因」という修正 → 同条件で比べたら、修正前 24回中0回、修正後 24回中11回落ちる。悪化していたので即取り消し
■原因その2:使い回された枠
6/14、やっと再現して記録を追ったら、犯人はタスク管理表の使い回された枠。
kill したタスクの枠に新しいタスクが入った直後に、前の住人のタイマが遅れて発火して、新しい住人の中身を書き換えていた。
最初の「古いタイマ」説は半分当たりだった。被害者は死んだタスクではなく、その枠を引き継いだ新しいタスクの方。
タイマ発火時に「そのタスクがまだ本当に待っているか」を確認して、待っていなければ何もしないようにした。
■メモ
犯人が2人いて、ほぼ同じ落ち方をしていた。1つ直しても「まだ直っていない」ように見える。
修正が効いたかどうかは、直す前と同じ日・同じ手順で比べないと分からない。
※リポジトリ: https://github.com/monyuonyu/p-kernel
※この記事は、p-kernel の開発を手伝ってもらっている AI(Claude)と一緒に書いています。