最終更新: 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 は安定化の道具として完成しているので、適用範囲を広げるだけでよい。
mmu::get_kernel_page_table()
を新設。カーネルスレッド生成(spawn_process
等)時に毎回行われていた
12KB(PageTables)のヒープ動的確保およびコピーを完全撤廃し、同一ページテーブルを共有。scheduler::schedule() において、切り替え先の
new_pt と現在の ttbr0_el1 を比較。cur_pt != new_pt の時のみ msr ttbr0_el1,
tlbi vmalle1is, dsb sy
を実行するように最適化。tlbi vmalle1is ブロードキャスト停止)。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 を残すハイブリッド構成にする。
.text は 0〜4MB
に収める前提がある(spec/README.md「メモリ管理」・
mmu.rs:207-213)。1GB
ブロック化でこの前提を壊さないことTLB ミス率の低下は原理的に期待できるが、実際にどれだけ効くかは未知。 現状 TLB ミスが支配的である証拠はまだ無い。作戦 1 の計測でスイッチ頻度が 低いと分かれば、この作戦の優先度は下がる。
.text
に対する読み取り専用/実行保護の粒度も 2MB 単位が必須であり、1GB
ブロック化は安全面・表示品質面から適用不可・現状の 2MB
ブロック維持が最適と確定。tlbi が 100% 抑制されているため、TLB
キャッシュヒット率は既に最大化されている。現状の 2MB
ブロック恒等マップが最も安全かつ最適な構造である。F-5 のとおり 10ms
固定、全コアで発火。interrupt.rs:104-112 で毎 tick:
if timer_fired {
scheduler::decrement_wait_ticks();
scheduler::scheduler_tick();
}
if scheduler::should_preempt() {
scheduler::schedule();
}register_idle_process(scheduler.rs:1110)が
あるので、idle 実行中は次の sleep
期限までタイマ間隔を延ばすtimer.rs:86 に「SYSTEM_TICKS は『1 tick ≈
10ms』という消費側の前提を保つため」
と明記されている。間隔を可変にするとこの前提が壊れる。
したがって着手順序は:
get_system_time_ms()(cntpct_el0
直読)へ完全移行するSYSTEM_TICKS に依存する箇所を全部洗い出して潰す順序を逆にすると時刻がずれ、sleep
やタイムアウトが壊れる。 spec/resource_loading.md の
R-8(依存順序を確かめてから着手する)に該当する。
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 全員に書き込み
}
}starvation_count += 1 が Ready
全プロセスのキャッシュラインを dirty にする。
読むだけなら共有できるが、書くと他コアのキャッシュを無効化する現状のプロセス数が少なければ効果はほぼ無い。 まず
num_proc の実際の値を測ること。 5 個程度なら作戦 1
に比べて桁違いに効果が小さい。
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 箇所あるが、
どれが複数コアから触られるかの一覧が存在しない。
同種の事故が再発する構造。
static mut 全 59
箇所を棚卸しし、spec/shared_state.md を新設して表で管理
| 変数名 | 所有サブシステム | 触るコア | 現在の排他 | 必要な排他 |
|---|
「単一コアからのみ」と判断したものはその根拠をコメントで明記する (後から並列化する人が判断できるように)
複数コアの可能性があるものは
RecoverableMutex(F-10)で保護
新規に他コアからアクセスを追加する際のチェックリストを
.agents/AGENTS.md へ追記
spec/resource_loading.md で cache.rs
だけ塞いで他に 5 箇所残っていた実例あり)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 回だけ出す。
num_proc
の実測次第で不要になる可能性がある