[インデックス 18654] ファイルの概要
このコミットは、Go言語のランタイムにおいて、FreeBSDオペレーティングシステム上のamd64
、386
、arm
アーキテクチャ向けに、タイマーの実装でシステムクロックとしてCLOCK_REALTIME
の代わりにCLOCK_MONOTONIC
を使用するように変更するものです。これにより、システム時刻の変更(NTP同期や手動調整など)によるタイマーのずれを防ぎ、より信頼性の高い時間計測を実現します。
コミット
commit 36013e4a2252c15d84e8c9dbfb58e8f5273aa41d
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Wed Feb 26 10:19:51 2014 +0900
runtime: use monotonic clock for timers on freebsd/amd64,386,arm
For now we don't use CLOCK_MONOTONIC_FAST instead because
it's not supported on prior to 9-STABLE.
Update #6007
LGTM=minux.ma
R=golang-codereviews, minux.ma, bradfitz
CC=golang-codereviews
https://golang.org/cl/68690043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/36013e4a2252c15d84e8c9dbfb58e8f5273aa41d
元コミット内容
GoランタイムがFreeBSD上でタイマーを実装する際に、CLOCK_REALTIME
(システム時刻)ではなく、CLOCK_MONOTONIC
(単調増加クロック)を使用するように変更します。これは、システム時刻の変更がタイマーの動作に影響を与えることを避けるためです。また、FreeBSD 9-STABLE以前のバージョンではCLOCK_MONOTONIC_FAST
がサポートされていないため、現時点ではCLOCK_MONOTONIC_FAST
は使用しないことが明記されています。
変更の背景
Go言語のランタイムは、ゴルーチンのスケジューリング、ネットワークI/Oのタイムアウト、time.Sleep
などの機能のために内部的にタイマーを使用しています。これらのタイマーがシステム時刻に依存している場合、システム管理者が時刻を手動で変更したり、NTP(Network Time Protocol)によって時刻が調整されたりすると、タイマーの動作に予期せぬ影響を与える可能性があります。
例えば、システム時刻が過去に戻された場合、タイマーが設定された時間よりも長く待機してしまう、あるいは既に期限切れと判断されてしまうといった問題が発生し得ます。逆に、システム時刻が未来に進められた場合、タイマーが設定された時間よりも早く期限切れと判断されてしまう可能性があります。このような挙動は、特に分散システムやリアルタイム性が求められるアプリケーションにおいて、深刻なバグやパフォーマンスの問題を引き起こす原因となります。
このコミットは、このようなシステム時刻の変更に影響されない、より堅牢で信頼性の高いタイマーメカニズムをFreeBSD環境で提供することを目的としています。具体的には、システム起動時からの経過時間を表す単調増加クロックを使用することで、外部からの時刻調整に左右されない正確な時間計測を実現します。
前提知識の解説
1. システムクロックの種類(Unix系OS)
Unix系OSには、主に以下の種類のシステムクロックが存在します。
-
CLOCK_REALTIME
:- これは「壁時計時間(Wall-clock time)」とも呼ばれ、現実世界の現在時刻を表します。
- NTPによる同期や手動での時刻調整によって、進んだり遅れたり、あるいは過去に戻ったりする可能性があります。
time.Time
構造体やtime.Now()
関数が通常このクロックに基づいています。- 人間が認識する「現在の時刻」としては適切ですが、時間間隔の計測やタイマーには不向きです。
-
CLOCK_MONOTONIC
:- システムが起動してからの経過時間を表す単調増加クロックです。
- システム時刻の変更(NTP同期や手動調整)には影響されず、常に未来に向かって進みます。
- システムのスリープ時には停止する場合があります(ただし、FreeBSDの
CLOCK_MONOTONIC
は通常スリープ時も進みます)。 - 時間間隔の計測やタイムアウト、タイマーの実装に非常に適しています。
-
CLOCK_MONOTONIC_RAW
:CLOCK_MONOTONIC
と同様に単調増加しますが、NTPなどによる調整の影響を一切受けない、より「生の」クロックです。- 通常、
CLOCK_MONOTONIC
で十分であり、CLOCK_MONOTONIC_RAW
が必要となるケースは稀です。
-
CLOCK_MONOTONIC_FAST
(FreeBSD固有):- FreeBSDに存在する、
CLOCK_MONOTONIC
よりも高速な単調増加クロックです。 - 精度は若干劣る可能性がありますが、オーバーヘッドが少ないため、頻繁に呼び出される時間計測に適しています。
- ただし、このコミットの時点では、FreeBSDの古いバージョン(9-STABLE以前)ではサポートされていませんでした。
- FreeBSDに存在する、
2. clock_gettime
システムコール
Unix系OSでは、clock_gettime
システムコールを使用して様々な種類のクロックの現在値を取得します。このシステムコールは、第一引数に取得したいクロックの種類(例: CLOCK_REALTIME
、CLOCK_MONOTONIC
など)を指定し、第二引数に時間を格納する構造体(timespec
など)へのポインタを渡します。
3. アセンブリ言語とシステムコール
Go言語のランタイムは、パフォーマンスが非常に重要となる部分や、OS固有の機能に直接アクセスする必要がある部分で、アセンブリ言語(特にPlan 9アセンブリ)を使用することがあります。システムコールを直接呼び出す場合も、通常はアセンブリ言語で記述されたラッパー関数を介して行われます。
このコミットで変更されている.s
ファイルは、GoランタイムがFreeBSD上でシステムコールを呼び出すためのアセンブリコードです。
4. FreeBSD 8-STABLEと9-STABLE
FreeBSDは、安定版リリース(STABLE)と開発版(CURRENT)のブランチを持っています。8-STABLE
や9-STABLE
は、それぞれFreeBSDのバージョン8系、9系の安定版ブランチを指します。このコミットの時点では、CLOCK_MONOTONIC_FAST
はFreeBSD 9-STABLE以降で導入された機能であり、それ以前のバージョン(8-STABLEなど)では利用できませんでした。Goランタイムは幅広いOSバージョンをサポートする必要があるため、当時最新の機能であっても、古いバージョンとの互換性を考慮する必要がありました。
技術的詳細
このコミットの核心は、GoランタイムがFreeBSD上で時間計測を行う際に使用するクロックソースをCLOCK_REALTIME
からCLOCK_MONOTONIC
へ変更することです。これは、clock_gettime
システムコールを呼び出す際のアセンブリコードにおける引数を変更することで実現されています。
具体的には、runtime·nanotime
関数(Goランタイムが内部的にナノ秒単位の時間を取得するために使用する関数)の実装において、clock_gettime
システムコールに渡すクロックIDを0
(CLOCK_REALTIME
に対応)から4
(CLOCK_MONOTONIC
に対応)に変更しています。
CLOCK_REALTIME
とCLOCK_MONOTONIC
の数値表現
FreeBSDのsys/time.h
や関連するヘッダファイルでは、これらのクロックIDがマクロとして定義されています。
CLOCK_REALTIME
は通常0
CLOCK_MONOTONIC
は通常4
といった数値に対応しています。アセンブリコードでは、これらの数値が直接レジスタにロードされてシステムコールに渡されます。
nanotime
関数の重要性
runtime·nanotime
は、Goランタイム内で非常に頻繁に呼び出される低レベルの時間取得関数です。タイマーの管理、スケジューラの時間計測、プロファイリングなど、時間に関連する多くの内部処理で利用されます。この関数が単調増加クロックを使用するように変更されることで、Goプログラム全体の時間依存の挙動がシステム時刻の変動から隔離され、より予測可能で安定したものになります。
CLOCK_MONOTONIC_FAST
に関する考慮
コミットメッセージにある「For now we don't use CLOCK_MONOTONIC_FAST instead because it's not supported on prior to 9-STABLE.」という記述は重要です。これは、Goランタイムが特定のOSバージョンに依存しすぎないように、後方互換性を維持しながら機能改善を進めるというGoプロジェクトの方針を示しています。当時、FreeBSD 8-STABLEのような古いバージョンもまだ広く使われていたため、より高速なCLOCK_MONOTONIC_FAST
の利用は見送られ、より互換性の高いCLOCK_MONOTONIC
が選択されました。将来的に古いOSバージョンのサポートが終了すれば、CLOCK_MONOTONIC_FAST
への切り替えが検討される可能性が示唆されています。
コアとなるコードの変更箇所
変更は、FreeBSDの異なるアーキテクチャ(386
、amd64
、arm
)向けのアセンブリファイルに集中しています。
src/pkg/runtime/sys_freebsd_386.s
--- a/src/pkg/runtime/sys_freebsd_386.s
+++ b/src/pkg/runtime/sys_freebsd_386.s
@@ -152,7 +152,9 @@ TEXT time·now(SB), NOSPLIT, $32
TEXT runtime·nanotime(SB), NOSPLIT, $32
MOVL $232, AX
LEAL 12(SP), BX
- MOVL $0, 4(SP)
+ // We can use CLOCK_MONOTONIC_FAST here when we drop
+ // support for FreeBSD 8-STABLE.
+ MOVL $4, 4(SP) // CLOCK_MONOTONIC
MOVL BX, 8(SP)
INT $0x80
MOVL 12(SP), AX // sec
src/pkg/runtime/sys_freebsd_amd64.s
--- a/src/pkg/runtime/sys_freebsd_amd64.s
+++ b/src/pkg/runtime/sys_freebsd_amd64.s
@@ -157,7 +157,9 @@ TEXT time·now(SB), NOSPLIT, $32
TEXT runtime·nanotime(SB), NOSPLIT, $32
MOVL $232, AX
- MOVQ $0, DI
+ // We can use CLOCK_MONOTONIC_FAST here when we drop
+ // support for FreeBSD 8-STABLE.
+ MOVQ $4, DI // CLOCK_MONOTONIC
LEAQ 8(SP), SI
SYSCALL
MOVQ 8(SP), AX // sec
src/pkg/runtime/sys_freebsd_arm.s
--- a/src/pkg/runtime/sys_freebsd_arm.s
+++ b/src/pkg/runtime/sys_freebsd_arm.s
@@ -162,7 +162,9 @@ TEXT time·now(SB), NOSPLIT, $32
// int64 nanotime(void) so really
// void nanotime(int64 *nsec)
TEXT runtime·nanotime(SB), NOSPLIT, $32
- MOVW $0, R0 // CLOCK_REALTIME
+ // We can use CLOCK_MONOTONIC_FAST here when we drop
+ // support for FreeBSD 8-STABLE.
+ MOVW $4, R0 // CLOCK_MONOTONIC
MOVW $8(R13), R1
MOVW $SYS_clock_gettime, R7
SWI $0
コアとなるコードの解説
各アーキテクチャのアセンブリコードにおいて、runtime·nanotime
関数の実装が変更されています。この関数は、clock_gettime
システムコールを呼び出して現在の時間を取得します。
-
変更前:
386
アーキテクチャではMOVL $0, 4(SP)
amd64
アーキテクチャではMOVQ $0, DI
arm
アーキテクチャではMOVW $0, R0
これらの命令は、システムコールに渡す引数として0
(CLOCK_REALTIME
に対応)をセットしていました。386
ではスタックのオフセット4(SP)
に、amd64
ではDI
レジスタに、arm
ではR0
レジスタにそれぞれ値をロードしています。これらは、clock_gettime
システムコールの第一引数(クロックID)を渡すための慣例的なレジスタまたはスタック位置です。
-
変更後:
386
アーキテクチャではMOVL $4, 4(SP)
amd64
アーキテクチャではMOVQ $4, DI
arm
アーキテクチャではMOVW $4, R0
これらの命令により、引数が4
(CLOCK_MONOTONIC
に対応)に変更されました。これにより、runtime·nanotime
がCLOCK_MONOTONIC
から時間を取得するようになり、システム時刻の変動に影響されない単調増加する時間値が返されるようになります。
また、各変更箇所には、CLOCK_MONOTONIC_FAST
に関するコメントが追加されています。これは、将来的にFreeBSD 8-STABLEのサポートを終了した際には、より高速なCLOCK_MONOTONIC_FAST
を使用する可能性があることを示唆しています。
time·now
関数(Goのtime.Now()
に対応する内部関数)は引き続きCLOCK_REALTIME
を使用するように変更されていません。これは、time.Now()
がユーザーが認識する「現在の時刻」を提供することを目的としているため、システム時刻に同期している必要があるからです。nanotime
はあくまでランタイム内部のタイマーや時間計測のための関数であり、その目的が異なるため、異なるクロックソースを使用することが適切です。
関連リンク
- Go issue #6007: https://github.com/golang/go/issues/6007 (このコミットが解決したissue)
- Go CL 68690043: https://golang.org/cl/68690043 (このコミットのGerritレビューページ)
参考にした情報源リンク
clock_gettime(2)
man page (FreeBSD): https://www.freebsd.org/cgi/man.cgi?query=clock_gettime&sektion=2sys/time.h
(FreeBSD source code, for clock IDs): https://github.com/freebsd/freebsd-src/blob/main/sys/sys/time.h- Go Assembly Language (Plan 9 style): https://go.dev/doc/asm
- Monotonic Clock (Wikipedia): https://en.wikipedia.org/wiki/Monotonic_clock
- NTP (Network Time Protocol) (Wikipedia): https://en.wikipedia.org/wiki/Network_Time_Protocol
- FreeBSD Release Engineering: https://www.freebsd.org/releases/
- Go runtime source code (for context on
nanotime
andtime.now
): https://github.com/golang/go/tree/master/src/runtime