sleepを使ったときに指定時間の間にどのような動きをしているのか、なんだか分からなくなってしまったので整理してみたいと思います。
ここではカーネルソースが手に入るLinuxをベースに考えてみたいと思います。
プロセスレベルで見るsleep
sleepを実行すると最終的にはnanosleep()が呼ばれます。nanosleepの中では
- スリープタイマーの初期化
- カレントプロセスを待ち状態に変更
- スリープタイマー開始
- カレントプロセスの実行権を手放す
という処理が行われます。
指定時間が経過してスリープタイマーが切れたときには、プロセスが実行可能状態(TASK_RUNNING)になりrun queueにキューイングされて実行権が渡ってくるのを待ちます。そのうちスケジューラがrun queueからプロセスを取り出して「カレントプロセスの実行権を手放す」直後からプロセスが再開されます。
プロセスレベルで見ればsleepを実行すると指定した時間だけ実行権を手放してCPUを使わないようになります。
カーネルレベルでみるsleep
ではスリープタイマーはどのように実装されているのでしょうか。
Linuxの場合、スリープタイマーは動的タイマーという仕組みを使っており、このタイマーの起動にはソフト割り込みが使われています。
では、このソフト割り込み自体はどのように行われるのでしょうか。それはPIT(Programmable Interval Timer)やその後継であるHPET(High Precision Event Timer)と呼ばれるハードからのIRQ 0によるハードウェア割り込みにより行われます。
当然、Windows VistaのデバイスマネージャでもIRQ 0はシステムタイマになっています。
- ハードウェアからのIRQ 0により割り込み(4msなどの単位で常に定期的)発生
- IRQ 0によりハードウェア割り込みが発生
- ハードウェア割り込みの中でソフトウェア割り込みを要求
- ソフトウェア割り込みで動的タイマ処理が行われる
sleepを実行しているプロセスがなくても1~3は常に行われています。つまり、OSがまったく他の処理をしていないとき(現実にはありえないけど)でも1~3の処理は例えば4ms単位で発生しています。つねに行われているのならば、sleepしたために「負荷が増えた」という事にはならないといえます。
では動的タイマ処理はどうでしょうか。詳細は省きますがどこかのプロセスでsleepが実行されると動的タイマ処理のリストに加えられるためリスト走査時間が若干延びます。しかし、動的タイマの処理で処理負荷が上がるのは得策ではないためデータ構造の工夫や遅延処理により処理負荷の上昇が最小限度になるように実装されています。きっと、このあたりもOS実装の腕の見せ所なのかも知れません。
以上のように、厳密に言えばsleepを実行することによりカーネルレベルでみれば若干処理が増えますがそれはカーネルレベルで見ても無視してよい(無視できる実装じゃないとOS的に問題)ものだといえるでしょう。
※O’REILLY Japanの「詳細Linuxカーネル」の260ページ以降あたりを参考
sleepを使ったときに指定時間の間にどのような動きをしている?
指定時間の間、該当プロセスはCPUを使わず、またsleep実行中もOSの処理負荷は無視できる程度。