[インデックス 16115] ファイルの概要
このコミットは、Goランタイムにおけるm->waitlock
フィールドの型をLock*
からvoid*
に変更し、park0
関数内でこのポインタをnil
にリセットする修正を導入しています。これにより、特にGC(ガベージコレクション)に関連する潜在的な問題、具体的には「dangling typed pointer(宙に浮いた型付きポインタ)」の問題を解決し、Goランタイムの堅牢性を向上させています。
コミット
commit 54340bf56fb4b29ea175d85cff4ba765a60961b6
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Sat Apr 6 20:01:28 2013 -0700
runtime: reset dangling typed pointer
+untype it because it can point to different types
Update #5193.
R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/8454043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/54340bf56fb4b29ea175d85cff4ba765a60961b6
元コミット内容
runtime: reset dangling typed pointer
+untype it because it can point to different types
Update #5193.
変更の背景
このコミットは、Goランタイムにおけるガベージコレクション(GC)の正確性に関わる潜在的なバグを修正するために行われました。具体的には、m->waitlock
というフィールドが、異なる種類のロックを指す可能性があるにもかかわらず、特定の型(Lock*
)として宣言されていたことが問題でした。これにより、GCがこのポインタを誤って解釈し、本来は解放されるべきメモリを保持し続けたり、あるいは誤ったメモリ領域を操作しようとしたりする「dangling typed pointer」の問題を引き起こす可能性がありました。
コミットメッセージにあるUpdate #5193
は、この変更がGoのIssue 5193に関連していることを示しています。Issue 5193は、GoランタイムのスケジューラがM(マシン)をパーク(待機)させる際に、m->waitlock
フィールドが適切にクリアされないことによって発生するGCの問題を報告していました。このフィールドがクリアされないままだと、GCが古い、無効なポインタを「生きている」と判断し、メモリリークやクラッシュの原因となる可能性がありました。
前提知識の解説
- Goランタイム (Go Runtime): Goプログラムの実行を管理する低レベルのシステム。スケジューラ、ガベージコレクタ、メモリ管理などが含まれます。
- M (Machine): Goランタイムのスケジューラにおける概念で、OSのスレッドに対応します。MはG(ゴルーチン)を実行し、P(プロセッサ)にアタッチされます。
- G (Goroutine): Goにおける軽量な実行単位。OSのスレッドよりもはるかに軽量で、数百万のゴルーチンを同時に実行できます。
- P (Processor): Goランタイムのスケジューラにおける概念で、MがGを実行するための論理的なプロセッサです。Pの数は通常、CPUのコア数に制限されます。
- ガベージコレクション (Garbage Collection, GC): プログラムが動的に確保したメモリのうち、もはや使用されていない(到達不能な)領域を自動的に解放する仕組み。GoのGCは並行かつ低遅延で動作します。
- ポインタ (Pointer): メモリ上の特定のアドレスを指し示す変数。
- 型付きポインタ (Typed Pointer): 特定のデータ型を指すポインタ。GCは型情報を使用して、ポインタが指すメモリ領域のサイズや内容を正確に判断します。
- 宙に浮いたポインタ (Dangling Pointer): 既に解放されたメモリ領域を指しているポインタ。このポインタをデリファレンス(参照解除)すると、未定義の動作やクラッシュを引き起こす可能性があります。
Lock*
: Goランタイム内で使用されるロックの型を指すポインタ。void*
: C言語系の言語における汎用ポインタ。任意の型のデータを指すことができますが、型情報を持たないため、デリファレンスする際には明示的な型キャストが必要です。GCの観点からは、void*
は通常、型情報を持たないため、GCがその指す先を追跡する際に特別な注意が必要です。このコミットでは、void*
にすることで、GCが特定の型として解釈するのを防ぎ、ポインタが指す内容が変化しても問題なく扱えるようにしています。park0
関数: Goランタイムのスケジューラの一部で、M(OSスレッド)を待機状態(パーク)にする際に呼び出される関数。Mがアイドル状態になったり、特定のイベントを待ったりする際に使用されます。
技術的詳細
このコミットの技術的な核心は、m->waitlock
フィールドの型変更と、そのポインタの明示的なリセットにあります。
-
m->waitlock
の型変更:- 変更前:
Lock* waitlock;
- 変更後:
void* waitlock;
m->waitlock
は、Mが待機状態に入る際に保持するロックを指すポインタです。このロックは、m->waitunlockf
という関数ポインタによって解放されます。問題は、waitlock
が常にLock
型のロックを指すとは限らず、異なる種類のロック(例えば、runtime.semaphores
のような他の同期プリミティブ)を指す可能性があったことです。Lock*
という型付きポインタとして宣言されていると、GoのGCは、このポインタが常にLock
構造体を指していると仮定して、そのメモリ領域をスキャンしようとします。しかし、もし実際に異なる型のデータや、既に解放されたメモリを指していた場合、GCは誤ったメモリをスキャンしたり、存在しないオブジェクトを追跡しようとしたりして、GCの正確性を損なう可能性がありました。void*
に変更することで、waitlock
は型情報を持たない汎用ポインタになります。これにより、GCはこのポインタを特定の型として解釈せず、その指す先を追跡する際に、より柔軟な(あるいはより慎重な)アプローチを取るようになります。コミットメッセージにある「+untype it because it can point to different types」は、この意図を明確に示しています。
- 変更前:
-
park0
関数内でのm->waitlock = nil;
の追加:park0
関数は、Mが待機状態に入る直前に呼び出されます。Mが待機状態に入るということは、そのMが現在保持しているリソース(特にロック)は、もはや必要ないか、別のMに引き渡されるべきであることを意味します。- 以前は、
m->waitunlockf
が呼び出されてロックが解放されても、m->waitlock
ポインタ自体はnil
にリセットされませんでした。これにより、Mが待機状態から復帰した後も、m->waitlock
は古い、無効なメモリ領域を指したままになる可能性がありました。これが「dangling typed pointer」の問題です。 m->waitlock = nil;
を追加することで、Mが待機状態に入る際に、このポインタが明示的にクリアされます。これにより、GCがこのポインタをスキャンする際に、無効なメモリ領域を追跡することを防ぎ、GCの正確性とメモリ安全性を確保します。ポインタをnil
にすることで、GCはそれが何も指していないことを認識し、安全に無視できます。
これらの変更は、Goランタイムの低レベルなメモリ管理とGCの挙動に深く関わっており、システムの安定性と信頼性を向上させる上で非常に重要です。
コアとなるコードの変更箇所
src/pkg/runtime/proc.c
--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -1178,6 +1178,7 @@ park0(G *gp)
if(m->waitunlockf) {
m->waitunlockf(m->waitlock);
m->waitunlockf = nil;
+ m->waitlock = nil;
}
if(m->lockedg) {
stoplockedm();
src/pkg/runtime/runtime.h
--- a/src/pkg/runtime/runtime.h
+++ b/src/pkg/runtime/runtime.h
@@ -317,7 +317,7 @@ struct M
bool needextram;
void* tracepc;
void (*waitunlockf)(Lock*);
- Lock* waitlock;
+ void* waitlock;
uint32 moreframesize_minalloc;
uintptr settype_buf[1024];
コアとなるコードの解説
src/pkg/runtime/proc.c
の変更
park0
関数は、現在のM(OSスレッド)をパーク(待機)状態にするためのGoランタイムの内部関数です。この関数内で、Mが待機状態に入る前に、m->waitunlockf
が設定されている場合に、関連するロックを解放する処理が行われます。
追加された行 m->waitlock = nil;
は、このロック解放処理の直後に実行されます。これにより、m->waitunlockf
によってロックが解放された後、m->waitlock
ポインタが指していたメモリ領域がもはや有効でないことを明示的に示します。ポインタをnil
に設定することで、GCがこのポインタをスキャンする際に、無効なメモリ参照を追跡することを防ぎ、GCの正確性を保証します。これは、特にGCが並行して動作するGoのような環境では非常に重要です。
src/pkg/runtime/runtime.h
の変更
M
構造体は、GoランタイムにおけるOSスレッド(マシン)の情報を保持します。
変更された行 void* waitlock;
は、M
構造体のwaitlock
フィールドの型定義を変更しています。
- 変更前は
Lock* waitlock;
であり、waitlock
が常にLock
型のオブジェクトを指すポインタであるとGCに伝えていました。 - 変更後は
void* waitlock;
となり、waitlock
が任意の型のオブジェクトを指すことができる汎用ポインタであることを示します。これにより、GCはwaitlock
が指す先の具体的な型を仮定せず、より安全な方法でこのポインタを扱えるようになります。これは、waitlock
が実際に異なる種類のロックや同期プリミティブを指す可能性があるという現実を反映したものであり、GCが誤った型情報に基づいてメモリをスキャンするのを防ぎます。
これらの変更は、Goランタイムのメモリ管理とGCの堅牢性を向上させるための、低レベルながらも重要な修正です。
関連リンク
- Go Issue 5193: https://github.com/golang/go/issues/5193 (このコミットが解決した問題の元のIssue)
- Go CL 8454043: https://golang.org/cl/8454043 (このコミットに対応するGoのコードレビューリンク)
参考にした情報源リンク
- Goのソースコード (特に
src/pkg/runtime/proc.c
とsrc/pkg/runtime/runtime.h
) - Goのガベージコレクションに関するドキュメントやブログ記事 (一般的なGCの概念理解のため)
- C言語における
void*
ポインタの概念 - Goランタイムスケジューラに関する資料 (M, P, Gの概念理解のため)
- GitHubのGoリポジトリのIssueトラッカー (Issue 5193の詳細確認のため)
- Goのコードレビューシステム (CL 8454043の詳細確認のため)
[インデックス 16115] ファイルの概要
このコミットは、Goランタイムにおけるm->waitlock
フィールドの型をLock*
からvoid*
に変更し、park0
関数内でこのポインタをnil
にリセットする修正を導入しています。これにより、特にGC(ガベージコレクション)に関連する潜在的な問題、具体的には「dangling typed pointer(宙に浮いた型付きポインタ)」の問題を解決し、Goランタイムの堅牢性を向上させています。
コミット
commit 54340bf56fb4b29ea175d85cff4ba765a60961b6
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Sat Apr 6 20:01:28 2013 -0700
runtime: reset dangling typed pointer
+untype it because it can point to different types
Update #5193.
R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/8454043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/54340bf56fb4b29ea175d85cff4ba765a60961b6
元コミット内容
runtime: reset dangling typed pointer
+untype it because it can point to different types
Update #5193.
変更の背景
このコミットは、Goランタイムにおけるガベージコレクション(GC)の正確性に関わる潜在的なバグを修正するために行われました。具体的には、m->waitlock
というフィールドが、異なる種類のロックを指す可能性があるにもかかわらず、特定の型(Lock*
)として宣言されていたことが問題でした。これにより、GCがこのポインタを誤って解釈し、本来は解放されるべきメモリを保持し続けたり、あるいは誤ったメモリ領域を操作しようとしたりする「dangling typed pointer」の問題を引き起こす可能性がありました。
コミットメッセージにあるUpdate #5193
は、この変更がGoのIssue 5193に関連していることを示しています。Issue 5193は、GoランタイムのスケジューラがM(マシン)をパーク(待機)させる際に、m->waitlock
フィールドが適切にクリアされないことによって発生するGCの問題を報告していました。このフィールドがクリアされないままだと、GCが古い、無効なポインタを「生きている」と判断し、メモリリークやクラッシュの原因となる可能性がありました。
補足: 現在のGoのGitHubリポジトリでは、Issue 5193は直接見つかりませんでした。これは、コミットが2013年に行われたものであり、当時のGoのIssueトラッキングシステムや番号付けが現在とは異なる可能性があるためです。しかし、コミットメッセージとコードの変更内容から、この修正がGCの正確性とメモリ安全性を向上させるためのものであることは明確です。
前提知識の解説
- Goランタイム (Go Runtime): Goプログラムの実行を管理する低レベルのシステム。スケジューラ、ガベージコレクタ、メモリ管理などが含まれます。
- M (Machine): Goランタイムのスケジューラにおける概念で、OSのスレッドに対応します。MはG(ゴルーチン)を実行し、P(プロセッサ)にアタッチされます。
- G (Goroutine): Goにおける軽量な実行単位。OSのスレッドよりもはるかに軽量で、数百万のゴルーチンを同時に実行できます。
- P (Processor): Goランタイムのスケジューラにおける概念で、MがGを実行するための論理的なプロセッサです。Pの数は通常、CPUのコア数に制限されます。
- ガベージコレクション (Garbage Collection, GC): プログラムが動的に確保したメモリのうち、もはや使用されていない(到達不能な)領域を自動的に解放する仕組み。GoのGCは並行かつ低遅延で動作します。
- ポインタ (Pointer): メモリ上の特定のアドレスを指し示す変数。
- 型付きポインタ (Typed Pointer): 特定のデータ型を指すポインタ。GCは型情報を使用して、ポインタが指すメモリ領域のサイズや内容を正確に判断します。
- 宙に浮いたポインタ (Dangling Pointer): 既に解放されたメモリ領域を指しているポインタ。このポインタをデリファレンス(参照解除)すると、未定義の動作やクラッシュを引き起こす可能性があります。
Lock*
: Goランタイム内で使用されるロックの型を指すポインタ。void*
: C言語系の言語における汎用ポインタ。任意の型のデータを指すことができますが、型情報を持たないため、デリファレンスする際には明示的な型キャストが必要です。GCの観点からは、void*
は通常、型情報を持たないため、GCがその指す先を追跡する際に特別な注意が必要です。このコミットでは、void*
にすることで、GCが特定の型として解釈するのを防ぎ、ポインタが指す内容が変化しても問題なく扱えるようにしています。park0
関数: Goランタイムのスケジューラの一部で、M(OSスレッド)を待機状態(パーク)にする際に呼び出される関数。Mがアイドル状態になったり、特定のイベントを待ったりする際に使用されます。
技術的詳細
このコミットの技術的な核心は、m->waitlock
フィールドの型変更と、そのポインタの明示的なリセットにあります。
-
m->waitlock
の型変更:- 変更前:
Lock* waitlock;
- 変更後:
void* waitlock;
m->waitlock
は、Mが待機状態に入る際に保持するロックを指すポインタです。このロックは、m->waitunlockf
という関数ポインタによって解放されます。問題は、waitlock
が常にLock
型のロックを指すとは限らず、異なる種類のロック(例えば、runtime.semaphores
のような他の同期プリミティブ)を指す可能性があったことです。Lock*
という型付きポインタとして宣言されていると、GoのGCは、このポインタが常にLock
構造体を指していると仮定して、そのメモリ領域をスキャンしようとします。しかし、もし実際に異なる型のデータや、既に解放されたメモリを指していた場合、GCは誤ったメモリをスキャンしたり、存在しないオブジェクトを追跡しようとしたりして、GCの正確性を損なう可能性がありました。void*
に変更することで、waitlock
は型情報を持たない汎用ポインタになります。これにより、GCはこのポインタを特定の型として解釈せず、その指す先を追跡する際に、より柔軟な(あるいはより慎重な)アプローチを取るようになります。コミットメッセージにある「+untype it because it can point to different types」は、この意図を明確に示しています。
- 変更前:
-
park0
関数内でのm->waitlock = nil;
の追加:park0
関数は、Mが待機状態に入る直前に呼び出されます。Mが待機状態に入るということは、そのMが現在保持しているリソース(特にロック)は、もはや必要ないか、別のMに引き渡されるべきであることを意味します。- 以前は、
m->waitunlockf
が呼び出されてロックが解放されても、m->waitlock
ポインタ自体はnil
にリセットされませんでした。これにより、Mが待機状態から復帰した後も、m->waitlock
は古い、無効なメモリ領域を指したままになる可能性がありました。これが「dangling typed pointer」の問題です。 m->waitlock = nil;
を追加することで、Mが待機状態に入る際に、このポインタが明示的にクリアされます。これにより、GCがこのポインタをスキャンする際に、無効なメモリ領域を追跡することを防ぎ、GCの正確性とメモリ安全性を確保します。ポインタをnil
にすることで、GCはそれが何も指していないことを認識し、安全に無視できます。
これらの変更は、Goランタイムの低レベルなメモリ管理とGCの挙動に深く関わっており、システムの安定性と信頼性を向上させる上で非常に重要です。
コアとなるコードの変更箇所
src/pkg/runtime/proc.c
--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -1178,6 +1178,7 @@ park0(G *gp)
if(m->waitunlockf) {
m->waitunlockf(m->waitlock);
m->waitunlockf = nil;
+ m->waitlock = nil;
}
if(m->lockedg) {
stoplockedm();
src/pkg/runtime/runtime.h
--- a/src/pkg/runtime/runtime.h
+++ b/src/pkg/runtime/runtime.h
@@ -317,7 +317,7 @@ struct M
bool needextram;
void* tracepc;
void (*waitunlockf)(Lock*);
- Lock* waitlock;
+ void* waitlock;
uint32 moreframesize_minalloc;
uintptr settype_buf[1024];
コアとなるコードの解説
src/pkg/runtime/proc.c
の変更
park0
関数は、現在のM(OSスレッド)をパーク(待機)状態にするためのGoランタイムの内部関数です。この関数内で、Mが待機状態に入る前に、m->waitunlockf
が設定されている場合に、関連するロックを解放する処理が行われます。
追加された行 m->waitlock = nil;
は、このロック解放処理の直後に実行されます。これにより、m->waitunlockf
によってロックが解放された後、m->waitlock
ポインタが指していたメモリ領域がもはや有効でないことを明示的に示します。ポインタをnil
に設定することで、GCがこのポインタをスキャンする際に、無効なメモリ参照を追跡することを防ぎ、GCの正確性を保証します。これは、特にGCが並行して動作するGoのような環境では非常に重要です。
src/pkg/runtime/runtime.h
の変更
M
構造体は、GoランタイムにおけるOSスレッド(マシン)の情報を保持します。
変更された行 void* waitlock;
は、M
構造体のwaitlock
フィールドの型定義を変更しています。
- 変更前は
Lock* waitlock;
であり、waitlock
が常にLock
型のオブジェクトを指すポインタであるとGCに伝えていました。 - 変更後は
void* waitlock;
となり、waitlock
が任意の型のオブジェクトを指すことができる汎用ポインタであることを示します。これにより、GCはwaitlock
が指す先の具体的な型を仮定せず、より安全な方法でこのポインタを扱えるようになります。これは、waitlock
が実際に異なる種類のロックや同期プリミティブを指す可能性があるという現実を反映したものであり、GCが誤った型情報に基づいてメモリをスキャンするのを防ぎます。
これらの変更は、Goランタイムのメモリ管理とGCの堅牢性を向上させるための、低レベルながらも重要な修正です。
関連リンク
- Go CL 8454043: https://golang.org/cl/8454043 (このコミットに対応するGoのコードレビューリンク)
参考にした情報源リンク
- Goのソースコード (特に
src/pkg/runtime/proc.c
とsrc/pkg/runtime/runtime.h
) - Goのガベージコレクションに関するドキュメントやブログ記事 (一般的なGCの概念理解のため)
- C言語における
void*
ポインタの概念 - Goランタイムスケジューラに関する資料 (M, P, Gの概念理解のため)
- Goのコードレビューシステム (CL 8454043の詳細確認のため)