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

[インデックス 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つの課題に対処しています。

  1. シグナルスタックの適切な管理: シグナルは非同期に発生し、プログラムの実行フローを中断させます。Goランタイムは、シグナルハンドラが安全に実行されるように、専用のシグナルスタックを使用します。しかし、Go以外のスレッドがGoコードを実行する際に、シグナルスタックが適切に設定されていない場合、問題が発生する可能性があります。特に、signalstack関数にnilが渡された場合に、シグナルスタックを無効にするSS_DISABLEフラグを適切に設定することで、この問題を回避しようとしています。
  2. シグナル用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) を引数として取ります。変更前は、pnilの場合の挙動が明示的に定義されていませんでした。

このコミットでは、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点です。

  1. 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ディレクティブで追加されています。

  2. 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;
    
  3. 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);
    
  4. 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->gsignalnilの場合にのみ割り当てられるようになりました。

この変更は、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://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")