【メモ】UNIX V6コードリーディング(トラップハンドラ/システムコール, p171〜184)
今回の内容
前回は, 主にタイマー割り込みに関連する箇所を読んでいました.
今回は, トラップハンドラでの処理を見ていきます. システムコールについてもこの辺りに書かれています.
トラップハンドラtrap
トラップハンドラも割り込みハンドラ同様に呼び出しが行われcsvが実行された後のスタックの状態は以下のようになります.
値 | 引数 | 備考 | |
---|---|---|---|
sp |
pc |
csv から戻るのに使用されるだけ(あまり関係ない) |
|
退避されたr2 |
|||
退避されたr3 |
|||
退避されたr4 |
|||
r5 |
退避されたr5 |
||
pc(割り込みハンドラから返る先) | |||
マスクされたPSW |
dev |
||
割り込まれたプロセスのsp |
sp |
||
古いr1 |
r1 |
||
PSW |
nps |
||
古いr0 |
r0 |
||
割り込まれたプロセスのpc |
pc |
||
割り込まれたプロセスのPSW |
ps |
trap
関数の9行目でu.u_ar0
に引数r0
のアドレスを格納していますが, これはスタック上のアドレスになります.
実際にスタック上の値を操作する際はu.u_ar0[R0]
のようにして操作するのですが,
R0
はただの定数でスタック上のr0
が格納されている位置からのオフセットです.
同様にしてR0
〜R7
とRPS
(PSWの操作に用いる)を使ってスタック上の値を書き換えています.
トラップハンドラ内ではあまりこの操作は行われませんが, システムコールハンドラ内で行われるのでしょうか?
その後のswitch分でdev(トラップ種別)の値によって処理が分岐していきますが, 基本的にはシグナルを送って処理は終わりです. 例外はillegal instruction, floating exception(カーネルプロセス), segmentation exception, システムコールの時です.
illegal instructionの時はSETD
なる謎の命令か否かをチェックしているようですが, 正直なんのためのものなのかあまりわかってないです.
カーネルプロセスでは浮動小数点演算を行わないらしいのですが, PDP11の特性上ユーザープロセスが行った浮動小数点演算によるfloating exceptionがトラップ発生後に引き起こされることがあるらしく, その場合はそのユーザープロセスにシグナルを送って関数から抜けます.
segmentation exceptionの場合, スタックを拡張します.
それでもexceptionを回避できない場合は, プロセスにシグナルを送ります.
このスタックの拡張はgrow
という関数の中で行われるのですが, 仮想メモリ空間の話とかをあまりちゃんと理解していないので一旦飛ばします.
ちなみに, grow
の前にbackup
という関数が実行されているのですが, この関数はUNIX V6の中で最難関らしいです.
exceptionが起きた時の状態を再現する関数らしいのですが, すべてアセンブリで書かれておりおまけに中でPDP11にはないレジスタの挙動をエミュレートしているようです.
実際にソースコードを読むと/ hard part
というコメントが見つけられます.
最難関なのでは仕方ないので飛ばします.
システムコールの場合の処理の前にシステムコールの処理の流れをまとめたいと思います.
システムコールの処理の流れ
システムコールハンドラはsysent構造体によって管理されます. この構造体にはシステムコールの引数の数とハンドラのアドレスが格納されます.
sysent構造体の配列sysentに各システムコールの引数の数とハンドラのアドレスが格納されています. この配列は要素数が64で, 添字はsys命令のオペランド(6 bit)と対応しています.
システムコールの0番は間接呼び出しに割り当てられています. 間接呼び出しというものを自分は初めて知ったのですが, どうやら普通の呼び出しと異なるのは引数の渡し方のようです. 通常の呼び出しは, sys命令の後に引数が埋め込まれます. 一方で, 間接呼び出しの場合は, sys命令の後に引数のアドレスが埋め込まれます. これは推測なのですが, 直接命令の場合は引数が埋め込まれる場所がテキストセグメントになるため, 動的にシステムコールの引数が変わる場合に困ってしまうのでこのような仕組みが用意されているのではないかと考えています. sys命令の次にデータ領域のアドレスを埋め込んでおけば, 複数のプロセスでテキストセグメントを共有しつつ個別に引数を扱うことができます. あるいは, 引数の数が動的に変わる場合などでしょうか.
システムコールの引数は上述のようなsys命令の後に埋め込むものと, r0
を経由するものがあります.
トラップハンドラ内でのシステムコールの処理
話はトラップハンドラのswitch文に戻ってくるのですが, システムコールの場合の処理を見ていきます.
基本的にはエラーフラグを初期化, 間接呼び出し/直接呼び出しそれぞれの場合で引数とシステムコールの番号を取得, trap1
を呼び出してシステムコールハンドラを実行, エラーフラグの処理という流れです.
特筆すべきなのがtrap1
を用いてハンドラを呼び出している点です.
trap1
の中を見てみるとわかるのですが, フラグを立ててからsavu
してシステムコールハンドラを呼び出しています.
これが意味するところなのですが, これはシステムコール中にシグナルを受け取った場合にエラーを起こすためのものらしいです.
詳しくはシグナル処理のあたりで書かれています.
このsavu
に対応するaretu
はsleep
関数の末尾にあります.
このエラーはシステムコールハンドラがsleep
を実行して処理を中断している間にシグナルを受け取った場合に起こるものらしいです.
シグナルを受け取った場合は, trap1
が返る場所から処理が継続されます.
この場合は, フラグが立ったまま処理が続行されるため, その後のtrap
での処理でユーザープロセスにはエラーが返ります.
このエラーを見てユーザープロセスは再度システムコールを実行することができるようになっています.
まとめ
ということで割り込みとトラップを見てきましたが, なかなか複雑だなぁという感想です. もう一度プロセス管理あたりも読み直してみると納得感が増すのかなぁと思います.
次回の予定
シグナル?メモリ管理? 未定です