[インデックス 18658] ファイルの概要
このコミットは、GoランタイムがOpenBSD/386およびOpenBSD/AMD64アーキテクチャ上で時間計測を行う際に、リアルタイムクロック(CLOCK_REALTIME
)ではなくモノトニッククロック(CLOCK_MONOTONIC
)を使用するように変更するものです。これにより、システム時刻の変更(例:NTP同期や手動での時刻調整)によってタイマーやスリープの動作が影響を受ける問題を解決し、より信頼性の高い時間計測を実現します。
コミット
commit 373466380586b04b8a9163938a8daf3f5cb9dd45
Author: Joel Sing <jsing@google.com>
Date: Wed Feb 26 13:20:36 2014 +1100
runtime: use monotonic clock for openbsd/386 and openbsd/amd64 timers
Switch nanotime to a monotonic clock on openbsd/386 and openbsd/amd64.
Also use a monotonic clock when for thrsleep, since the sleep duration
is based on the value returned from nanotime.
Update #6007
LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/68460044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/373466380586b04b8a9163938a8daf3f5cb9dd45
元コミット内容
Goランタイムにおいて、OpenBSD/386およびOpenBSD/AMD64環境でのタイマー処理にモノトニッククロックを使用するように変更します。具体的には、nanotime
関数がモノトニッククロックを使用するように切り替えられ、thrsleep
関数もnanotime
から返される値に基づいてスリープ期間が決定されるため、モノトニッククロックを使用するように修正されます。この変更は、Issue #6007に対応するものです。
変更の背景
従来のGoランタイムでは、OpenBSD環境においてタイマーやスリープ処理にCLOCK_REALTIME
(リアルタイムクロック)を使用していました。CLOCK_REALTIME
は、システムが現在認識している壁時計(wall-clock time)を表すため、NTP(Network Time Protocol)による時刻同期や、システム管理者が手動で時刻を変更した場合に、その時刻変更の影響を直接受けます。
この特性は、Goのランタイムが内部的に使用するタイマーやスケジューリングにおいて問題を引き起こす可能性がありました。例えば、プログラムが一定時間スリープするように設定されていても、そのスリープ中にシステム時刻が過去に戻された場合、スリープ時間が意図せず延長されたり、逆に短縮されたりする可能性があります。これは、GoのgoroutineスケジューラやネットワークI/Oのタイムアウトなど、時間に基づいた正確な動作が求められる多くの場面で、予期せぬ挙動やバグの原因となり得ます。
Issue #6007は、まさにこの問題、すなわちシステム時刻の変更がGoプログラムのタイマーに影響を与えることを報告していました。このコミットは、この問題を解決するために、時刻変更の影響を受けないモノトニッククロックを使用するように変更することで、タイマーとスリープの信頼性と正確性を向上させることを目的としています。
前提知識の解説
クロックの種類: CLOCK_REALTIME
と CLOCK_MONOTONIC
Unix系OSでは、時間計測のために複数のクロックIDが提供されています。このコミットで特に重要なのは以下の2つです。
-
CLOCK_REALTIME
(リアルタイムクロック):- これは「壁時計時間(wall-clock time)」とも呼ばれ、人間が日常的に使用するカレンダーや時計の時刻と同じです。
- システム起動時からの経過時間ではなく、特定のエポック(通常は1970年1月1日00:00:00 UTC)からの経過時間を表します。
- NTPによる時刻同期、手動での時刻変更、サマータイムの適用などによって、進んだり遅れたり、あるいは過去に戻ったりする可能性があります。
- そのため、時間の絶対的な表現には適していますが、時間間隔の計測には不向きです。例えば、ある処理が完了するまでの時間を正確に測りたい場合、途中で時刻が変更されると誤った結果が得られる可能性があります。
-
CLOCK_MONOTONIC
(モノトニッククロック):- これは「単調増加クロック」とも呼ばれ、システム起動時からの経過時間を表します。
- システム時刻の変更(NTP同期や手動変更)やサマータイムの影響を受けず、常に単調に増加します。つまり、過去に戻ることはありません。
- そのため、時間間隔(duration)の計測や、タイムアウト、スリープなどの相対的な時間計測に非常に適しています。
- ただし、絶対的な日付や時刻(例:現在の日付と時刻)を知ることはできません。
Goランタイムにおける時間計測
Goランタイムは、内部的にgoroutineのスケジューリング、タイマー、ネットワークI/Oのタイムアウトなど、様々な場面で時間計測を利用しています。
-
runtime·nanotime
:- Goランタイム内部で使用される関数で、ナノ秒単位の時間を取得します。
- この関数がどのクロックIDを使用するかは、OSやアーキテクチャに依存します。
- このコミット以前のOpenBSDでは
CLOCK_REALTIME
を使用していました。
-
runtime·thrsleep
:- Goランタイム内部で使用されるスリープ関数です。
- 指定された期間だけ現在のスレッドをスリープさせます。
- スリープ期間は、
nanotime
から取得した値に基づいて計算されるため、nanotime
が使用するクロックの種類に直接影響を受けます。
OpenBSDのシステムコール
OpenBSDを含むUnix系OSでは、clock_gettime
システムコールを使用して様々なクロックの時間を取得します。このシステムコールは、引数としてクロックID(例:CLOCK_REALTIME
やCLOCK_MONOTONIC
)と、時間を格納する構造体へのポインタを受け取ります。
技術的詳細
このコミットの核心は、OpenBSD環境におけるGoランタイムの時間計測メカニズムを、CLOCK_REALTIME
からCLOCK_MONOTONIC
へ移行することです。
os_openbsd.c
の変更
src/pkg/runtime/os_openbsd.c
ファイルでは、runtime·semasleep
関数内のruntime·thrsleep
の呼び出しが変更されています。
変更前:
runtime·thrsleep(&m->waitsemacount, CLOCK_REALTIME, &ts, &m->waitsemalock, nil);
変更後:
runtime·thrsleep(&m->waitsemacount, CLOCK_MONOTONIC, &ts, &m->waitsemalock, nil);
runtime·thrsleep
は、セマフォを待機する際にスリープする関数です。この関数に渡されるクロックIDがCLOCK_REALTIME
からCLOCK_MONOTONIC
に変更されました。これにより、セマフォ待機中のスリープ期間がシステム時刻の変更に影響されなくなり、より正確なタイムアウト処理が可能になります。
アセンブリファイルの変更 (sys_openbsd_386.s
, sys_openbsd_amd64.s
)
src/pkg/runtime/sys_openbsd_386.s
とsrc/pkg/runtime/sys_openbsd_amd64.s
は、それぞれOpenBSDの32ビット(i386)および64ビット(AMD64)アーキテクチャ向けのアセンブリコードファイルです。これらのファイルでは、runtime·nanotime
関数がclock_gettime
システムコールを呼び出す際に使用するクロックIDが変更されています。
変更前は、clock_id
として$0
(これは通常CLOCK_REALTIME
に対応します)を渡していました。
変更後は、CLOCK_MONOTONIC
という新しいマクロを定義し、その値($3
)をclock_id
として渡すように変更されています。
-
#define CLOCK_MONOTONIC $3
:- これは、OpenBSDシステムにおける
CLOCK_MONOTONIC
のシステムコール引数としての値が3
であることを示しています。OSによってこれらの定数の値は異なるため、OpenBSD固有の定義が必要です。
- これは、OpenBSDシステムにおける
-
runtime·nanotime
関数の変更:- 386版 (
sys_openbsd_386.s
):MOVL $0, 4(SP)
がMOVL CLOCK_MONOTONIC, 4(SP)
に変更。4(SP)
は、clock_gettime
システムコールの第一引数(clock_id
)が格納されるスタック上の位置です。
- AMD64版 (
sys_openbsd_amd64.s
):MOVQ $0, DI
がMOVQ CLOCK_MONOTONIC, DI
に変更。- AMD64では、システムコールの第一引数は
DI
レジスタに渡されます。
- 386版 (
これらの変更により、GoランタイムがOpenBSD上でnanotime
を呼び出す際に、システム時刻の変更に影響されないモノトニックな時間を取得するようになります。これは、Goの内部タイマーやスリープ処理の正確性と信頼性を大幅に向上させます。
コアとなるコードの変更箇所
このコミットによって変更されたファイルは以下の3つです。
src/pkg/runtime/os_openbsd.c
:runtime·semasleep
関数内でruntime·thrsleep
を呼び出す際のクロックIDをCLOCK_REALTIME
からCLOCK_MONOTONIC
に変更。
src/pkg/runtime/sys_openbsd_386.s
:CLOCK_MONOTONIC
マクロを定義(値は$3
)。runtime·nanotime
関数内でclock_gettime
システムコールを呼び出す際のclock_id
を$0
からCLOCK_MONOTONIC
に変更。
src/pkg/runtime/sys_openbsd_amd64.s
:CLOCK_MONOTONIC
マクロを定義(値は$3
)。runtime·nanotime
関数内でclock_gettime
システムコールを呼び出す際のclock_id
を$0
からCLOCK_MONOTONIC
に変更。
コアとなるコードの解説
src/pkg/runtime/os_openbsd.c
--- a/src/pkg/runtime/os_openbsd.c
+++ b/src/pkg/runtime/os_openbsd.c
@@ -82,7 +82,7 @@ runtime·semasleep(int64 ns)
// NOTE: tv_nsec is int64 on amd64, so this assumes a little-endian system.
ts.tv_nsec = 0;
ts.tv_sec = runtime·timediv(ns, 1000000000, (int32*)&ts.tv_nsec);
- runtime·thrsleep(&m->waitsemacount, CLOCK_REALTIME, &ts, &m->waitsemalock, nil);
+ runtime·thrsleep(&m->waitsemacount, CLOCK_MONOTONIC, &ts, &m->waitsemalock, nil);
}
// reacquire lock
while(runtime·xchg(&m->waitsemalock, 1))
この変更は、Goランタイムがセマフォを待機する際に使用するスリープ関数runtime·thrsleep
の動作を変更します。以前はCLOCK_REALTIME
を使用していましたが、これをCLOCK_MONOTONIC
に変更することで、システム時刻の変更に影響されない正確なスリープ期間を保証します。これは、Goのスケジューラがgoroutineを一時停止させる際に、外部の時刻変更によって予期せぬ動作が発生するのを防ぐために重要です。
src/pkg/runtime/sys_openbsd_386.s
--- a/src/pkg/runtime/sys_openbsd_386.s
+++ b/src/pkg/runtime/sys_openbsd_386.s
@@ -9,6 +9,8 @@
#include "zasm_GOOS_GOARCH.h"
#include "../../cmd/ld/textflag.h"
+#define CLOCK_MONOTONIC $3
+
// Exit the entire program (like C exit)
TEXT runtime·exit(SB),NOSPLIT,$-4
MOVL $1, AX
@@ -133,7 +135,7 @@ TEXT time·now(SB), NOSPLIT, $32
// void nanotime(int64 *nsec)
TEXT runtime·nanotime(SB),NOSPLIT,$32
LEAL 12(SP), BX
-\tMOVL $0, 4(SP)\t\t// arg 1 - clock_id
+\tMOVL CLOCK_MONOTONIC, 4(SP)\t// arg 1 - clock_id
MOVL BX, 8(SP)\t\t// arg 2 - tp
MOVL $87, AX\t\t\t// sys_clock_gettime
INT $0x80
このアセンブリコードは、OpenBSD/386アーキテクチャにおけるruntime·nanotime
関数の実装です。
#define CLOCK_MONOTONIC $3
は、CLOCK_MONOTONIC
というシンボルを値3
として定義しています。これはOpenBSDシステムコールにおけるCLOCK_MONOTONIC
の識別子です。
MOVL $0, 4(SP)
からMOVL CLOCK_MONOTONIC, 4(SP)
への変更は、clock_gettime
システムコールを呼び出す際に、第一引数(clock_id
)としてCLOCK_REALTIME
(通常0
)ではなくCLOCK_MONOTONIC
を渡すように指示しています。これにより、runtime·nanotime
が返す時間がシステム時刻の変更に影響されない、単調増加する時間になります。
src/pkg/runtime/sys_openbsd_amd64.s
--- a/src/pkg/runtime/sys_openbsd_amd64.s
+++ b/src/pkg/runtime/sys_openbsd_amd64.s
@@ -9,6 +9,8 @@
#include "zasm_GOOS_GOARCH.h"
#include "../../cmd/ld/textflag.h"
+#define CLOCK_MONOTONIC $3
+
// int64 tfork(void *param, uintptr psize, M *mp, G *gp, void (*fn)(void));
TEXT runtime·tfork(SB),NOSPLIT,$32
@@ -166,7 +168,7 @@ TEXT time·now(SB), NOSPLIT, $32
RET
TEXT runtime·nanotime(SB),NOSPLIT,$24
-\tMOVQ\t$0, DI\t\t\t// arg 1 - clock_id
+\tMOVQ\tCLOCK_MONOTONIC, DI\t// arg 1 - clock_id
LEAQ\t8(SP), SI\t\t// arg 2 - tp
MOVL\t$87, AX\t\t\t// sys_clock_gettime
SYSCALL
このアセンブリコードは、OpenBSD/AMD64アーキテクチャにおけるruntime·nanotime
関数の実装です。
386版と同様に、#define CLOCK_MONOTONIC $3
でCLOCK_MONOTONIC
を定義しています。
MOVQ $0, DI
からMOVQ CLOCK_MONOTONIC, DI
への変更は、AMD64のシステムコール規約に従い、第一引数(clock_id
)をDI
レジスタに渡す際にCLOCK_MONOTONIC
を使用するようにしています。これにより、AMD64環境でもruntime·nanotime
がモノトニックな時間を返すようになります。
これらの変更は、GoランタイムがOpenBSD上でより堅牢で正確な時間計測を行うための基盤を確立し、システム時刻の変動に起因する潜在的な問題を排除します。
関連リンク
- Go Issue #6007: https://github.com/golang/go/issues/6007
- Go Code Review: https://golang.org/cl/68460044
参考にした情報源リンク
clock_gettime
man page (OpenBSD): https://man.openbsd.org/clock_gettime.2 (一般的なUnix系OSのclock_gettime
の動作を理解するために参照)CLOCK_REALTIME
vsCLOCK_MONOTONIC
の概念に関する一般的な情報源 (例: Linux man pages, POSIX標準ドキュメントなど)