[インデックス 17032] ファイルの概要
このコミットは、Goランタイムにおける特定の変更(CL 12250043 / e911f94c4902)を元に戻すものです。元の変更は、システムコール中のゴルーチンのトレースバックにおいて、gp->sched
フィールドではなく、より安定しているとされるgcpc
/gcsp
(後にsyscallpc
/syscallsp
に改名)を使用することを目的としていました。しかし、この変更が386アーキテクチャのビルドを壊したため、このコミットで元の状態に戻されました。
コミット
commit f38ff9e5ea24d1ea27928cfdc35c4679abe4673f
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Mon Aug 5 23:33:50 2013 +0400
undo CL 12250043 / e911f94c4902
Break all 386 builders.
««« original CL description
runtime: use gcpc/gcsp during traceback of goroutines in syscalls
gcpc/gcsp are used by GC in similar situation.
gcpc/gcsp are also more stable than gp->sched,
because gp->sched is mutated by entersyscall/exitsyscall
in morestack and mcall. So it has higher chances of being inconsistent.
Also, rename gcpc/gcsp to syscallpc/syscallsp.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/12250043
»»»
R=rsc
CC=golang-dev
https://golang.org/cl/12424045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f38ff9e5ea24d1ea27928cfdc35c4679abe4673f
元コミット内容
このコミットは、以下の内容を持つ元のコミット(CL 12250043 / e911f94c4902)を元に戻すものです。
- 目的: システムコール中のゴルーチンのトレースバックにおいて、
gp->sched
フィールドの代わりにgcpc
/gcsp
を使用する。 - 理由:
gp->sched
はentersyscall
/exitsyscall
やmorestack
/mcall
の際に変更されるため、一貫性が失われる可能性が高い。一方、gcpc
/gcsp
はGC(ガベージコレクション)で同様の状況で使用されており、より安定しているとされていた。 - 変更:
gcpc
/gcsp
をsyscallpc
/syscallsp
に改名する。
変更の背景
元のコミット(CL 12250043)は、Goランタイムがシステムコール中にいるゴルーチンのスタックトレースをより正確に取得するための改善を試みました。具体的には、ゴルーチンのスケジューリング情報(gp->sched
)がシステムコールへの出入りやスタックの拡張(morestack
)などの際に頻繁に更新され、その結果、トレースバック時に不整合な状態になる可能性があるという問題に対処しようとしました。
この問題を解決するため、元のコミットでは、ガベージコレクション(GC)が同様の状況で利用しているgcpc
(プログラムカウンタ)とgcsp
(スタックポインタ)というフィールドを、システムコール中のゴルーチンのスタック情報として利用することを提案しました。さらに、これらのフィールドの目的を明確にするため、syscallpc
とsyscallsp
に改名されました。
しかし、この変更が適用された結果、386アーキテクチャのビルドが全て壊れるという重大な問題が発生しました。これは、特定のアーキテクチャにおいて、この新しいスタック情報管理のロジックが正しく機能しなかったことを示唆しています。ビルドの安定性を最優先するため、このコミットでは問題を引き起こした変更を即座に元に戻すことが決定されました。
前提知識の解説
このコミットを理解するためには、Goランタイムの以下の概念を理解しておく必要があります。
- ゴルーチン (Goroutine): Goの軽量な並行処理単位です。OSのスレッドよりもはるかに軽量で、数百万のゴルーチンを同時に実行できます。各ゴルーチンは独自のスタックを持ちます。
- スケジューラ (Scheduler): Goランタイムの重要なコンポーネントで、ゴルーチンをOSスレッド(M: Machine)にマッピングし、実行を管理します。ゴルーチンは実行状態(Rrunning)、システムコール中(Gsyscall)、待機中(Gwaiting)など、様々なステータスを持ちます。
- スタック (Stack): 各ゴルーチンは、関数呼び出しの引数、ローカル変数、リターンアドレスなどを格納するためのスタックを持っています。Goのスタックは動的に伸縮します。
- トレースバック (Traceback): プログラムの実行中にエラーやパニックが発生した際に、現在の関数呼び出しの連鎖(コールスタック)を遡って表示する機能です。デバッグや問題解析に不可欠です。
- システムコール (System Call): プログラムがOSの機能(ファイルI/O、ネットワーク通信など)を利用するために、カーネルに要求を出すことです。Goのゴルーチンがシステムコールを実行する際、そのゴルーチンは
Gsyscall
ステータスに移行し、OSスレッドにブロックされます。 G
構造体: Goランタイム内部でゴルーチンを表す構造体です。この構造体には、ゴルーチンのスタック情報、スケジューリング情報、ステータスなどが含まれます。gp->sched.pc
/gp->sched.sp
: ゴルーチンの現在のプログラムカウンタとスタックポインタを保持します。これはゴルーチンがスケジューラによって中断されたり再開されたりする際に使用される一般的なスケジューリング情報です。gp->stackbase
/gp->stackguard
: ゴルーチンのスタックの基底アドレスとガードページのアドレスを示します。スタックの拡張やオーバーフロー検出に使用されます。
- ガベージコレクション (GC): Goの自動メモリ管理機能です。GCは、到達可能なオブジェクトを特定し、到達不能なオブジェクトが占めるメモリを解放します。GCの過程で、実行中のゴルーチンのスタックをスキャンして、参照されているオブジェクトを特定する必要があります。システムコール中のゴルーチンのスタックをスキャンする際には、特別な考慮が必要です。
entersyscall
/exitsyscall
: ゴルーチンがシステムコールに入る際と出る際に呼び出されるランタイム関数です。これらの関数は、ゴルーチンのステータスを変更し、スタック情報を更新する可能性があります。morestack
/mcall
: スタックの拡張が必要になった際に呼び出されるランタイム関数です。mcall
は、現在のゴルーチンのコンテキストを保存し、ランタイムの特別なスタックに切り替えるために使用されます。
技術的詳細
このコミットは、Goランタイムがシステムコール中のゴルーチンのスタック情報をどのように扱うかという、非常に低レベルな部分に影響を与えます。
元のコミット(CL 12250043)の意図は、システムコール中のゴルーチンのトレースバックの信頼性を向上させることでした。gp->sched.pc
とgp->sched.sp
は、ゴルーチンがユーザーコードを実行している間は適切ですが、システムコールに入ったり出たりする際に、entersyscall
やexitsyscall
、あるいはスタックの拡張を伴うmorestack
やmcall
といったランタイム関数によって頻繁に更新されます。この更新のタイミングによっては、トレースバック時にこれらの値が一時的に不整合な状態になる可能性がありました。
そこで、元のコミットでは、GCがシステムコール中のゴルーチンのスタックをスキャンする際に使用するgcpc
とgcsp
というフィールドに注目しました。これらのフィールドは、GCが安全にスタックをスキャンできるように、システムコールに入る直前の安定したスタック情報を保持するように設計されていました。元のコミットの作者は、これらのフィールドがgp->sched
よりも安定しており、トレースバックにも適していると考えました。そのため、gcpc
/gcsp
をsyscallpc
/syscallsp
に改名し、システムコール中のトレースバックに利用するように変更しました。
しかし、この変更が386アーキテクチャでビルドエラーを引き起こしたということは、以下のいずれかの問題が発生した可能性が高いです。
- アーキテクチャ固有のレジスタ/スタックの扱い: 386アーキテクチャのABI(Application Binary Interface)やレジスタの利用規約が、他のアーキテクチャ(x86-64やARMなど)と異なり、
syscallpc
/syscallsp
に保存される値がトレースバック時に期待される形式ではなかった。 - コンパイラ/アセンブラの挙動: 386向けのGoコンパイラやアセンブラが、
entersyscall
/exitsyscall
やmorestack
/mcall
のコード生成において、syscallpc
/syscallsp
の更新タイミングや値の保存方法に関して、他のアーキテクチャとは異なる挙動を示した。 - スタックの整合性チェックの失敗:
proc.c
内のentersyscall
およびentersyscallblock
関数で行われるスタックの整合性チェック(g->syscallsp < g->syscallguard-StackGuard || g->syscallstack < g->syscallsp
)が、386アーキテクチャで頻繁に失敗するようになった。これは、syscallsp
やsyscallstack
の値が期待通りに設定されなかったか、スタックガードの計算に問題があったことを示唆します。
このコミットは、これらの問題を解決するために、元の変更を完全に元に戻し、システムコール中のゴルーチンのトレースバックには再びgp->sched.pc
とgp->sched.sp
を使用するようにしました。これは、問題の原因を特定して修正するよりも、安定した状態に戻すことを優先した結果です。
コアとなるコードの変更箇所
このコミットは、以下の5つのファイルにわたる変更を元に戻しています。
-
src/pkg/runtime/mgc0.c
:addstackroots
関数内で、システムコール中のゴルーチンのスタックをスキャンする際に使用するポインタとガードのフィールドを、gp->syscallsp
,gp->syscallpc
,gp->syscallstack
,gp->syscallguard
から、元のgp->gcsp
,gp->gcpc
,gp->gcstack
,gp->gcguard
に戻しています。
-
src/pkg/runtime/proc.c
:save
関数内で、g->gcpc
とg->gcsp
への代入を削除しています。entersyscall
関数とentersyscallblock
関数内で、システムコールに入る直前のゴルーチンのスタック情報を保存するフィールドを、g->syscallsp
,g->syscallpc
,g->syscallstack
,g->syscallguard
から、元のg->gcsp
,g->gcpc
,g->gcstack
,g->gcguard
に戻しています。- これらの関数内で行われるスタックの整合性チェックも、
g->syscallsp
などからg->gcsp
などを使用するように戻されています。 runtime·exitsyscall
関数内で、システムコール終了時にクリアするフィールドを、g->syscallstack
,g->syscallsp
から、元のg->gcstack
,g->gcsp
に戻しています。
-
src/pkg/runtime/runtime.h
:G
構造体内のフィールド名を、syscallstack
,syscallguard
,syscallsp
,syscallpc
から、元のgcstack
,gcsp
,gcpc
,gcguard
に戻しています。これは、元のコミットで行われたフィールド名の変更を元に戻すものです。
-
src/pkg/runtime/traceback_arm.c
:runtime·traceback
関数内で、gp->status == Gsyscall
の場合にトレースバックに使用するPCとSPを、gp->syscallpc
とgp->syscallsp
から、元のgp->sched.pc
とgp->sched.sp
に戻しています。
-
src/pkg/runtime/traceback_x86.c
:runtime·traceback
関数内で、gp->status == Gsyscall
の場合にトレースバックに使用するPCとSPを、gp->syscallpc
とgp->syscallsp
から、元のgp->sched.pc
とgp->sched.sp
に戻しています。
コアとなるコードの解説
このコミットの核心は、システムコール中のゴルーチンのスタック情報を管理するためのアプローチを、元の(問題を引き起こした)変更以前の状態に戻すことです。
具体的には、以下の点が重要です。
- フィールド名の復元:
runtime.h
におけるG
構造体のフィールド名が、syscall*
からgc*
に復元されました。これは、元のコミットがgcpc
/gcsp
をsyscallpc
/syscallsp
に改名したことを元に戻すものです。これにより、これらのフィールドが主にGCの目的で使用されることが再び明確になります。 - スタック情報保存ロジックの復元:
proc.c
内のentersyscall
およびentersyscallblock
関数において、システムコールに入る直前のゴルーチンのPCとSP、およびスタックの基底とガードのアドレスを保存するロジックが、g->syscall*
フィールドではなく、g->gc*
フィールドを使用するように戻されました。これは、元のコミットがsyscall*
フィールドにこれらの情報を保存しようとした試みを無効化します。 - トレースバックロジックの復元:
traceback_arm.c
とtraceback_x86.c
において、システムコール中のゴルーチンのトレースバック時に使用するPCとSPが、gp->syscallpc
/gp->syscallsp
ではなく、gp->sched.pc
/gp->sched.sp
に戻されました。これは、元のコミットがsyscall*
フィールドをトレースバックに使用しようとした試みを元に戻し、以前の(そして386で動作していた)ロジックに戻すものです。
この変更により、386アーキテクチャのビルドが再び正常に動作するようになりました。これは、元の変更が386アーキテクチャの特定の特性と互換性がなかったことを強く示唆しています。Goランタイムは様々なアーキテクチャをサポートしているため、このような低レベルの変更は、各アーキテクチャの特性を考慮に入れる必要があります。このコミットは、機能改善よりも安定性を優先するというGo開発チームの姿勢を示しています。
関連リンク
- 元の変更 (CL 12250043): https://golang.org/cl/12250043
- このコミット (CL 12424045): https://golang.org/cl/12424045
参考にした情報源リンク
- Goのソースコード(特に
src/pkg/runtime/
ディレクトリ内のファイル) - GoのIssueトラッカーやメーリングリスト(関連する議論がある場合)
- Goのガベージコレクションに関するドキュメントやブログ記事
- Goのスケジューラに関するドキュメントやブログ記事
- Goのスタック管理に関する技術記事
- 386アーキテクチャの特性に関する一般的な情報 (必要に応じて)
- Goのコミット履歴とCL (Change List) のシステムに関する情報