[インデックス 18855] ファイルの概要
このコミットは、Goランタイムのsrc/pkg/runtime/syscall_solaris.goc
ファイルに対する変更です。このファイルは、Solarisオペレーティングシステムにおけるシステムコール(syscall)のラッパー関数や関連するランタイムの挙動を定義しています。特に、Cgo(GoとCの相互運用機能)に関連する低レベルの関数呼び出しを扱っています。
コミット
- コミットハッシュ:
28792f5d83578d9087be5d3b2490ae8a10e189de
- 作者: Aram Hăvărneanu aram@mgk.ro
- 日付: 2014年3月13日 木曜日 18:26:01 +1100
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/28792f5d83578d9087be5d3b2490ae8a10e189de
元コミット内容
runtime: avoid runtime·cgocall in functions called by forkAndExecInChild
Calling runtime·cgocall could trigger a GC in the child while
gclock was held by the parent.
Fixes #7511
LGTM=bradfitz, dvyukov, dave
R=golang-codereviews, bradfitz, dvyukov, dave
CC=golang-codereviews, rsc
https://golang.org/cl/75210044
変更の背景
この変更の背景には、Goプログラムが子プロセスを生成し、その子プロセス内で新しいプログラムを実行する際の潜在的なデッドロック問題があります。具体的には、forkAndExecInChild
という関数によって呼び出される関数内でruntime·cgocall
を使用すると、親プロセスがgclock
(ガベージコレクションのロック)を保持している間に、子プロセスでガベージコレクション(GC)がトリガーされる可能性がありました。このような状況は、子プロセスがGCを完了するためにgclock
を待機し、親プロセスがgclock
を解放しないため、デッドロックを引き起こす可能性があります。このコミットは、この特定のシナリオでのデッドロックを回避することを目的としています。
前提知識の解説
このコミットを理解するためには、以下の概念を理解しておく必要があります。
- Cgo: Go言語とC言語のコードを相互運用するためのGoの機能です。GoプログラムからC関数を呼び出したり、CプログラムからGo関数を呼び出したりすることを可能にします。
runtime·cgocall
: Goランタイム内部で使用される関数で、GoルーチンからC関数を呼び出す際に利用されます。この関数は、C関数呼び出しの前後でGoランタイムの状態を適切に管理し、特にガベージコレクション(GC)の安全性を確保するための処理を含んでいます。runtime·asmcgocall
:runtime·cgocall
と同様にC関数を呼び出すためのGoランタイム内部関数ですが、cgocall
よりも低レベルで、GCのトリガーを避けるような特定のコンテキストで使用されます。asm
という接頭辞が示すように、アセンブリコードレベルでの最適化や特殊な挙動を持つ可能性があります。forkAndExecInChild
: Unix系システムにおけるfork()
とexec()
システムコールを組み合わせたような操作を行うGoランタイム内部の概念です。新しいプロセス(子プロセス)を生成し、その子プロセス内で指定されたプログラムを実行します。この操作は、Goプログラムが外部コマンドを実行する際などに利用されます。- ガベージコレクション (GC): Goのランタイムが自動的にメモリを管理する仕組みです。不要になったメモリ領域を自動的に解放し、メモリリークを防ぎます。GCは、特定のタイミングで実行され、その間はプログラムの実行が一時停止(ストップ・ザ・ワールド)することがあります。
gclock
: Goランタイム内部で使用されるグローバルなロックです。ガベージコレクションの実行中など、ランタイムの重要なデータ構造が変更される際に、複数のGoルーチンやプロセスが同時にアクセスして競合状態になるのを防ぐために使用されます。
技術的詳細
問題の核心は、forkAndExecInChild
によって生成された子プロセスが、親プロセスがgclock
を保持している間にruntime·cgocall
を呼び出すことでした。runtime·cgocall
は、C関数を呼び出す際にGCをトリガーする可能性のある内部処理を含んでいます。
通常のGoプログラムの実行では、GCはランタイムによって適切に管理されますが、fork()
された直後の子プロセスは特殊な状態にあります。fork()
は親プロセスのメモリ空間のコピーを作成しますが、親プロセスが保持していたロックの状態もコピーされる可能性があります。この場合、親プロセスがgclock
を保持したままfork()
し、子プロセスがそのコピーされたgclock
の状態を引き継いでしまうと、子プロセスがGCをトリガーしようとした際にgclock
の取得を試み、すでに親プロセスが保持しているためデッドロックが発生します。
このコミットでは、forkAndExecInChild
によって呼び出される関数内でruntime·cgocall
の代わりにruntime·asmcgocall
を使用することで、この問題を解決しています。runtime·asmcgocall
は、cgocall
が持つGCトリガーの可能性を回避するように設計されているため、子プロセスがGCを試みることなくC関数を安全に呼び出すことができます。これにより、親プロセスと子プロセスの間のgclock
を巡るデッドロックが回避されます。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/syscall_solaris.goc
ファイルで行われています。具体的には、以下の4箇所でruntime·cgocall
がruntime·asmcgocall
に置き換えられています。
--- a/src/pkg/runtime/syscall_solaris.goc
+++ b/src/pkg/runtime/syscall_solaris.goc
@@ -170,7 +170,7 @@ func execve(path uintptr, argv uintptr, envp uintptr) (err uintptr) {
c.fn = (void*)libc·execve;
c.n = 3;
c.args = (void*)&path;
- runtime·cgocall(runtime·asmsysvicall6, &c);
+ runtime·asmcgocall(runtime·asmsysvicall6, &c);
err = c.err;
}
@@ -193,7 +193,7 @@ func fcntl1(fd uintptr, cmd uintptr, arg uintptr) (val uintptr, err uintptr) {
c.fn = (void*)libc·fcntl;
c.n = 3;
c.args = (void*)&fd;
- runtime·cgocall(runtime·asmsysvicall6, &c);
+ runtime·asmcgocall(runtime·asmsysvicall6, &c);
err = c.err;
val = c.r1;
}
@@ -227,7 +227,7 @@ func ioctl(fd uintptr, req uintptr, arg uintptr) (err uintptr) {
c.fn = (void*)libc·ioctl;
c.n = 3;
c.args = (void*)&fd;
- runtime·cgocall(runtime·asmsysvicall6, &c);
+ runtime·asmcgocall(runtime·asmsysvicall6, &c);
err = c.err;
}
@@ -338,7 +338,7 @@ func write1(fd uintptr, buf uintptr, nbyte uintptr) (n uintptr, err uintptr) {
c.fn = (void*)libc·write;
c.n = 3;
c.args = (void*)fd;
- runtime·cgocall(runtime·asmsysvicall6, &c);
+ runtime·asmcgocall(runtime·asmsysvicall6, &c);
err = c.err;
n = c.r1;
}
これらの変更は、execve
、fcntl1
、ioctl
、write1
といったシステムコールをラップする関数内で行われています。これらの関数は、子プロセスが生成された直後に実行される可能性のある低レベルの操作です。
コアとなるコードの解説
この変更の核心は、runtime·cgocall
とruntime·asmcgocall
の使い分けにあります。
runtime·cgocall
: この関数は、GoランタイムがC関数を呼び出すための一般的なメカニズムです。Goのスケジューラやガベージコレクタと連携するように設計されており、C関数呼び出しの前後でGoルーチンの状態を保存・復元したり、GCのトリガーを考慮したりします。しかし、fork()
直後の子プロセスのような特殊な環境では、このGC関連の挙動が問題となる可能性がありました。runtime·asmcgocall
: この関数は、より低レベルのC関数呼び出しメカニズムを提供します。名前が示すように、アセンブリコードレベルで実装されており、runtime·cgocall
が持つようなGC関連のオーバーヘッドや潜在的なトリガーを回避するように設計されています。fork()
直後の子プロセスでは、GCが安全に実行できない可能性があるため、GCをトリガーしないasmcgocall
を使用することが適切です。これにより、子プロセスがC関数を呼び出す際に、親プロセスが保持しているgclock
との競合を避けることができます。
この変更により、Solaris環境でGoプログラムが子プロセスを生成し、その子プロセス内でシステムコールを実行する際の安定性と信頼性が向上しました。
関連リンク
- Go Code Review (CL): https://golang.org/cl/75210044
- Go Issue: コミットメッセージには
Fixes #7511
とありますが、現在のGoのGitHubリポジトリでは直接的なIssue #7511は見つかりませんでした。これは、Issueトラッカーの移行や番号の再割り当てなど、過去の変更履歴によるものかもしれません。しかし、CLの議論から、この変更が特定のデッドロック問題に対処していることは明らかです。
参考にした情報源リンク
- コミットデータ:
/home/orange/Project/comemo/commit_data/18855.txt
- Go Code Review 75210044 の要約情報 (Web検索による)