【メモ】UNIX V6コードリーディング(クロック割り込み, p160〜170)
今回の内容
前回までは, 割り込みとトラップが発生してからハンドラが実行されるまでの流れを追っていました.
今回は, クロック割り込みハンドラのお話です. クロック割り込みによって処理される指定時間実行とsleepシステムコールも一緒に見ていきます.
クロック装置
UNIX V6では以下の2種類のクロックが使用可能です.
- 電源周波数に従うクロック: KW11-L
- プログラマブルクロック: KW11-P
前者のクロックは電源周波数が50Hzか60Hzかによってクロック周波数が変わります. 後者のクロックの間隔などを設定することができるようです.
クロック周波数はparams.hの中でHZ
としてdefineされているようです.
書籍ではどちらの装置を用いるにしてもクロック周波数が60Hzであるという前提で解説を進めています.
クロック割り込みハンドラ
クロック割り込みハンドラはC言語で書かれたclock
関数になります.
この関数に前回まで見てきた箇所で積まれたスタックの中身が引数として渡ります.
C言語で書かれている関数は, コンパイルの際に先頭にcsv
というアセンブリで書かれた関数の実行が挿入されます.
簡単に説明だけしておくと, r2
, r3
, r4
を退避する関数です.
このcsv
については, 書籍の78ページから詳しく説明されています.
(このメモを書き始める前に読んでます.)
csvが実行された直後のスタックの状態とclock
関数の引数の対応は以下のようになります.
引数の内容がわからなくなったらここを見ましょう.
値 | 引数 | 備考 | |
---|---|---|---|
sp |
pc |
csv から戻るのに使用されるだけ(あまり関係ない) |
|
退避されたr2 |
|||
退避されたr3 |
|||
退避されたr4 |
|||
r5 |
退避されたr5 |
||
pc(割り込みハンドラから返る先) | |||
マスクされたPSW |
dev |
||
割り込まれたプロセスのsp |
sp |
||
古いr1 |
r1 |
||
PSW |
nps |
||
古いr0 |
r0 |
||
割り込まれたプロセスのpc |
pc |
||
割り込まれたプロセスのPSW |
ps |
クロック割り込みハンドラの中身を読んでいくと以下の処理が上から順にされていることがわかります.
- クロック毎の処理
- クロック装置のレジスタを再設定
- 指定時間実行の処理を行う ... (1)
- CPU時間のインクリメント
- 1秒毎の処理
- 時刻のインクリメント
- sleepしているプロセスを起こす ... (2)
- プロセスの実行優先度を再計算 ... (3)
- スワップ処理を継続できない場合の再スケジューリング ... (4)
- シグナル処理 ... (5)
- 4秒毎の処理
- lightning boltの処理 ... (6)
以下, いくつか数字を振ったものについて補足しておきます.
(1) 指定時間実行
これについては, 書籍の162ページから詳しく書かれています.
指定時間に実行したい関数を登録するtimeout
関数などのソースコードが出てきますが, 書いてある通りに読めば難しい箇所はないと思います.
クロック割り込みハンドラの中では, 指定時間になった関数の実行をしています.
(2) sleepしているプロセスを起こす
書籍の165ページにsleepシステムコールのハンドラであるsslep
が掲載されているのですが, これだけでは少し何が起きているのかわからないのでメモをば.
まず, time
はグローバル変数で現在の時刻を秒単位で管理しています.
いわゆるUNIX時間(?).
この時刻データは, 2 word分(32 bit)に収められています.
dpadd
, dpcmp
はこの2 wordをまとめて足したり比較したりするための関数です.
書籍の441ページに説明があります.
もう一つ関数の中に出てくるグローバル変数に, tout
という時刻データがあります.
これは, 次にsleepから起こされる関数が起こされる時刻を表しています.
個人的にここが今回得られた理解の中で一番おもしろかったのですが, sleepしているプロセスはtout
が示す時刻を過ぎたらすべて起こされます.
そして, 指定された時刻を過ぎていないプロセスは再びsleep状態に戻ります.
これを実現しているのがsslep
関数中のwhile文です.
while文の条件が真になるのは, 指定された秒数分sleepした場合のみです.
真になるまでは, if文によって直近に起こさないといけないプロセスを起こす時間をtout
に格納して, sleep
関数を呼び出しています.
sleep
関数については, 73ページにソースコードが掲載されています.
sleep
関数の第一引数には待っている資源のアドレスを渡すのですが, ここではtout
のアドレスを渡しています.
clock
関数の中でwakeup
関数にtout
を渡して, すべてのsleep中のプロセスを起こしています.
起こされたプロセスはwhile文の最後から処理を再開し, while文の継続条件を判定してsslep
関数から抜けるもしくは再度sleep
関数を呼び出します.
このようにしてsleepシステムコールは実現されているわけですが, これって何かに似てるなーと思ったらJavaのマルチスレッドプログラミングで出てくるwait/notifyAllと同じようなもんかなという気持ちになりました. あれもnotifyAllですべてのプロセスが起きた時にwhile文でガードするってことをしますよね.
(3) プロセスの実行優先度を再計算
プロセスのあたりは一応読んだはずなのですが, 細かいところはかなり忘れているので再度読み直します...
(4) スワップを継続できない場合の再スケジューリング
スワップ辺りを飛ばしているので, あまりよくわかってないです...
(5) シグナル処理
シグナルの話は次の章に出てくるので一旦飛ばす.
(6) lightning bolt
lightning boltというのは, カーネルが特に待ち時間は決まっていないけれど適当に待たせておきたい時に使用するものらしいです.
lbolt変数を待ち資源にしてsleepさせておくんでしょうか?
4秒に1度clock
関数の中で起こされます.
次回の予定
トラップハンドラの中身を読み進めていきたいと思います.