【メモ】UNIX V6コードリーディング(割り込み, p154〜159, その2)
今回の内容
前回の続きから.
アセンブリで書かれたtrap
とcall
を詳しく見ていきます.
前回解決できなかった疑問点
nofault
が設定されている場合の割り込み箇所への復帰方法について, 前回の記事には,
もう一つわからないことがあって, この後のトラップハンドラからどうやってユーザープログラムに復帰するのかということ.
なんてことを書いたのですが, そもそも前提が間違ってました.
nofault
を設定した上で命令を実行するのはカーネルプロセスでした.
このnofault
というのは, カーネルプロセスが例外が起きるとまずい命令を実行する前に設定するというものみたいです.
ここからは推測ですが, こういった命令を実行する直前にカーネルはnofault
にエラーハンドラを設定しカーネルスタックに戻り先のアドレスを積んだ状態で命令を実行するのではないかと.
すると, 例外が起きた場合には, スタックに戻り先のアドレスのみが積まれた状態でnofault
が指すアドレスにジャンプし, その処理の最後にrts
で戻り先に復帰するという風になるんじゃないでしょうか.
こっちの方はわかってないのですが, 読み進めていく上ではあまり問題なさそうなので一旦スルーします.
ここが若干謎で,
SR0
の1ビット目が1になるとMMUが有効になるらしい.
しかし, なぜこのタイミングでSR0
に1を格納しているのかがわからない.
trap
でnofault
が設定されていない場合
nofault
に値が入っていない場合は, 前回軽く書いた通りSR0
, SR2
を退避, SR0
を初期化してjsr
命令でcall1
へとジャンプします.
この時, 古いr0
の値がスタックに積まれ, r0
には_trap
(C言語で書かれたtrap
関数のアドレス)が格納されることに注意してください.
call1
にジャンプすると, sp
をひとつ上にずらし, プロセッサ優先度を0にし, call
の途中へとジャンプします.
ここからの処理は後述するcall
での処理とほぼ同じです.
call
の1行目を実行した直後とスタック, r0
の状態は同じ形になっている点に注意.
最終的なスタックの状態は以下のようになっており, r0
にはtrap
関数のアドレスが入っています.
値 | |
---|---|
sp |
トラップ種別を含んだPSW |
古いr0 |
|
割り込まれたプロセスのpc |
|
割り込まれたプロセスのPSW |
call
の処理
前々回ぐらいのおさらいになるのですが, call
に処理が移った時のスタックの状態は以下のようになっています.
値 | |
---|---|
sp |
古いr0 |
割り込まれたプロセスのpc |
|
割り込まれたプロセスのPSW |
ここにPSW
の値が積まれて以下のような状態になります.
値 | |
---|---|
sp |
PSW |
古いr0 |
|
割り込まれたプロセスのpc |
|
割り込まれたプロセスのPSW |
ここからはtrap
のnofault
が設定されていない場合と同じ処理になります.
スタックが同じような形をしているところに気をつけてください.
まず, 古いr1
がスタックに積まれます.
次にmfpi
命令を用いて割り込まれたプロセスのsp
をスタックに積みます.
mfpi
命令はPSWの前のモードの仮想アドレス空間から値を取得して現在のモードのスタックに積む命令です.
ここで得たsp
を用いて割込みハンドラの中でスタックを操作するのかな?と予想しています.
さらに, SPW
をスタックに積み, 下位5ビット以外を0でクリアします.
ここで, 割り込まれたプロセスがユーザーモードだったかカーネルモードだったかで処理が分岐するのですが, やっていることは大きくは変わらないです.
ユーザーモードだった場合は, jsr
命令で現在のpc
をスタックに積みながらr0
に設定された割り込みハンドラにジャンプします.
ここで積んだpc
が割り込みハンドラからの戻り先になります.
割り込みハンドラから処理が戻ると, コンテキストスイッチを行います.
割り込みによってプロセスの実行可能状態や優先度が変化した分を考慮するためのものかなと考えています.
最後に, mtpi
命令で前モードのsp
の値を戻します.
カーネルモードだった場合は, PSW
の前モードをユーザーモードに書き換えてから, 割み込みハンドラへジャンプします.
ここは上記と同様にjsr
を用いています.
割り込みハンドラが呼び出される時のスタックは以下のような状態になります.
実際にはこれがハンドラ関数の引数として扱われます.
具体的にどのように使われるのかはclock
, trap
などの関数を見ていくことになるかと思います.
値 | |
---|---|
sp |
pc(割り込みハンドラから返る先) |
マスクされたPSW |
|
割り込まれたプロセスのsp |
|
古いr1 |
|
PSW |
|
古いr0 |
|
割り込まれたプロセスのpc |
|
割り込まれたプロセスのPSW |
ここからは両モード共通です.
スタックに積んでいたr1
, r0
を復帰してrtt
命令で割り込まれた箇所に復帰します.
まとめ
ソースコードを読むだけだと理解しづらいので, 簡単にまとめておきます.
- カーネルは例外が起こりうる命令を実行する前に
nofault
を設定することがある. nofault
が設定されているトラップは設定されたハンドラによって処理される.- そうでないトラップは
trap
関数によって処理される. - 割り込みはそれぞれの割り込みハンドラによって処理される.
次回の予定
割り込みハンドラ(clock
)とトラップハンドラ(trap
)を読んでいく予定.