0x19fの日報

なるべく毎日書きます

【メモ】UNIX V6コードリーディング(クロック割り込み, p160〜170)

今回の内容

前回までは, 割り込みとトラップが発生してからハンドラが実行されるまでの流れを追っていました.

0x19f.hatenablog.com

今回は, クロック割り込みハンドラのお話です. クロック割り込みによって処理される指定時間実行とsleepシステムコールも一緒に見ていきます.

クロック装置

UNIX V6では以下の2種類のクロックが使用可能です.

前者のクロックは電源周波数が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秒毎の処理

以下, いくつか数字を振ったものについて補足しておきます.

(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関数の中で起こされます.

次回の予定

トラップハンドラの中身を読み進めていきたいと思います.