Dev Study
OSと低レイヤのしくみ コース

3. RISC-Vのトラップ — 割り込みが起きてから戻るまでの一往復

トラップが起きてから mret で元の場所へ帰り着くまでの一往復を、CSRの値の動きとして図で追っていきましょう。

①トラップとは「実行の流れが横取りされる」こと

通常の実行と、トラップが割り込んだときの流れ
通常の実行命令を1つずつ順に実行
ここでトラップ発生(割り込み or 例外)
実行中の命令 Aこの地点で流れが止まる
ハードウェアが自動で退避 → 分岐
トラップハンドラmtvec が指すアドレス
mret
命令 A(または A の次)へ復帰止めた地点から再開
  • ・RISC-Vでは割り込み(非同期)と例外(同期)をまとめて「トラップ」と呼びます
  • ・退避と分岐はハードウェアが自動で行い、復元は mret 命令が行います

トラップは「止める・記録する・飛ぶ」と「戻す・帰る」の一往復です。この往復を成立させるために、どの情報をどこへ記録するかが設計の核心になります。

②ハードウェアが自動で書き込む3本のCSR

トラップ発生時にM-modeで自動更新されるレジスタ
トラップ発生ハードウェアが以下を同時に実施
自動書き込み
mepc止まった命令のアドレス
帰り先
mcause何が起きたか
原因コード
mstatusMIE / MPIE / MPP を更新
元の状態
pc を書き換え
pc ← mtvec が指す先ハンドラへ分岐
  • ・mepc は「中断された命令、または例外を起こした命令」の仮想アドレスを保持します
  • ・mepc の最下位ビットは常に0です。IALIGN=32 のみを実装する環境では下位2ビットが常に0になります

帰り先(mepc)、原因(mcause)、元の状態(mstatus)。この3つが揃って初めて「元通りに戻る」ことが可能になります。

③mstatus の中で起きている3つの退避

mstatus のビット配置と、トラップ時の書き換え
MPPbit 12:11
直前の特権モード
MPIEbit 7
直前の割り込み許可
MIEbit 3
現在の割り込み許可
M-mode へトラップした瞬間
MPP ← 元の特権モードU=00 / S=01 / M=11
MPIE ← 旧 MIE許可状態を退避
MIE ← 0多重割り込みを止める
  • ・仕様では「y モードから x モードへトラップしたとき xPIE には xIE が入り、xIE は 0 になり、xPP には y が入る」と定義されています
  • ・MPP が2ビットなのは、戻る先が U / S / M の3種類あり得るためです

mstatus は1本のレジスタで「特権モードの退避」と「割り込み許可の退避」を同時に済ませています。MIE=0 にするのは、ハンドラの入口で別の割り込みに再び横取りされないためです。

④mtvec が決める飛び先 — Direct と Vectored

mtvec の BASE と MODE、2つの分岐方式
mtvecBASE[上位ビット] + MODE[1:0]
BASEは4バイト境界以上
MODE の値で分岐方式が決まる
MODE = 0(Direct)全トラップが pc ← BASE
同じ入口
MODE = 1(Vectored)割り込みは pc ← BASE + 4×cause
原因ごとに別入口
到着
単一ハンドラmcause を読んで自分で振り分け
ジャンプテーブル4バイトごとに命令を並べる
  • ・Vectored モードで BASE+4×cause になるのは非同期の割り込みだけで、同期例外は BASE へ飛びます
  • ・BASE は最低でも4バイト境界ですが、Vectored では Direct より厳しい境界が要求される場合があります(加算器を省く実装を許すため)
  • ・例えばマシンタイマ割り込み(cause 7)では BASE+0x1c が飛び先になります

Direct はソフトウェアで振り分ける代わりに構成が単純、Vectored はハードウェアが振り分ける代わりにテーブルが必要です。どちらを選ぶかは応答速度とコードサイズのトレードオフになります。

⑤mcause の読み方 — 割り込みか例外か

mcause の最上位ビットが種別を分ける
mcauseInterrupt(最上位1ビット) + Exception Code(残り)
最上位ビットで判定
Interrupt = 1非同期 = 割り込み
外部要因
Interrupt = 0同期 = 例外
命令自身が原因
Exception Code の例
code 7マシンタイマ割り込み
Interrupt=1
code 2不正命令
code 3ブレークポイント
code 11M-mode からの ecall
  • ・環境呼び出し(ecall)の例外コードは、呼び出し元のモードで異なります(U-mode=8 / S-mode=9 / M-mode=11)
  • ・最上位ビットで種別を分けるため、1本のCSRを読むだけで判定と分類が同時にできます

ハンドラの最初の仕事は mcause を読むことです。最上位ビットで割り込みか例外かを分け、下位のコードで具体的な原因を特定します。

⑥mret — 一往復を閉じる復元処理

mret が実行する復元の内訳
mret 実行ハンドラの最後の命令
ハードウェアがまとめて復元
特権モード ← MPPMPP が示すモードへ戻る
MIE ← MPIE割り込み許可を復元
pc ← mepc止めた地点へ帰る
同時に後始末
MPIE ← 1次回に備えた既定値
MPP ← 最も権限の低い実装モードU があれば U、なければ M
  • ・仕様では「xPP が y のとき、特権モードを y にし、xIE に xPIE を入れ、xPIE を 1、xPP を最も権限の低い実装モードにする」と定義されています
  • ・復元される割り込み許可は xIE(mret を実行した M-mode 側のビット)であり、戻り先 y のビットではありません
  • ・MPP を最低権限へ落とすのは、書き忘れによって意図せず高い特権のまま復帰する事故を防ぐためです

mret は入口で退避した3つを一度に巻き戻します。②③で自動的に記録された値が、ここでちょうど対称に消費されるのがトラップ機構の設計です。

⑦なぜ mepc は「次の命令」ではなく「止まった命令」なのか

停止地点を記録する設計が、再実行と続行の両方を可能にする
mepc = 停止した命令そのもののアドレス「次の命令」ではない
同じ記録から、種別ごとに異なる復帰先を選べる
割り込みの場合その命令はまだ実行されていない
そのまま戻れば再実行
ecall の場合ecall 自体は処理済み
そのままだと無限ループ
ソフトウェアが mcause を見て決める
mepc のまま mret中断した命令を再実行
mepc を命令長だけ進めて mret次の命令へ続行
  • ・mepc には「中断された命令、または例外を起こした命令」のアドレスが入るため、ecall では ecall 命令自身を指します
  • ・進める幅はソフトウェアの責任です。mepc の指す命令の下位2ビットが 0b11 なら4バイト、そうでなければ圧縮命令なので2バイト進めます
  • ・ハードウェアが自動で進めないからこそ、再実行が必要なページフォルトなどにも同じ仕組みが使えます

ハードウェアが一律に「停止地点」だけを記録するため、再実行したい場合と先へ進めたい場合をソフトウェアが選べます。mepc の設計が、トラップ機構を割り込みと例外の両方に使い回せるものにしています。

公式ドキュメントで詳しく ↗