[インデックス 15298] ファイルの概要
このコミットは、Goランタイムにおけるシグナルハンドリングとスレッド管理に関する変更を導入しています。特に、Go以外のスレッドがGoコードを実行する際の準備として、シグナルスタックの扱いとシグナル用goroutine (gsignal) の割り当てロジックが改善されています。
コミット
commit 86d509b463d92be4ea9f51d61760d8383b1f96e4
Author: Russ Cox <rsc@golang.org>
Date: Mon Feb 18 13:43:12 2013 -0500
runtime: preparation for non-Go threads running Go code
* Handle p==nil in signalstack by setting SS_DISABLE flag.
* Make minit only allocate a signal g if there's not one already.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/7323072
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/86d509b463d92be4ea9f51d61760d8383b1f96e4
元コミット内容
runtime: preparation for non-Go threads running Go code
* Handle p==nil in signalstack by setting SS_DISABLE flag.
* Make minit only allocate a signal g if there's not one already.
変更の背景
このコミットの主な背景は、GoランタイムがGo以外のスレッド(C言語のライブラリなど、Goのスケジューラが管理しないスレッド)からGoコードを実行できるようにするための準備です。Goのランタイムは、シグナルハンドリングやgoroutineのスケジューリングなど、多くの内部処理を自身で管理しています。Go以外のスレッドがGoコードを呼び出す場合、これらの内部処理が適切に機能するように、ランタイムの挙動を調整する必要があります。
具体的には、以下の2つの課題に対処しています。
- シグナルスタックの適切な管理: シグナルは非同期に発生し、プログラムの実行フローを中断させます。Goランタイムは、シグナルハンドラが安全に実行されるように、専用のシグナルスタックを使用します。しかし、Go以外のスレッドがGoコードを実行する際に、シグナルスタックが適切に設定されていない場合、問題が発生する可能性があります。特に、
signalstack
関数にnil
が渡された場合に、シグナルスタックを無効にするSS_DISABLE
フラグを適切に設定することで、この問題を回避しようとしています。 - シグナル用goroutine (gsignal) の重複割り当ての防止: Goランタイムは、シグナルを処理するための特別なgoroutine (
gsignal
) を持っています。これは、シグナルハンドラ内でGoコードを実行するためのコンテキストを提供します。minit
関数は、各M (Machine、OSスレッドに対応) の初期化時にこのgsignal
を割り当てる役割を担っています。しかし、Go以外のスレッドがGoコードを呼び出す場合、既にgsignal
が割り当てられている可能性があり、重複して割り当てようとすると無駄なリソース消費や予期せぬ挙動を引き起こす可能性があります。このコミットでは、gsignal
が既に存在する場合は再割り当てしないように変更することで、この問題を解決しています。
これらの変更は、GoのCgo機能(C言語とGo言語の相互運用)や、Goをライブラリとして他の言語から利用するシナリオにおいて、より堅牢なランタイム動作を保証するために重要です。
前提知識の解説
このコミットを理解するためには、以下のGoランタイムの概念とOSのシグナルハンドリングに関する知識が必要です。
- Goランタイム (Go Runtime): Goプログラムの実行を管理するシステム。ガベージコレクション、goroutineのスケジューリング、チャネル通信、メモリ管理、シグナルハンドリングなど、Go言語の多くの機能はランタイムによって提供されます。
- Goroutine (G): Goランタイムによって管理される軽量なスレッド。Goプログラムの並行処理の基本単位です。
- M (Machine): OSスレッドに対応するGoランタイムの抽象化。MはP (Processor) を取得し、その上でG (goroutine) を実行します。
- P (Processor): Goランタイムのスケジューラがgoroutineを実行するために必要な論理プロセッサ。PはMに割り当てられ、MはP上でGを実行します。
- シグナル (Signal): オペレーティングシステムがプロセスに送信する非同期通知。例えば、Ctrl+Cによる割り込み (SIGINT)、不正なメモリアクセス (SIGSEGV) などがあります。
- シグナルハンドリング (Signal Handling): プロセスがシグナルを受信した際に、そのシグナルをどのように処理するかを定義するメカニズム。通常、シグナルハンドラと呼ばれる関数が実行されます。
- シグナルスタック (Signal Stack): シグナルハンドラが実行される際に使用される、通常のスタックとは別の専用スタック。これにより、シグナル発生時のスタックオーバーフローを防ぎ、シグナルハンドラの安全な実行を保証します。
sigaltstack
システムコール: シグナルスタックを設定または取得するためのシステムコール。stack_t
構造体を使用して、シグナルスタックのアドレス、サイズ、フラグなどを指定します。SS_DISABLE
フラグ:sigaltstack
システムコールで使用されるフラグの一つ。このフラグが設定されている場合、シグナルスタックは無効化され、シグナルハンドラは通常のスタックで実行されます。minit
関数: Goランタイムの内部関数で、各M (OSスレッド) が初期化される際に呼び出されます。この関数内で、シグナルハンドリングの初期設定や、シグナル処理用のgoroutine (gsignal
) の割り当てなどが行われます。gsignal
: Goランタイムがシグナルを処理するために使用する特別なgoroutine。シグナルハンドラがGoコードを呼び出す場合、このgsignal
のスタック上で実行されます。runtime·malg
: Goランタイム内部のメモリ割り当て関数で、goroutineのスタックなどのメモリを割り当てます。
技術的詳細
このコミットは、Goランタイムのシグナルハンドリングとスレッド初期化のロジックに焦点を当てています。
1. signalstack
関数におけるp==nil
のハンドリング
signalstack
関数は、シグナルスタックを設定するために使用されます。この関数は、シグナルスタックのアドレス (p
) とサイズ (n
) を引数として取ります。変更前は、p
がnil
の場合の挙動が明示的に定義されていませんでした。
このコミットでは、p == nil
の場合にst.ss_flags = SS_DISABLE;
という行が追加されました。
SS_DISABLE
は、sigaltstack
システムコールに渡されるフラグで、シグナルスタックを無効化することを意味します。つまり、signalstack
関数にnil
が渡された場合(これはシグナルスタックを使用しないことを意図している場合が多い)、シグナルハンドラは通常のスタックで実行されるようになります。
これは、Go以外のスレッドがGoコードを呼び出す際に、Goランタイムがシグナルスタックを管理しない、あるいは管理できない状況に対応するために重要です。例えば、C言語のコードからGo関数を呼び出す場合、C言語の環境ではGoランタイムが期待するシグナルスタックが設定されていない可能性があります。このような状況でsignalstack(nil, ...)
が呼ばれた際に、明示的にシグナルスタックを無効化することで、予期せぬクラッシュや挙動を防ぎます。
SS_DISABLE
の値はOSによって異なりますが、このコミットでは各OSのヘッダーファイル (os_darwin.h
, os_freebsd.h
, os_linux.h
, os_netbsd.h
, os_openbsd.h
) に#define SS_DISABLE ...
が追加され、その値が定義されています。
2. minit
関数におけるgsignal
の条件付き割り当て
minit
関数は、GoランタイムがOSスレッド (M) を初期化する際に呼び出されます。この関数内で、シグナルハンドリングの初期設定が行われ、特にシグナル処理用のgoroutine (m->gsignal
) が割り当てられます。
変更前は、m->gsignal = runtime·malg(32*1024);
という行が常に実行されていました。これは、minit
が呼び出されるたびに新しいgsignal
が割り当てられることを意味します。
このコミットでは、if(m->gsignal == nil)
という条件が追加されました。これにより、m->gsignal
がまだ割り当てられていない場合にのみ、新しいgsignal
が割り当てられるようになります。
この変更は、Go以外のスレッドがGoコードを実行するシナリオで特に重要です。Go以外のスレッドがGoランタイムにアタッチされ、minit
が呼び出される場合、そのスレッドが既にgsignal
を持っている可能性があります(例えば、以前にGoコードを実行したことがある場合など)。このような場合にgsignal
を重複して割り当てようとすると、リソースの無駄遣いや、既存のgsignal
との競合による問題が発生する可能性があります。gsignal
の重複割り当てを防ぐことで、ランタイムの安定性と効率が向上します。
3. runtime·osyield
関数の変更 (Darwinのみ)
src/pkg/runtime/thread_darwin.c
ファイルでは、runtime·osyield
関数にruntime·usleep(1);
が追加されています。これは、OSスレッドが他のスレッドにCPUを譲るためのヒントとして、1マイクロ秒のスリープを導入しています。この変更は、Go以外のスレッドがGoコードを実行する際のスケジューリングの挙動に影響を与える可能性がありますが、直接的な関連はコミットメッセージからは読み取れません。しかし、一般的にosyield
は、スレッドがCPUを積極的に手放すことで、他のスレッドが実行される機会を増やすために使用されます。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下の2点です。
-
SS_DISABLE
フラグの定義追加:src/pkg/runtime/os_darwin.h
src/pkg/runtime/os_freebsd.h
src/pkg/runtime/os_linux.h
src/pkg/runtime/os_netbsd.h
src/pkg/runtime/os_openbsd.h
これらのファイルに、各OSに応じたSS_DISABLE
の値が#define
ディレクティブで追加されています。 -
signalstack
関数におけるp==nil
のハンドリング:src/pkg/runtime/signal_darwin_386.c
src/pkg/runtime/signal_darwin_amd64.c
src/pkg/runtime/signal_freebsd_386.c
src/pkg/runtime/signal_freebsd_amd64.c
src/pkg/runtime/signal_freebsd_arm.c
src/pkg/runtime/signal_linux_386.c
src/pkg/runtime/signal_linux_amd64.c
src/pkg/runtime/signal_linux_arm.c
src/pkg/runtime/signal_netbsd_386.c
src/pkg/runtime/signal_netbsd_amd64.c
src/pkg/runtime/signal_netbsd_arm.c
src/pkg/runtime/signal_openbsd_386.c
src/pkg/runtime/signal_openbsd_amd64.c
これらのファイル内のruntime·signalstack
関数に、以下の行が追加されています。if(p == nil) st.ss_flags = SS_DISABLE;
-
minit
関数におけるgsignal
の条件付き割り当て:src/pkg/runtime/thread_darwin.c
src/pkg/runtime/thread_linux.c
src/pkg/runtime/thread_netbsd.c
src/pkg/runtime/thread_openbsd.c
これらのファイル内のruntime·minit
関数におけるm->gsignal
の割り当て部分が、以下の条件付き割り当てに変更されています。if(m->gsignal == nil) m->gsignal = runtime·malg(32*1024);
-
runtime·osyield
関数の変更 (Darwinのみ):src/pkg/runtime/thread_darwin.c
このファイル内のruntime·osyield
関数に、以下の行が追加されています。runtime·usleep(1);
コアとなるコードの解説
SS_DISABLE
フラグの定義
各OSのヘッダーファイルに追加されたSS_DISABLE
は、sigaltstack
システムコールに渡すための定数です。この定数は、シグナルスタックを無効化する(つまり、シグナルハンドラが通常のスタックで実行されるようにする)ことをOSに指示します。OSによってその値が異なるため、各OSのヘッダーファイルで個別に定義されています。
例: src/pkg/runtime/os_darwin.h
#define SS_DISABLE 4
signalstack
関数の変更
runtime·signalstack
関数は、Goランタイムがシグナルスタックを設定するために使用する内部関数です。この変更により、p
(シグナルスタックのアドレス)がnil
の場合、stack_t
構造体のss_flags
フィールドにSS_DISABLE
が設定されます。その後、runtime·sigaltstack
システムコールが呼び出され、この設定がOSに反映されます。
これにより、Goランタイムがシグナルスタックを管理しない、または管理できない状況(例えば、Go以外のスレッドからGoコードが呼び出される場合)でも、シグナルハンドリングが安全に機能するようになります。シグナルスタックが不要な場合や、外部からGoコードが呼び出される際にGoランタイムがシグナルスタックを制御できない場合に、明示的に無効化することで、予期せぬ動作を防ぎます。
例: src/pkg/runtime/signal_darwin_386.c
runtime·signalstack(byte *p, int32 n)
{
stack_t st;
st.ss_sp = p;
st.ss_size = n;
st.ss_flags = 0;
if(p == nil) // 追加された行
st.ss_flags = SS_DISABLE; // 追加された行
runtime·sigaltstack(&st, nil);
}
minit
関数の変更
runtime·minit
関数は、GoランタイムのM (OSスレッド) が初期化される際に呼び出されます。この関数内で、m->gsignal
(シグナル処理用のgoroutine)が割り当てられます。変更前は無条件に割り当てられていましたが、変更後はm->gsignal
がnil
の場合にのみ割り当てられるようになりました。
この変更は、Go以外のスレッドがGoコードを実行するシナリオにおいて、gsignal
の重複割り当てを防ぐために重要です。Go以外のスレッドがGoランタイムにアタッチされる際、minit
が複数回呼び出される可能性があります。既にgsignal
が割り当てられているMに対して再度割り当てを行おうとすると、リソースの無駄や潜在的な競合状態を引き起こす可能性があります。この条件付き割り当てにより、ランタイムの堅牢性と効率が向上します。
例: src/pkg/runtime/thread_darwin.c
void
runtime·minit(void)
{
// Initialize signal handling.
if(m->gsignal == nil) // 変更された行
m->gsignal = runtime·malg(32*1024); // OS X wants >=8K, Linux >=2K
runtime·signalstack((byte*)m->gsignal->stackguard - StackGuard, 32*1024);
runtime·sigprocmask(SIG_SETMASK, &sigset_none, nil);
}
runtime·osyield
関数の変更 (Darwinのみ)
runtime·osyield
関数は、GoランタイムがOSスレッドにCPUを譲るように要求する際に使用されます。Darwin (macOS) の実装において、この関数にruntime·usleep(1);
が追加されました。これは、1マイクロ秒の短いスリープを導入することで、OSスケジューラに他のスレッドを実行する機会を与えることを意図しています。これは、Go以外のスレッドがGoコードを実行する際のスケジューリングの挙動に影響を与える可能性があります。
例: src/pkg/runtime/thread_darwin.c
void
runtime·osyield(void)
{
runtime·usleep(1); // 追加された行
}
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
- Goランタイムのソースコード: https://github.com/golang/go/tree/master/src/runtime
sigaltstack
システムコールに関するmanページ (Linux): https://man7.org/linux/man-pages/man2/sigaltstack.2.html
参考にした情報源リンク
- Goのコミット履歴: https://github.com/golang/go/commits/master
- Goのコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージに記載されている
https://golang.org/cl/7323072
は、このGerritの変更リストへのリンクです。) - Goのランタイムに関する議論やドキュメント (GoのIssueトラッカーやメーリングリストなど)
- オペレーティングシステムのシグナルハンドリングに関するドキュメント (例: POSIX標準)
- GoのCgoに関するドキュメント: https://golang.org/cmd/cgo/
- Goの内部構造に関するブログ記事や解説記事 (例: "Go's work-stealing scheduler")