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

[インデックス 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フィールドの型変更と、そのポインタの明示的なリセットにあります。

  1. 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」は、この意図を明確に示しています。
  2. 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のソースコード (特にsrc/pkg/runtime/proc.csrc/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フィールドの型変更と、そのポインタの明示的なリセットにあります。

  1. 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」は、この意図を明確に示しています。
  2. 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のソースコード (特にsrc/pkg/runtime/proc.csrc/pkg/runtime/runtime.h)
  • Goのガベージコレクションに関するドキュメントやブログ記事 (一般的なGCの概念理解のため)
  • C言語におけるvoid*ポインタの概念
  • Goランタイムスケジューラに関する資料 (M, P, Gの概念理解のため)
  • Goのコードレビューシステム (CL 8454043の詳細確認のため)