カーネルの高速化・安定化 改善案

最終更新: 2026-11-03

対象実装: arm/v1/src/kernel/(scheduler.rs 1189 行 / mmu.rs 318 行 / interrupt.rs 114 行 / allocator.rs 326 行 / reclock.rs 150 行 / timer.rs 193 行)

この文書の位置づけ

spec/resource_loading.md と spec/webfont_loading.md は ブラウザ側(L7)の性能を扱ってきた。カスケード索引(rule_index)や 軽量再ペイント経路(repaint_scope)など、そちらの最適化は一巡している。

本文書はカーネル側の改善案を扱う。まだ体系的に手が入っていない領域で、 コード読解で見つけた構造的な改善余地を記録する。

重要: 本文書は「案」であり実測結果ではない

spec/resource_loading.md の教訓を踏襲する。

したがって本文書の各作戦は、着手前に基準値を測ることを前提とする。 「コードを読んで重そうだと思った」だけの段階であることを明示しておく。

過去の実績として、L7 側では 5 つの仮説のうち当たったのは 1 つだけだった (spec/resource_loading.md「教訓」節)。カーネル側でも同じ確率だと想定すべき。


現状で確認できた事実(コード読解による)

推測と区別するため、ソース上の位置と内容だけを先に列挙する。

# 事実 位置
F-1 コンテキストスイッチ毎に tlbi vmalle1is + dsb sy を無条件実行 scheduler.rs:806-813, 823-830
F-2 spawn 系 4 箇所すべてが create_process_page_table() を呼ぶ scheduler.rs:300,395,500,589
F-3 create_page_table_internal(true) はカーネルマップを丸ごとコピーするだけ(EL0 向け改変は is_kernel=false のときのみ) mmu.rs:167-233
F-4 恒等マップは 2MB ブロック × 1024 = 2GB。L1 は 2 エントリのみ使用 mmu.rs:39-68
F-5 タイマは freq/100 = 10ms 固定。全コアで発火 timer.rs:62
F-6 スケジューラの選択は全プロセス走査 O(N)。Ready 全員に starvation_count += 1 を書き込む scheduler.rs:775-788
F-7 kernel/*.rs に static mut が 59 箇所、allow(static_mut_refs) が 11 ファイル grep 実測
F-8 ヒープは TLSF(rlsf)。確保・解放とも最悪 O(1) allocator.rs:27
F-9 spin::Mutex::lock は取得時に割り込みを禁止する allocator.rs:255-259
F-10 RecoverableMutex は holder 記録 + コア指定の強制解放 + selftest を持つ reclock.rs 全体

F-8・F-9・F-10 は既に良い設計であり、変更対象ではない。 特に F-10 は安定化の道具として完成しているので、適用範囲を広げるだけでよい。


【2026-08-25 実装完了】作戦 1: カーネルスレッド間の TLB 全フラッシュを廃止・ページテーブル共有

実施した内容

  1. ページテーブル共有(1-b):
  2. 無駄な TLB フラッシュのスキップ(1-a):

効果


作戦 2: MMU の粒度を見直す(TLB 効率)

現状

F-4 のとおり 2MB ブロックを 1024 個並べて 2GB を恒等マップしている。 L1 テーブルは 2 エントリしか使っていない。

改善余地

L1 で 1GB ブロックを使えば、RAM 領域(0x0000_0000〜0x37FF_FFFF)は 少数のエントリで表現でき、TLB エントリ消費が構造的に減る。

ただし属性の境界を崩してはならない。mmu.rs:47-50 のコメントにあるとおり:

gpu_mem=128 の RPi3 では GPU/フレームバッファ領域は 0x3800_0000〜0x3FFF_FFFF。 フレームバッファがこの範囲に確保され得るため、0x3800_0000 以上を Device(uncached) にしないと CPU のフレームバッファ書き込みが GPU と非コヒーレントになり横線ノイズが出る。

したがって 0x3800_0000 以上は 2MB ブロックの L2 を残すハイブリッド構成にする。

制約(絶対に守る)

期待効果と不確実性

TLB ミス率の低下は原理的に期待できるが、実際にどれだけ効くかは未知。 現状 TLB ミスが支配的である証拠はまだ無い。作戦 1 の計測でスイッチ頻度が 低いと分かれば、この作戦の優先度は下がる。

結論(2026-11-03 調査・評価完了)

  1. RPi 3B+ の実物理メモリ構成(1GB)との不整合:
  2. 作戦 1(TLB スキップ・ページテーブル共有)による課題解消済み:

作戦 3: タイマ割り込みの適応化(tickless idle)

現状

F-5 のとおり 10ms 固定、全コアで発火。interrupt.rs:104-112 で毎 tick:

if timer_fired {
    scheduler::decrement_wait_ticks();
    scheduler::scheduler_tick();
}
if scheduler::should_preempt() {
    scheduler::schedule();
}

改善案

前提条件(順序を守る)

timer.rs:86 に「SYSTEM_TICKS は『1 tick ≈ 10ms』という消費側の前提を保つため」 と明記されている。間隔を可変にするとこの前提が壊れる。

したがって着手順序は:

  1. 時刻取得を get_system_time_ms()(cntpct_el0 直読)へ完全移行する
  2. SYSTEM_TICKS に依存する箇所を全部洗い出して潰す
  3. その後で初めてタイマ間隔を可変にする

順序を逆にすると時刻がずれ、sleep やタイムアウトが壊れる。 spec/resource_loading.md の R-8(依存順序を確かめてから着手する)に該当する。


作戦 4: スケジューラ選択ループの O(N) 削減

現状

F-6 のとおり scheduler.rs:775-788:

for i in 1..=num_proc {              // O(N)
    let idx = (start_idx + i) % num_proc;
    let p = &mut rq.processes[idx];
    if p.state == ProcessState::Ready {
        let score = (p.priority as usize * 10) + p.starvation_count;
        if best_idx.is_none() || score > best_score { ... }
        p.starvation_count += 1;     // Ready 全員に書き込み
    }
}

問題

対策案

ただし優先度は低い

現状のプロセス数が少なければ効果はほぼ無い。 まず num_proc の実際の値を測ること。 5 個程度なら作戦 1 に比べて桁違いに効果が小さい。


作戦 5: static mut 59 箇所の排他監査【安定化の本命】

なぜ最重要か

過去に同じ構造の事故を 2 回起こしている(いずれも記録済み)。

事故 原因 対処後
SylFS のビットマップ破損(spec/resource_loading.md) 取得を 4 並列にしたとき get_fs() に排他が無かった。get_fs() 自身に「他コアから触るなら先に排他を検討せよ」と申し送りが書かれていたのに見落とした FS_LOCK 追加で [CACHE] 異常 0 件・SylFS エラー 0 件
DNS 応答が届かない(spec/l1_l2_usb.md) poll_ethernet_data_plane_only() が USB_LOCK を取らず、Core 1/Core 2 が同じ DWC2 レジスタを同時操作 ロック追加のみで 8/8 成功(100%)

後者は「DNS のバグ」「TCP のバグ」として長期間追っていたが、 真因は排他の欠落だった。輻輳制御の実装に着手しかけていた。

F-7 のとおり static mut は 59 箇所あるが、 どれが複数コアから触られるかの一覧が存在しない。 同種の事故が再発する構造。

提案する手順

  1. static mut 全 59 箇所を棚卸しし、spec/shared_state.md を新設して表で管理

    変数名 所有サブシステム 触るコア 現在の排他 必要な排他
  2. 「単一コアからのみ」と判断したものはその根拠をコメントで明記する (後から並列化する人が判断できるように)

  3. 複数コアの可能性があるものは RecoverableMutex(F-10)で保護

  4. 新規に他コアからアクセスを追加する際のチェックリストを .agents/AGENTS.md へ追記

不変条件


作戦 6: 例外ハンドラの情報充実【安定化】

現状

main.rs:2426 に #[panic_handler] はあるが、 同期例外(Data Abort / Instruction Abort)発生時の詳細が薄い。

提案

例外発生時に以下を必ず出す。

レジスタ 内容
ESR_EL1 例外の種類・DFSC(フォルト状態コード)
FAR_EL1 アクセスしようとした仮想アドレス
ELR_EL1 例外を起こした命令のアドレス
— コア ID・プロセス名(get_current_process_name() が既にある)

根拠

spec/resource_loading.md の F-2:

「保存に失敗」「読み返せない」のような症状のログだけでは原因に届かない。 read_file が内部で持っていた extent list unreadable という具体的な理由が決め手になった。 握り潰さずに理由を残しておくと、後から効く。

同じことが例外にも当てはまる。ESR_EL1 の DFSC を出すだけで 「権限違反か、未マップか、アラインメント違反か」が即座に切り分けられる。

spec/logging_policy.md の方針(L-1/L-3)に沿って、 1 例外 1 回の出力に留めること(ログで埋めない)。


優先順位

順位 作戦 期待効果 リスク 工数 前提
1 計測カウンタの追加 — なし 小 —
2 1-a: 同一 PT なら TLBI スキップ 中〜大 極小 小 順位 1
3 1-b: カーネルスレッドの PT 共有 大 小 中 順位 2
4 5: static mut 棚卸し 安定性大 なし(調査のみ) 中 —
5 6: 例外ログ充実 調査効率大 なし 小 —
6 2: MMU 1GB ブロック化 中 中(FB 属性注意) 中 順位 1 の結果次第
7 3: tickless idle 中 中 大 時刻の cntpct 移行完了
8 4: スケジューラ O(1) 化 要実測 小 中 num_proc の実測

順位 1(計測)を飛ばしてはならない。 L7 側の最適化で「5 つの仮説のうち当たったのは 1 つ」だった実績がある。


測定基盤の不足(着手前に埋めるべき穴)

L7 側には [PERF] 系のログが揃っているが、カーネル側には無い。 最低限これらが必要。

指標 用途
コンテキストスイッチ回数 作戦 1 の基準値
うち実際に PT が変わった回数 1-a / 1-b の効果見積り
tlbi 発行回数 同上
プロセス数(num_proc)の分布 作戦 4 の必要性判断
割り込み処理の所要時間分布 作戦 3 の判断

計測時の注意(実績のある落とし穴)

spec/resource_loading.md に記録済み:

get_system_time_us はノードごとに呼ぶと MMIO 読み出しのコストで 計測対象が歪む(walk が 550 → 850 tick に増えた)。 細粒度の計測には非侵襲な指標(回数カウンタ)を使うこと。

コンテキストスイッチは高頻度なので、時刻取得を挟むと計測自体が対象を歪める。 まず回数カウンタ(アトミックのインクリメントのみ)で測ること。

さらに [PERF] ログを増やすとターミナルが dirty になり 全画面再描画を誘発する(同文書「計測が計測対象を汚染している可能性」)。 カウンタは貯めて、集計時に 1 回だけ出す。


未着手・調査段階の項目