Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 18654] ファイルの概要

このコミットは、Go言語のランタイムにおいて、FreeBSDオペレーティングシステム上のamd64386armアーキテクチャ向けに、タイマーの実装でシステムクロックとして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以前)ではサポートされていませんでした。

2. clock_gettimeシステムコール

Unix系OSでは、clock_gettimeシステムコールを使用して様々な種類のクロックの現在値を取得します。このシステムコールは、第一引数に取得したいクロックの種類(例: CLOCK_REALTIMECLOCK_MONOTONICなど)を指定し、第二引数に時間を格納する構造体(timespecなど)へのポインタを渡します。

3. アセンブリ言語とシステムコール

Go言語のランタイムは、パフォーマンスが非常に重要となる部分や、OS固有の機能に直接アクセスする必要がある部分で、アセンブリ言語(特にPlan 9アセンブリ)を使用することがあります。システムコールを直接呼び出す場合も、通常はアセンブリ言語で記述されたラッパー関数を介して行われます。

このコミットで変更されている.sファイルは、GoランタイムがFreeBSD上でシステムコールを呼び出すためのアセンブリコードです。

4. FreeBSD 8-STABLEと9-STABLE

FreeBSDは、安定版リリース(STABLE)と開発版(CURRENT)のブランチを持っています。8-STABLE9-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を0CLOCK_REALTIMEに対応)から4CLOCK_MONOTONICに対応)に変更しています。

CLOCK_REALTIMECLOCK_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の異なるアーキテクチャ(386amd64arm)向けのアセンブリファイルに集中しています。

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 これらの命令は、システムコールに渡す引数として0CLOCK_REALTIMEに対応)をセットしていました。386ではスタックのオフセット4(SP)に、amd64ではDIレジスタに、armではR0レジスタにそれぞれ値をロードしています。これらは、clock_gettimeシステムコールの第一引数(クロックID)を渡すための慣例的なレジスタまたはスタック位置です。
  • 変更後:

    • 386アーキテクチャでは MOVL $4, 4(SP)
    • amd64アーキテクチャでは MOVQ $4, DI
    • armアーキテクチャでは MOVW $4, R0 これらの命令により、引数が4CLOCK_MONOTONICに対応)に変更されました。これにより、runtime·nanotimeCLOCK_MONOTONICから時間を取得するようになり、システム時刻の変動に影響されない単調増加する時間値が返されるようになります。

また、各変更箇所には、CLOCK_MONOTONIC_FASTに関するコメントが追加されています。これは、将来的にFreeBSD 8-STABLEのサポートを終了した際には、より高速なCLOCK_MONOTONIC_FASTを使用する可能性があることを示唆しています。

time·now関数(Goのtime.Now()に対応する内部関数)は引き続きCLOCK_REALTIMEを使用するように変更されていません。これは、time.Now()がユーザーが認識する「現在の時刻」を提供することを目的としているため、システム時刻に同期している必要があるからです。nanotimeはあくまでランタイム内部のタイマーや時間計測のための関数であり、その目的が異なるため、異なるクロックソースを使用することが適切です。

関連リンク

参考にした情報源リンク