接続タイムアウト、サスペンド後の資格情報期限切れ、マシン間ログのソートに必要なものは、同じタイムラインではない。
参照目標 読書位置
クロックの選択とカウント変換 第 1~2 節
補正と時間スケール 第 3~4 節
ハードウェア同期、読み取り、および観測 後続の章
時間管理は異なる問題に答える必要がある:ログが何日に発生したか、操作が実際にどれくらい経過したか、システムのサスペンドがタイムアウトにカウントされるか、そして2台のマシンのタイムスタンプを比較できるか。クロックの選択は、まずこれらの意味論的な問題に答え、次に読み取りコストを考慮する。
1. 意味論に基づいたクロックの選択
クロック タイムラインと補正 サスペンド 典型的な用途
CLOCK_REALTIME カレンダー時間、ステップまたは漸進的な補正が可能 サスペンド時間を経過としてカウント ログ、人間の日期、カレンダーの期限
CLOCK_MONOTONIC カレンダークロックのジャンプの影響を受けないが、周波数の漸進的調整の影響を受ける カウントしない 単一マシンでのタイムアウトと間隔
CLOCK_MONOTONIC_RAW NTP などの周波数調整を適用しない カウントしない タイマー周波数/補正の影響分析
CLOCK_BOOTTIME MONOTONIC に似ているが、サスペンドも累積する カウントする サスペンドを跨いで期限切れになる期限が必要
CLOCK_TAI カーネル設定の TAI オフセットなどを使用して対応する時間スケールを形成 カウントする 正しく同期・設定された場合の特定の時間マーキング
PROCESS/THREAD_CPUTIME_ID 対応する CPU 実行時間を累積 スリープを実行としてカウントしない CPU 消費分析
MONOTONIC の単調性は通常、隣接する読み取りで等しくなることを許可するが、読み取るたびに厳密に増加することを保証するものではない。それは「NTP 調整の影響を受けない」ことを意味せず、さらにカウンターが永遠に周波数誤差を持たないことを意味するものでもない。RAW もまた、マシン間で比較可能な UTC ではなく、無条件で最も正確なビジネスクロックと呼べるものでもない。API の意味論は clock_gettime を参照。
2つの期限の例
接続確立には最大5秒待つ必要があるが、通常は MONOTONIC の期限切れを使用する必要がある。これは、管理者がカレンダークロックを1分戻した際に、さらに1分待つことにならないようにするためである。認証資格情報がコンピュータのサスペンド後も経過時間に基づいて期限切れになるべき場合、BOOTTIME または正しく同期された絶対期限を組み合わせて設計する必要がある。
MONOTONIC の値をディスクに保存し、再起動後に比較を続けることは通常意味をなさない。Aマシンの MONOTONIC を Bマシンの値から減算しても、ネットワークレイテンシは得られない。分散ログには、カレンダー時間、誤差評価、因果関係の識別など、共同での解釈が必要である。
ウェイクアップ時間を先に進め、その後サスペンドまたはジャンプする壁掛け時計を跨ぐ。以下では、「クロックが期限を過ぎたこと」と「コールバックが実行されたこと」を分離し、期限切れをその時点で実行中であると誤解しないようにする。
図を準備しています 再読み込み
同じ5秒の期限、サスペンドと補正は誰を変えるのか? MONOTONIC はサスペンドを累積しないが、BOOTTIME は累積する。REALTIME はさらにカレンダーのジャンプの影響を受ける。期限切れ条件の成立とコールバックの実行は異なる状態である。
{"id":"clock-deadline-semantics","title":"同じ5秒の期限、サスペンドと補正は誰を変えるのか?","summary":"MONOTONIC はサスペンドを累積しないが、BOOTTIME は累積する。REALTIME はさらにカレンダーのジャンプの影響を受ける。期限切れ条件の成立とコールバックの実行は異なる状態である。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"actions\"><button id=\"awake\" data-i18n=\"awake\"></button><button id=\"suspend\" data-i18n=\"suspend\"></button></div><div class=\"control\"><label for=\"step\" data-i18n=\"step\"></label><output for=\"step\" id=\"step-value\"></output><input id=\"step\" type=\"range\" min=\"-10\" max=\"10\" value=\"-4\" step=\"1\"></div><div class=\"actions\"><button id=\"apply\" data-i18n=\"apply\"></button></div><div class=\"clock\"><header>MONOTONIC<strong id=\"now-M\"></strong></header><div class=\"deadline\"><i id=\"bar-M\"></i></div><div class=\"clock-result\"><span id=\"remaining-M\"></span><span id=\"status-M\"></span></div></div><div class=\"clock\"><header>BOOTTIME<strong id=\"now-B\"></strong></header><div class=\"deadline\"><i id=\"bar-B\"></i></div><div class=\"clock-result\"><span id=\"remaining-B\"></span><span id=\"status-B\"></span></div></div><div class=\"clock\"><header>REALTIME<strong id=\"now-R\"></strong></header><div class=\"deadline\"><i id=\"bar-R\"></i></div><div class=\"clock-result\"><span id=\"remaining-R\"></span><span id=\"status-R\"></span></div></div><div class=\"actions\"><button id=\"dispatch\" data-i18n=\"dispatch\"></button></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"note\"></p></div><div class=\"actions\"><button id=\"reset\" data-i18n=\"reset\"></button></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n.clock{margin:18px 0;padding:14px;border:1px solid var(--rule);border-radius:8px}.clock header{display:flex;justify-content:space-between;gap:8px;font:12px var(--viz-font-ui)}.clock header strong{font-size:16px}.deadline{height:20px;width:100%;position:relative;background:var(--surface);border-right:2px solid var(--second);margin:12px 0}.deadline i{height:100%;display:block;background:var(--accent);transition:width .25s}.clock-result{display:grid;grid-template-columns:1fr 1fr;gap:8px;min-height:5em;font-size:12px}.clock-result span:last-child{text-align:right;color:var(--muted)}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}let now=[0,0,1000],fired=[false,false,false];const due=[5,5,1005],keys=['M','B','R'];function draw(){set('step-value',$('#step').value+' s');keys.forEach((k,i)=>{set('now-'+k,now[i]+' s');set('remaining-'+k,t('remaining')+'\\n'+(due[i]-now[i]));set('status-'+k,t(fired[i]?'fired':now[i]>=due[i]?'due':'waiting'));$('#bar-'+k).style.width=Math.max(0,Math.min(100,(now[i]-(i===2?1000:0))/5*100))+'%';});$('#dispatch').disabled=!now.some((v,i)=>v>=due[i]&&!fired[i]);set('explanation',t('note'));}$('#awake').onclick=()=>{now=now.map(v=>v+2);draw();};$('#suspend').onclick=()=>{now[1]+=4;now[2]+=4;draw();};$('#apply').onclick=()=>{now[2]+=+$('#step').value;draw();};$('#dispatch').onclick=()=>{fired=fired.map((v,i)=>v||now[i]>=due[i]);draw();};$('#step').oninput=draw;$('#reset').onclick=()=>{now=[0,0,1000];fired=[false,false,false];draw();};draw();","audio":false,"strings":{"apply":"今回の補正を適用","awake":"2秒間実行","dispatch":"期限切れのコールバックを処理","due":"期限切れ、未処理","fired":"コールバック実行済み","heading":"同じ5秒の期限、サスペンドと補正は誰を変えるのか?","intro":"3つのクロックそれぞれに「現在値 + 5秒」の絶対期限を1回設定する。実行時間を進め、サスペンドを通過するか、カレンダー時間を調整し、最後に期限切れとなったコールバックを明示的に処理する。","note":"縦線は元の期限を表す。進行状況は期限までしか描画されず、数字は符号付きの残り秒数を保持する。期限を過ぎた後もコールバックの処理は必要であり、システムのサスペンドを CPU が実行中であるかのように偽装しない。","remaining":"元の期限からの残り / 秒","reset":"リセット","scope":"クロックが M=0、B=0、R=1000 秒から始まることを示し、期限はそれぞれ 5、5、1005 に固定される。サスペンド操作は直接ウェイクアップ後の状態を返し、コールバックは実行されない。NTP の漸進的調整、アラームウェイクアップ機能、TAI、timerfd CANCEL_ON_SET は省略されている。実行されたコールバックは、巻き戻しによっても再度実行されない。","scopeLabel":"モデル範囲と規約","step":"カレンダークロックの調整 / 秒","suspend":"4秒間のサスペンド後にウェイクアップ","waiting":"待機中"}}
2. Timekeeping はどのようにカウントを時間に変換するか
同じ経過時間は、異なる時間スケールにマッピングできる
Clocksource カウント差
周波数、マスク、および変換
Timekeeping
基準、補正、サスペンドなどのオフセットを維持
MONOTONIC
巻き戻さない;サスペンドを含まない
BOOTTIME
前者に基づいてサスペンドをカウント
REALTIME / TAI
カレンダー/国際時間スケール
RAW は、適用された周波数補正を施していないタイムラインを保持する;時間スケールと読み取り実装は別物である。
最近更新されたカウントと基準時間を維持し、新しいカウントを読み取った後に経過量を計算する。簡略化された整数変換は以下の通りである:
delta_ns ≈ ((cycles_now - cycles_last) & mask) × mult >> shift
mask は有限幅のカウンターラップを処理し、mult/shift は高価な計算を減らす。ただし、実際の処理系では中間のオーバーフローを回避し、異なる基準と補正を処理する必要があり、この式を完全なドライバコードとして扱うことはできない。周波数の維持は、ラップを区別するのに十分でなければならず、無限に更新しないことはできない。
時間の読み取りは、次の tick を待つ必要がない。現在のカウント差から、基準以降の経過時間を推定できる。timekeeper の実際の構造はバージョンによって変化し、それを維持する更新パスは「すべての hrtimer 割り込みがすべてのクロックを完全に再計算する」ものではない。下層の責任は timekeeping を参照。
なぜ一貫性のあるスナップショットが必要なのか
基準、mult、shift などのフィールドは共同で解釈される必要がある。更新前の基準と更新後の変換パラメータを読み取った場合、結果は誤っている可能性がある。したがって、読み取りパスはシーケンス検証などのメカニズムを使用し、並列更新が発見されると再試行する。これは、カウンターを1回読み取るだけで完了するものではない。
3. NTP:時間サーバー、ユーザーランド制御、およびカーネル補正
chronyd/ntpd などのユーザーランドサービスはネットワーク時間ソースと推定を処理し、カーネルは adjtimex/clock_adjtime などのインターフェースを通じて対応する周波数と位相の調整を実行する。カーネルの ntp.c は、独立してネットワークに接続された「NTP デーモン」ではない。
補正方法 タイムラインの変化 測定への影響
step カレンダー時間を直接変更 REALTIME はジャンプする可能性があるが、MONOTONIC は通常巻き戻らない
slew 速度を調整し、誤差を段階的に解消 MONOTONIC は対応する周波数の漸進的変化を反映する
RAW と MONOTONIC の長期差の変化は補正の手がかりを提供できるが、測定ノイズとクロックの選択を考慮する必要がある。
struct timex のフィールド単位は flags/mode によって異なる。例えば、周波数は規定されたスケーリング単位を使用し、offset のマイクロ秒/ナノ秒の意味は状態による。adjtimex のすべてのフィールドを通常のナノ秒と ppm として注釈付けることはできない。adjtimex を参照。
同期状態と誤差範囲は一緒に読む必要がある
同期サービスが同期を報告しても、2台のマシンの時間が完全に同一であることを意味するものではない。ネットワークパスの非対称性、クロックドリフト、サンプリング、およびハードウェアにより誤差が発生する。ミリ秒未満の間隔をマシン間で判断する場合、誤差範囲を結論に組み込む必要がある。
4. TAI、UTC、およびうるう秒
TAI と UTC のオフセットは歴史的なうるう秒によって決定され、長期的な知識記事の中で特定の値を永久定数として記載すべきではない。Linux の CLOCK_TAI は、システムが正しいオフセットを設定していることに依存する。オフセットが設定されていない、または誤っている場合、呼び出しが成功しても、信頼できる TAI が返されるとは限らない。
TAI 時間スケールには、UTC のうるう秒挿入のような同様の不連続処理はないが、カーネルの時間補正と設定変更は実際の観測に影響を与える可能性がある。leap smear を使用する時間サービスは、ウィンドウ内で意図的に厳密な UTC から外れる。異なる smear 戦略を混合したマシンは、タイムスタンプが直接一致すると仮定できない。
プロトコルは、時間スケール、単位、およびうるう秒/補正戦略を明確にする必要があり、単に整数を渡して timestamp と命名するだけでは不十分である。
5. PTP、PHC、およびハードウェアタイムスタンプ
PHC はデバイス上の PTP ハードウェアクロックであり、Linux は /dev/ptpN を介してアクセスを提供できる。ネットワークカードは、送受信ハードウェアに近い位置でタイムスタンプを打つことで、ソフトウェアキューイングに起因する不確実性を減らす。PTP プロトコルおよびユーザーランドサービスは、これらの測定を使用して対応するクロックを同期する。
ネットワークカードの PHC とシステムの REALTIME は異なるクロックオブジェクトであり、ptp4l がインストールされただけでは、両者が自動的に完全に一致することが保証されるものではない。phc2sys などのツールは、対応するクロック間の同期を担当し、設定ではどちらが参照であり、どちらが調整されるかを説明する必要がある。
SO_TIMESTAMPING のソフトウェア、変換、および生のハードウェアタイムスタンプフィールドとフラグは、インターフェースに従って解釈する必要がある。ナノ秒の保存単位は、ナノ秒単位の同期精度を保証するものではなく、実際にはネットワークカード、スイッチ、リンク、設定、温度などに影響される。PTP clock および ネットワークタイムスタンプ を参照。
6. vDSO は高速読み取りパスであり、新しい時間スケールではない
カーネルは vDSO ELF をユーザープロセスにマップし、libc は其中的なクロック関数を呼び出すことができる。カーネルが公開するデータページとユーザーランドで読み取りが許可されたカウントソースと組み合わせることで、一般的なクロッククエリはカーネルへの陥落を回避できる。
これは依然としてカーネルが維持する時間パラメータと一貫性プロトコルに依存している。対象アーキテクチャ、clocksource、または clock ID が高速パスでサポートされていない場合、syscall にフォールバックする可能性がある。strace で clock_gettime syscall が見えないからといって、プログラムが時間を読んでいないことを意味するものではない。
syscall によるカーネルへの入り込みは特権レベルの変換を伴い、必ずしもタスクコンテキストスイッチが発生するわけではない。読み取りコストは測定する必要があり、vDSO が 20 ns、syscall が 100 ns であると固定して約束することはできない。x86 のパスを、ARM の __arch_counter_get_cntvct() を呼び出すかのように偽装して記述してはならない。vDSO を参照。
7. ハードウェアの能力が最終的な時間の品質を意味するわけではない
TSC、HPET、ACPI PM timer、ARM Generic Timer、および仮想化 paravirtual clock は、異なるアクセス、周波数、および安定性の属性を持つ。TSC の invariant などの能力は安定した計時に役立つが、CPU 間の同期、ファームウェア、仮想化、および移行にはカーネルの判断が必要であり、CPU 周波数の逆数を測定精度として直接使用することはできない。
clocksource watchdog およびカーネルの検証により、信頼できないソースのいくつかを発見し、切り替えることができる。「理論的により高速な」ソースを強制的に選択すると、信頼性が低下する可能性がある。現在の設定を理解するための第一歩は、読み取り専用で観察することである。
from pathlib import Path
import time
root = Path ( ' /sys/devices/system/clocksource/clocksource0' )
for field in ( ' current_clocksource' , ' available_clocksource' ) :
try :
print ( field , ( root / field ) . read_text ( ) . strip ( ) )
except OSError as exc :
print ( field , type ( exc ) . __name__ )
names = ( ' CLOCK_REALTIME' , ' CLOCK_MONOTONIC' , ' CLOCK_MONOTONIC_RAW' ,
' CLOCK_BOOTTIME' , ' CLOCK_TAI' )
for name in names :
if not hasattr ( time , name ) :
continue
clock = getattr ( time , name )
try :
print ( name , ' ns=' , time . clock_gettime_ns ( clock ) ,
' resolution_s=' , time . clock_getres ( clock ) )
except OSError as exc :
print ( name , type ( exc ) . __name__ )
start = time . clock_gettime_ns ( time . CLOCK_MONOTONIC )
time . sleep ( 0. 02 )
elapsed = time . clock_gettime_ns ( time . CLOCK_MONOTONIC ) - start
assert elapsed >= 20_000_000
print ( ' observed sleep elapsed_ns=' , elapsed )
これらのクロックは順次読み取られるため、隣接する印刷値の差を正確なオフセットとして直接使用することはできない。この例では、クロックの変更やサスペンドのトリガーも行わない。clock_getres() は分解能情報を提供するが、現在の UTC 誤差や実際のウェイクアップジッターを証明するものではない。
8. 時間測定の実行順序
まず、比較対象が同じ時間スケールおよび同じマシン/起動サイクルであることを確認する。次に、サスペンド、補正、および仮想化の一時停止がカウントされるべきかどうかを確認する。周期的な実行が必要な場合は、絶対/相対期限と補走ルールを確認し、マシン間の比較が必要な場合は同期誤差を確認し、最後に vDSO およびハードウェア読み取りのオーバーヘッドを分析する。
関連リンク:クロックとタイマー 、同期メカニズム 。