[インデックス 18109] ファイルの概要
このコミットは、GoランタイムのCgo関連コードにおけるシグナルマスク操作の修正に関するものです。具体的には、マルチスレッド環境でのsigprocmask
の使用が未定義動作を引き起こす可能性があるため、よりスレッドセーフなpthread_sigmask
に置き換える変更が行われています。
コミット
commit 41183d015d17ab537287599e2bf72ca1cdeafb55
Author: S.Çağlar Onur <caglar@10ur.org>
Date: Sun Dec 22 08:55:29 2013 -0800
cgo/runtime: replace sigprocmask with pthread_sigmask.
sigprocmask use in a multithreaded environment is undefined so replace it with pthread_sigmask.
Fixes #6811.
R=jsing, iant
CC=golang-codereviews, golang-dev
https://golang.org/cl/30460043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/41183d015d17ab537287599e2bf72ca1cdeafb55
元コミット内容
このコミットの目的は、「マルチスレッド環境でのsigprocmask
の使用が未定義であるため、pthread_sigmask
に置き換える」ことです。これはGoのCgo(C言語との相互運用)ランタイムにおけるシグナルハンドリングの堅牢性を向上させるための変更です。
変更の背景
GoプログラムがCgoを介してCコードを呼び出す際、GoランタイムはCライブラリが適切に動作するための環境を整える必要があります。これには、スレッドの管理やシグナルハンドリングが含まれます。
従来のsigprocmask
関数は、プロセス全体のシグナルマスクを設定するために設計されています。しかし、POSIXスレッド(pthreads)が導入されたマルチスレッド環境では、各スレッドが独自のシグナルマスクを持つことが一般的です。sigprocmask
をマルチスレッドプログラム内で使用すると、どのスレッドのシグナルマスクが変更されるか、あるいはプロセス全体のシグナルマスクにどのように影響するかについて、未定義の動作を引き起こす可能性があります。これは、特にGoランタイムのように複数のゴルーチン(Goの軽量スレッド)がOSのスレッドにマッピングされる環境では、予測不能なシグナル処理のバグにつながる可能性があります。
この問題は、GoのIssue #6811として報告されており、このコミットはその問題を解決するために作成されました。
前提知識の解説
シグナル (Signals)
Unix系OSにおけるシグナルは、プロセスに対して非同期的にイベントを通知するメカニズムです。例えば、Ctrl+Cを押すとSIGINT
シグナルが、不正なメモリアクセスが発生するとSIGSEGV
シグナルがプロセスに送信されます。プロセスはこれらのシグナルを受信し、デフォルトの動作(プロセス終了など)を実行するか、カスタムのシグナルハンドラを登録して特定の処理を行うことができます。
シグナルマスク (Signal Mask)
シグナルマスクは、プロセスまたはスレッドが現在ブロックしているシグナルのセットを定義します。シグナルがブロックされている間は、そのシグナルが配送されてもすぐに処理されず、シグナルマスクから解除されるまで保留されます。
sigprocmask
sigprocmask
は、プロセス全体のシグナルマスクを操作するためのPOSIX関数です。この関数は、指定されたシグナルセットを現在のシグナルマスクに追加、削除、または置き換えることができます。しかし、マルチスレッド環境では、この関数がどのスレッドのシグナルマスクに影響を与えるか、あるいはプロセス全体のシグナルマスクにどのように影響するかは、実装依存であり、未定義の動作を引き起こす可能性があります。
pthread_sigmask
pthread_sigmask
は、特定の呼び出し元スレッドのシグナルマスクを操作するためのPOSIXスレッド(pthreads)関数です。これにより、マルチスレッドプログラムにおいて、各スレッドが独立して自身のシグナルマスクを管理できるようになります。これは、スレッドごとに異なるシグナル処理ロジックが必要な場合に特に重要です。
Cgo
Cgoは、GoプログラムからC言語のコードを呼び出すためのGoの機能です。GoのランタイムとCライブラリの間でデータの受け渡しや関数の呼び出しを可能にします。Cgoを使用する際には、GoのスケジューラとCライブラリのスレッドモデルとの間で、シグナルハンドリングのような低レベルのOS機能の連携が重要になります。
技術的詳細
このコミットは、GoランタイムのCgo部分、特に新しいOSスレッドを作成する際にシグナルマスクを一時的に変更する箇所に焦点を当てています。
Goランタイムは、Cgo呼び出しのために新しいOSスレッドを生成する際に、シグナルを一時的にブロックして、スレッドの初期化が安全に行われるようにします。具体的には、SIG_SETMASK
操作を使用して、すべてのシグナルをブロックするシグナルセット(ign
)を現在のシグナルマスクに設定し、元のシグナルマスク(oset
)を保存します。スレッドの作成が完了した後、保存しておいた元のシグナルマスクに戻します。
問題は、このシグナルマスク操作にsigprocmask
を使用していた点です。Goランタイムは内部的に複数のOSスレッドを使用し、それぞれがGoのゴルーチンを実行します。Cgoが新しいOSスレッドを生成する際にsigprocmask
を使用すると、その操作が意図しない他のGoランタイムスレッドのシグナルマスクに影響を与えたり、競合状態を引き起こしたりする可能性がありました。
pthread_sigmask
は、呼び出し元のスレッドのシグナルマスクのみを操作するため、この問題を解決します。これにより、Cgoが生成する新しいOSスレッドの初期化中にシグナルマスクを安全に操作できるようになり、他のGoランタイムスレッドのシグナル処理に悪影響を与えることなく、予測可能な動作が保証されます。
この変更は、DragonFly BSD, FreeBSD, Linux, NetBSD, OpenBSD の各アーキテクチャ(386, amd64, arm)向けのCgoランタイムファイルに適用されています。これは、これらのOSがPOSIXスレッドモデルを採用しており、pthread_sigmask
が利用可能であることを示しています。
コアとなるコードの変更箇所
変更は、src/pkg/runtime/cgo/
ディレクトリ以下の、各OSおよびアーキテクチャ固有のCgoランタイムファイルにわたっています。
例として、src/pkg/runtime/cgo/gcc_dragonfly_386.c
の変更を見てみましょう。
--- a/src/pkg/runtime/cgo/gcc_dragonfly_386.c
+++ b/src/pkg/runtime/cgo/gcc_dragonfly_386.c
@@ -36,14 +36,14 @@ _cgo_sys_thread_start(ThreadStart *ts)
int err;
SIGFILLSET(ign);
- sigprocmask(SIG_SETMASK, &ign, &oset);
+ pthread_sigmask(SIG_SETMASK, &ign, &oset);
pthread_attr_init(&attr);
pthread_attr_getstacksize(&attr, &size);
ts->g->stackguard = size;
err = pthread_create(&p, &attr, threadentry, ts);
- sigprocmask(SIG_SETMASK, &oset, nil);
+ pthread_sigmask(SIG_SETMASK, &oset, nil);
if (err != 0) {
fprintf(stderr, "runtime/cgo: pthread_create failed: %s\\n", strerror(err));
同様の変更が、以下のファイルにも適用されています。
src/pkg/runtime/cgo/gcc_dragonfly_amd64.c
src/pkg/runtime/cgo/gcc_freebsd_386.c
src/pkg/runtime/cgo/gcc_freebsd_amd64.c
src/pkg/runtime/cgo/gcc_linux_386.c
src/pkg/runtime/cgo/gcc_linux_amd64.c
src/pkg/runtime/cgo/gcc_netbsd_386.c
src/pkg/runtime/cgo/gcc_netbsd_amd64.c
src/pkg/runtime/cgo/gcc_netbsd_arm.c
src/pkg/runtime/cgo/gcc_openbsd_386.c
src/pkg/runtime/cgo/gcc_openbsd_amd64.c
コアとなるコードの解説
変更されたコードは、_cgo_sys_thread_start
という関数内にあります。この関数は、Cgoが新しいOSスレッドを起動する際に呼び出されるエントリポイントです。
-
SIGFILLSET(ign);
ign
というsigset_t
型の変数に、すべてのシグナルを含むセットを初期化します。これは、一時的にすべてのシグナルをブロックするために使用されます。
-
sigprocmask(SIG_SETMASK, &ign, &oset);
- 変更前: ここで
sigprocmask
が使用されていました。SIG_SETMASK
は、現在のシグナルマスクをign
で完全に置き換え、元のシグナルマスクをoset
に保存します。マルチスレッド環境では、この操作がプロセス全体に影響を与える可能性があり、未定義動作の原因となっていました。
- 変更前: ここで
-
pthread_sigmask(SIG_SETMASK, &ign, &oset);
- 変更後:
pthread_sigmask
に置き換えられました。この関数は、呼び出し元のスレッドのシグナルマスクのみを操作します。これにより、新しく作成されるスレッドの初期化中に、そのスレッド自身のシグナルマスクを安全に設定し、他のスレッドに影響を与えることなくシグナルをブロックできるようになります。
- 変更後:
-
pthread_create(&p, &attr, threadentry, ts);
- 新しいPOSIXスレッドを作成します。このスレッドは
threadentry
関数を実行します。
- 新しいPOSIXスレッドを作成します。このスレッドは
-
sigprocmask(SIG_SETMASK, &oset, nil);
- 変更前: スレッド作成後、元のシグナルマスクを
sigprocmask
で復元していました。
- 変更前: スレッド作成後、元のシグナルマスクを
-
pthread_sigmask(SIG_SETMASK, &oset, nil);
- 変更後:
pthread_sigmask
に置き換えられました。これにより、新しく作成されたスレッドのシグナルマスクが、スレッド作成前の元の状態に安全に戻されます。
- 変更後:
この変更により、GoのCgoランタイムがOSスレッドを扱う際のシグナルハンドリングが、マルチスレッド環境のPOSIX標準に準拠し、より堅牢で予測可能なものになりました。
関連リンク
- Go Issue #6811: https://github.com/golang/go/issues/6811
- Go Code Review 30460043: https://golang.org/cl/30460043
参考にした情報源リンク
sigprocmask
man page: https://man7.org/linux/man-pages/man2/sigprocmask.2.htmlpthread_sigmask
man page: https://man7.org/linux/man-pages/man3/pthread_sigmask.3.html- POSIX Threads (pthreads) Signal Handling: https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_sigmask.html
- Go Cgo Documentation: https://go.dev/blog/c-go-cgo (Cgoの基本的な概念理解のため)
- Go Runtime Source Code (general understanding): https://github.com/golang/go/tree/master/src/runtime
- Go Runtime Cgo Source Code (general understanding): https://github.com/golang/go/tree/master/src/runtime/cgo
- Stack Overflow and other technical forums for
sigprocmask
vspthread_sigmask
in multithreaded environments.