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

[インデックス 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スレッドを起動する際に呼び出されるエントリポイントです。

  1. SIGFILLSET(ign);

    • ignというsigset_t型の変数に、すべてのシグナルを含むセットを初期化します。これは、一時的にすべてのシグナルをブロックするために使用されます。
  2. sigprocmask(SIG_SETMASK, &ign, &oset);

    • 変更前: ここでsigprocmaskが使用されていました。SIG_SETMASKは、現在のシグナルマスクをignで完全に置き換え、元のシグナルマスクをosetに保存します。マルチスレッド環境では、この操作がプロセス全体に影響を与える可能性があり、未定義動作の原因となっていました。
  3. pthread_sigmask(SIG_SETMASK, &ign, &oset);

    • 変更後: pthread_sigmaskに置き換えられました。この関数は、呼び出し元のスレッドのシグナルマスクのみを操作します。これにより、新しく作成されるスレッドの初期化中に、そのスレッド自身のシグナルマスクを安全に設定し、他のスレッドに影響を与えることなくシグナルをブロックできるようになります。
  4. pthread_create(&p, &attr, threadentry, ts);

    • 新しいPOSIXスレッドを作成します。このスレッドはthreadentry関数を実行します。
  5. sigprocmask(SIG_SETMASK, &oset, nil);

    • 変更前: スレッド作成後、元のシグナルマスクをsigprocmaskで復元していました。
  6. pthread_sigmask(SIG_SETMASK, &oset, nil);

    • 変更後: pthread_sigmaskに置き換えられました。これにより、新しく作成されたスレッドのシグナルマスクが、スレッド作成前の元の状態に安全に戻されます。

この変更により、GoのCgoランタイムがOSスレッドを扱う際のシグナルハンドリングが、マルチスレッド環境のPOSIX標準に準拠し、より堅牢で予測可能なものになりました。

関連リンク

参考にした情報源リンク