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

[インデックス 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_REALTIMECLOCK_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_REALTIMECLOCK_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.ssrc/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固有の定義が必要です。
  • 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, DIMOVQ CLOCK_MONOTONIC, DI に変更。
      • AMD64では、システムコールの第一引数はDIレジスタに渡されます。

これらの変更により、GoランタイムがOpenBSD上でnanotimeを呼び出す際に、システム時刻の変更に影響されないモノトニックな時間を取得するようになります。これは、Goの内部タイマーやスリープ処理の正確性と信頼性を大幅に向上させます。

コアとなるコードの変更箇所

このコミットによって変更されたファイルは以下の3つです。

  1. src/pkg/runtime/os_openbsd.c:
    • runtime·semasleep関数内でruntime·thrsleepを呼び出す際のクロックIDをCLOCK_REALTIMEからCLOCK_MONOTONICに変更。
  2. src/pkg/runtime/sys_openbsd_386.s:
    • CLOCK_MONOTONICマクロを定義(値は$3)。
    • runtime·nanotime関数内でclock_gettimeシステムコールを呼び出す際のclock_id$0からCLOCK_MONOTONICに変更。
  3. 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 $3CLOCK_MONOTONICを定義しています。 MOVQ $0, DIからMOVQ CLOCK_MONOTONIC, DIへの変更は、AMD64のシステムコール規約に従い、第一引数(clock_id)をDIレジスタに渡す際にCLOCK_MONOTONICを使用するようにしています。これにより、AMD64環境でもruntime·nanotimeがモノトニックな時間を返すようになります。

これらの変更は、GoランタイムがOpenBSD上でより堅牢で正確な時間計測を行うための基盤を確立し、システム時刻の変動に起因する潜在的な問題を排除します。

関連リンク

参考にした情報源リンク

  • clock_gettime man page (OpenBSD): https://man.openbsd.org/clock_gettime.2 (一般的なUnix系OSのclock_gettimeの動作を理解するために参照)
  • CLOCK_REALTIME vs CLOCK_MONOTONIC の概念に関する一般的な情報源 (例: Linux man pages, POSIX標準ドキュメントなど)