処理に何秒かかったかを調べるため、開始と終了の時刻を引き算したら、負の値になった。これは計算の誤りとは限りません。現在の日時を表す時計は、途中で合わせ直されることがあるからです。Linuxでは、日時、経過時間、サスペンドを含む時間を用途に応じて分けられます。本稿ではLinux man-pages 6.19を参照し、CLOCK_REALTIME、CLOCK_MONOTONIC、CLOCK_BOOTTIMEの違いと、期限を決める際の読み方を整理します。[1][2]
簡単にまとめると?
- 現在の日時を表すCLOCK_REALTIMEは、時刻の設定によって前後へ飛ぶことがあります。二つの値の差が、そのまま処理の経過時間になるとは限りません。
- CLOCK_MONOTONICは不連続な時刻変更で後戻りしません。ただし、Linuxではサスペンド中の時間を数えず、周波数の補正は受けます。
- サスペンド中も期限までの時間へ含めるなら、CLOCK_BOOTTIMEとの違いを確認します。時間を数えることと、眠った機器を起こすことは別です。
- 時計の種類と、相対待機・絶対期限の指定は別の選択です。使うAPIの仕様まで揃えて、日時と経過時間を扱います。
日時を残す時計と、経過を測る時計
CLOCK_REALTIMEは、利用者が見る日時に対応するシステム全体の時計です。ログに「いつ起きたか」を残したり、外部から与えられた日時と照合したりする用途に関係します。この時計は設定可能で、管理者による変更などで値が不連続に変わるほか、時刻同期に伴う周波数補正の影響も受けます。[1、DESCRIPTION]
一方、CLOCK_MONOTONICは、ある起点からの単調な時間を表します。同じ時計を続けて読んだ値が後戻りしないため、日時の変更と切り離して経過を測る際に使えます。ただし、続けて読むと必ず異なる値が得られるという意味ではありません。分解能などによって、二回の読取りが同じ値になることもあります。[1]
重要なのは、開始と終了で同じ種類の時計を使うことです。日時を記録する値と、経過時間を計算する値を別々に保存しても構いません。違う起点を持つ値を、単位がどちらも秒だからという理由で引き算したり、別マシンや別の起動で読んだ単調時計の値をそのまま共通の日時として比較したりしないようにします。
時計が20秒戻ると、5秒の処理はどう見えるか
独自の合成モデルで、実際には5秒進む処理を考えます。開始時の日時時計を説明用に100秒とし、2秒経過した瞬間に20秒だけ後ろへ設定し直したとします。終了時の値は100+5−20で85秒です。終了値から開始値を引くと−15秒になります。同じモデルの単調時計では、開始からの差は5秒です。
期限判定にも違いが現れます。「開始時の日時時計+5秒」、つまり105秒へ達したら終了するという比較を繰り返す場合、20秒の後戻り後は、開始から25秒進むまで105へ到達しません。反対に、2秒経過した時点で20秒前へ飛ばせば、日時時計は122となり、その時点で105という期限を過ぎます。

この例は、自分で日時時計を読みながら絶対期限と比較する場合の説明です。「REALTIMEを使う待機はすべて時刻変更で延び縮みする」と一般化してはいけません。Linuxのclock_nanosleepでは、REALTIMEを使っていても、相対時間で待機しているスレッドは、その時計の設定変更では影響を受けないと仕様に記されています。影響するのは、どの時計かだけでなく、何を期限として渡したかです。[2、NOTES]
サスペンドの10秒を数えるかどうか
LinuxのCLOCK_MONOTONICは、システムがサスペンドしていた時間を含みません。2秒動いた後、10秒サスペンドし、その後3秒動いた単純なモデルなら、この時計の増加は5秒です。CLOCK_BOOTTIMEは同じ状況でサスペンド時間も含むため、増加は15秒になります。[1]
たとえば、再開後も「機器が動いていた時間として、あと何秒待つか」を扱うのか、「眠っている間も期限の時間は進んだとみなすか」を扱うのかで、適切な条件が変わります。ただし、BOOTTIMEの値がサスペンド中も進むことは、それだけで機器を起こせるという意味ではありません。マニュアルにはALARMを伴う別の時計も区別されており、起床の仕組みはタイマーAPIや権限も含めた別の確認が必要です。本稿は起床設定を変更する手順を扱いません。
また、CLOCK_MONOTONICは不連続な日時変更を避けますが、周波数補正の影響まで受けない時計ではありません。Linux固有のCLOCK_MONOTONIC_RAWは、この補正を受けない生のハードウェアに基づく時間を提供します。[1] RAWという名前だけで、アプリケーションの期限管理に常に最適だとは判断できません。どの補正と経過を必要とするかを先に決めます。
絶対期限は、カレンダー上の日時とは限らない
clock_nanosleepには、現在から何秒という相対指定と、選んだ時計の値がどこへ達するまでという絶対指定があります。後者はTIMER_ABSTIMEで指定します。「絶対」という語が付いていても、CLOCK_MONOTONICを選べば、その時計の起点に基づく期限です。カレンダー上の日時を指定したことにはなりません。[2、DESCRIPTION]
周期処理でも、この違いが役立ちます。処理自体が毎回0.2秒かかり、その終了後に相対的に1秒眠るモデルでは、開始時刻は0、1.2、2.4、3.6秒とずれていきます。一方、基準時刻から1、2、3秒という期限を先に定め、各回の処理が次の期限に間に合うなら、この処理時間を毎回周期へ足さずに済みます。これは独自の時間足し算の例で、実機でその時刻に必ず実行できるという保証ではありません。
マニュアルは、絶対期限が相対待機の繰返しによるずれを避けるのに役立つと説明しています。また、シグナルで中断された絶対待機は、同じ期限を使って再度呼び出せます。ただし、戻り値を確認し、中断と正常終了を分ける必要があります。待機時間の丸めや、待機が終わってもCPUで再実行されるまでの遅れもあるため、ナノ秒単位で指定できることと、その精度で処理を開始できることは同じではありません。[2、NOTES]
評価するときは、時計・待ち方・実行遅れを分ける
負の所要時間や、周期のずれを見つけたら、まず記録した時計の種類を確認します。次に、差分を計算しているのか、相対待機なのか、絶対期限なのかを確認します。そのうえで、サスペンドを含めるか、周波数補正の影響をどう扱うか、期限後の実行遅れをどう記録するかを揃えます。
本稿の5秒・20秒の変更、サスペンド例、周期例はすべて合成計算です。利用者の機器の時計や電源設定は変更しておらず、Linux実機でのスケジューリング遅延も測っていません。ログの日時は出来事を共有するために残し、経過時間は適切な同一時計の差として測る。この二つを区別することで、時刻同期の調整と処理そのものの遅さを混同しにくくなります。
