[インデックス 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) のシステムに関する情報