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

[インデックス 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·cgocallruntime·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;
 }

これらの変更は、execvefcntl1ioctlwrite1といったシステムコールをラップする関数内で行われています。これらの関数は、子プロセスが生成された直後に実行される可能性のある低レベルの操作です。

コアとなるコードの解説

この変更の核心は、runtime·cgocallruntime·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検索による)