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

[インデックス 12172] ファイルの概要

このコミットは、Go言語のランタイムがmacOS (Darwin) 環境でシグナルを適切にマスクするよう修正するものです。特に、新しいOSスレッドを作成する際やランタイムの初期化時に、シグナルハンドリングの競合状態や予期せぬ動作を防ぐための重要な変更が含まれています。

コミット

commit 224f05ba8848d2ef897705a5d587f7918037d6b7
Author: Russ Cox <rsc@golang.org>
Date:   Thu Feb 23 14:44:06 2012 -0500

    runtime: darwin signal masking
    
    Fixes #3101 (darwin).
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/5693044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/224f05ba8848d2ef897705a5d587f7918037d6b7

元コミット内容

GoランタイムにおけるDarwin (macOS) 環境でのシグナルマスキングに関する修正。Issue #3101 (Darwin関連) を解決します。

変更の背景

このコミットは、GoランタイムがmacOS上で新しいOSスレッドを作成する際に発生する可能性のあるシグナル関連の問題に対処するために行われました。具体的には、GoのIssue #3101で報告されたバグを修正することを目的としています。

Goランタイムは、GoルーチンをOSスレッドにマッピングして実行します。新しいOSスレッドが作成される際、そのスレッドが予期せぬシグナルを受信したり、シグナルハンドラが正しく設定される前にシグナルが配送されたりすると、プログラムのクラッシュやデッドロックなど、予測不能な動作を引き起こす可能性があります。特に、シグナルハンドラの設定はスレッドセーフに行われる必要があり、その間にシグナルが配送されることを防ぐために、一時的にシグナルをブロック(マスク)するメカニズムが必要となります。

この修正は、GoランタイムがOSスレッドのライフサイクル管理において、より堅牢なシグナルハンドリングを保証するためのものです。

前提知識の解説

シグナル (Signals)

シグナルは、Unix系OSにおいてプロセス間通信や非同期イベント通知のために使用されるソフトウェア割り込みの一種です。特定のイベント(例: Ctrl+Cによる割り込み、不正なメモリアクセス、子プロセスの終了など)が発生した際に、OSがプロセスにシグナルを送信します。プロセスは、シグナルを受信すると、デフォルトの動作を実行するか、事前に登録されたシグナルハンドラ関数を実行するか、シグナルを無視するかを選択できます。

一般的なシグナルには以下のようなものがあります。

  • SIGINT: 割り込みシグナル(通常Ctrl+Cで発生)
  • SIGTERM: プロセス終了要求
  • SIGKILL: 強制終了(捕捉・無視・マスク不可)
  • SIGSEGV: セグメンテーション違反(不正なメモリアクセス)
  • SIGPIPE: パイプへの書き込み中に読み取り側が終了した場合

シグナルマスキング (Signal Masking)

シグナルマスキングとは、特定のシグナルがプロセスやスレッドに配送されるのを一時的にブロックするメカニズムです。シグナルマスクは、ブロックしたいシグナルの集合(ビットマスク)で表現されます。スレッドのシグナルマスクに特定のシグナルが含まれている場合、そのシグナルはブロックされ、配送が保留されます。シグナルマスクからシグナルが削除されると、保留されていたシグナルが配送されます。

シグナルマスキングは、クリティカルセクション(共有リソースへのアクセスなど、アトミックに実行されるべきコード領域)において、非同期に発生するシグナルによって処理が中断されるのを防ぐために重要です。例えば、シグナルハンドラを設定する際や、新しいスレッドを初期化する際に、一時的にすべてのシグナルをマスクすることで、競合状態を回避し、安全な操作を保証できます。

sigprocmask システムコール

sigprocmaskは、現在のスレッドのシグナルマスクを検査または変更するために使用されるPOSIX標準のシステムコールです。 そのプロトタイプは通常以下のようになります: int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

  • how: シグナルマスクの変更方法を指定します。
    • SIG_BLOCK: setで指定されたシグナルを現在のマスクに追加します。
    • SIG_UNBLOCK: setで指定されたシグナルを現在のマスクから削除します。
    • SIG_SETMASK: 現在のマスクをsetで指定されたシグナル集合に置き換えます。
  • set: 新しいシグナルマスクとして設定するシグナルの集合へのポインタ。
  • oldset: 変更前のシグナルマスクを格納するためのポインタ。不要な場合はNULLを指定します。

このシステムコールは、特にマルチスレッド環境において、スレッドごとのシグナルハンドリングの整合性を保つ上で不可欠です。

技術的詳細

このコミットは、GoランタイムがmacOS上で新しいOSスレッドを生成する際に、シグナルハンドリングの安全性を向上させることを目的としています。具体的には、sigprocmaskシステムコールをGoランタイムから呼び出せるようにし、新しいスレッドの作成前後にシグナルマスクを適切に設定することで、競合状態を防ぎます。

変更のポイントは以下の通りです。

  1. os_darwin.hの変更:

    • Sigset型(シグナル集合を表すuint32)が定義されました。これは、シグナルマスクをビットマスクとして扱うための型です。
    • runtime·sigprocmask関数のプロトタイプが追加されました。これは、GoランタイムがC言語のコードからsigprocmaskシステムコールを呼び出すための宣言です。
    • SIG_SETMASKマクロが定義されました。これはsigprocmaskシステムコールで使用されるhow引数の値(3)です。
  2. sys_darwin_386.sおよびsys_darwin_amd64.sの変更:

    • runtime·sigprocmask関数のアセンブリ実装が追加されました。これは、Goランタイムが直接sigprocmaskシステムコールを呼び出すためのラッパーです。
    • 32-bit (i386) および 64-bit (amd64) アーキテクチャの両方に対応する実装が提供されています。
    • これらのアセンブリコードは、適切なシステムコール番号(48 for sigprocmask on Darwin)を設定し、レジスタに引数をロードしてINT $0x80 (32-bit) または SYSCALL (64-bit) 命令を実行することで、カーネルにシステムコールを要求します。
  3. thread_darwin.cの変更:

    • sigset_allsigset_noneという静的変数が追加されました。これらはそれぞれ、すべてのシグナルをブロックするマスクと、どのシグナルもブロックしないマスクを表します。
    • runtime·newosproc関数(新しいOSスレッドを作成するGoランタイムの内部関数)内で、runtime·bsdthread_createを呼び出す前にsigset_allでシグナルを完全にマスクし、スレッド作成後に元のシグナルマスクに戻す処理が追加されました。これにより、新しいスレッドが初期化されるまでの間に予期せぬシグナルが配送されるのを防ぎます。
    • runtime·minit関数(Goランタイムの初期化関数)の最後に、sigset_noneでシグナルマスクをクリアする処理が追加されました。これは、ランタイムの初期化が完了した後に、すべてのシグナルが適切に配送されるようにするためです。

これらの変更により、GoランタイムはmacOS上でより安全かつ予測可能なシグナルハンドリングを実現し、特にスレッド作成時の競合状態による問題を回避できるようになります。

コアとなるコードの変更箇所

src/pkg/runtime/os_darwin.h

--- a/src/pkg/runtime/os_darwin.h
+++ b/src/pkg/runtime/os_darwin.h
@@ -20,6 +20,9 @@ uint32	runtime·mach_thread_self(void);
 uint32	runtime·mach_thread_self(void);
 int32	runtime·sysctl(uint32*, uint32, byte*, uintptr*, byte*, uintptr);
 
+typedef uint32 Sigset;
+void	runtime·sigprocmask(int32, Sigset*, Sigset*);
+
 struct Sigaction;
 void	runtime·sigaction(uintptr, struct Sigaction*, struct Sigaction*);
 void	runtime·setsig(int32, void(*)(int32, Siginfo*, void*, G*), bool);
@@ -35,3 +38,4 @@ void	runtime·raisesigpipe(void);
 
 #define	NSIG 32
 #define	SI_USER	0  /* empirically true, but not what headers say */
+#define	SIG_SETMASK 3

src/pkg/runtime/sys_darwin_386.s

--- a/src/pkg/runtime/sys_darwin_386.s
+++ b/src/pkg/runtime/sys_darwin_386.s
@@ -106,6 +106,13 @@ TEXT runtime·nanotime(SB), 7, $32
 	MOVL	DX, 4(DI)
 	RET
 
+TEXT runtime·sigprocmask(SB),7,$0
+	MOVL	$48, AX
+	INT	$0x80
+	JAE	2(PC)
+	CALL	runtime·notok(SB)
+	RET
+
 TEXT runtime·sigaction(SB),7,$0
 	MOVL	$46, AX
 	INT	$0x80

src/pkg/runtime/sys_darwin_amd64.s

--- a/src/pkg/runtime/sys_darwin_amd64.s
+++ b/src/pkg/runtime/sys_darwin_amd64.s
@@ -92,6 +92,16 @@ TEXT runtime·nanotime(SB), 7, $32
 	ADDQ	DX, AX
 	RET
 
+TEXT runtime·sigprocmask(SB),7,$0
+	MOVL	8(SP), DI
+	MOVQ	16(SP), SI
+	MOVQ	24(SP), DX
+	MOVL	$(0x2000000+48), AX	// syscall entry
+	SYSCALL
+	JCC	2(PC)
+	CALL	runtime·notok(SB)
+	RET
+
 TEXT runtime·sigaction(SB),7,$0
 	MOVL	8(SP), DI		// arg 1 sig
 	MOVQ	16(SP), SI		// arg 2 act

src/pkg/runtime/thread_darwin.c

--- a/src/pkg/runtime/thread_darwin.c
+++ b/src/pkg/runtime/thread_darwin.c
@@ -9,6 +9,9 @@
 
 extern SigTab runtime·sigtab[];
 
+static Sigset sigset_all = ~(Sigset)0;
+static Sigset sigset_none;
+
 static void
 unimplemented(int8 *name)
 {
@@ -70,13 +73,19 @@ void
 runtime·newosproc(M *m, G *g, void *stk, void (*fn)(void))
 {
 	int32 errno;
+	Sigset oset;
 
 	m->tls[0] = m->id;	// so 386 asm can find it
 	if(0){
 		runtime·printf("newosproc stk=%p m=%p g=%p fn=%p id=%d/%d ostk=%p\n",
 			stk, m, g, fn, m->id, m->tls[0], &m);
 	}
-	if((errno = runtime·bsdthread_create(stk, m, g, fn)) < 0) {
+
+	runtime·sigprocmask(SIG_SETMASK, &sigset_all, &oset);
+	errno = runtime·bsdthread_create(stk, m, g, fn);
+	runtime·sigprocmask(SIG_SETMASK, &oset, nil);
+	
+	if(errno < 0) {
 		runtime·printf("runtime: failed to create new OS thread (have %d already; errno=%d)\\n", runtime·mcount(), -errno);
 		runtime·throw("runtime.newosproc");
 	}
@@ -89,6 +98,7 @@ runtime·minit(void)
 	// Initialize signal handling.
 	m->gsignal = runtime·malg(32*1024);	// OS X wants >=8K, Linux >=2K
 	runtime·signalstack(m->gsignal->stackguard - StackGuard, 32*1024);
+	runtime·sigprocmask(SIG_SETMASK, &sigset_none, nil);
 }
 
 // Mach IPC, to get at semaphores

コアとなるコードの解説

src/pkg/runtime/os_darwin.h

  • typedef uint32 Sigset;: シグナルマスクを表現するためのSigset型を定義しています。uint32は32ビットの符号なし整数であり、各ビットが特定のシグナルに対応します。
  • void runtime·sigprocmask(int32, Sigset*, Sigset*);: sigprocmaskシステムコールをGoランタイムから呼び出すためのC言語関数プロトタイプを宣言しています。これは、アセンブリで実装されるruntime·sigprocmask関数に対応します。
  • #define SIG_SETMASK 3: sigprocmaskhow引数に渡す定数を定義しています。SIG_SETMASKは、現在のシグナルマスクを完全に置き換えることを意味します。

src/pkg/runtime/sys_darwin_386.s および src/pkg/runtime/sys_darwin_amd64.s

これらのファイルは、それぞれ32ビットおよび64ビットのDarwinアーキテクチャ向けにruntime·sigprocmask関数のアセンブリ実装を提供します。

  • TEXT runtime·sigprocmask(SB),7,$0: runtime·sigprocmask関数の開始を宣言しています。SBはスタックベースレジスタ、7はフレームポインタのオフセット、$0はスタックフレームサイズを示します。
  • MOVL $48, AX (386) / MOVL $(0x2000000+48), AX (amd64): システムコール番号48AXレジスタにロードしています。Darwinでは、sigprocmaskのシステムコール番号は48です。64ビット版では、0x2000000はシステムコールエントリポイントのオフセットを示します。
  • INT $0x80 (386) / SYSCALL (amd64): システムコールを実行します。INT $0x80は32ビットLinux/Unix系システムで一般的なソフトウェア割り込みによるシステムコール呼び出し、SYSCALLは64ビットシステムで高速なシステムコール呼び出しに使用されます。
  • JAE 2(PC) / JCC 2(PC): システムコールが成功したかどうかをチェックします。JAE (Jump if Above or Equal) または JCC (Jump if Carry Clear) は、キャリーフラグがクリアされている(エラーがない)場合にジャンプします。
  • CALL runtime·notok(SB): システムコールが失敗した場合に、エラーハンドリング関数runtime·notokを呼び出します。
  • RET: 関数から戻ります。

これらのアセンブリコードは、Goランタイムが直接OSのsigprocmask機能を利用するための低レベルなインターフェースを提供します。

src/pkg/runtime/thread_darwin.c

  • static Sigset sigset_all = ~(Sigset)0;: すべてのシグナルをブロックするためのシグナルマスクsigset_allを初期化しています。~(Sigset)0は、すべてのビットがセットされた値(つまり、すべてのシグナルがマスクされる状態)を生成します。
  • static Sigset sigset_none;: どのシグナルもブロックしないためのシグナルマスクsigset_noneを宣言しています。これはデフォルトでゼロ初期化され、すべてのビットがクリアされた状態になります。
  • runtime·newosproc関数内の変更:
    • Sigset oset;: 変更前のシグナルマスクを保存するための変数osetを宣言しています。
    • runtime·sigprocmask(SIG_SETMASK, &sigset_all, &oset);: 新しいOSスレッドを作成する直前に、現在のスレッドのシグナルマスクをsigset_all(すべてのシグナルをブロック)に設定し、元のマスクをosetに保存します。これにより、runtime·bsdthread_createが実行されている間に、予期せぬシグナルが配送されるのを防ぎます。
    • errno = runtime·bsdthread_create(stk, m, g, fn);: 実際に新しいOSスレッドを作成する関数を呼び出します。
    • runtime·sigprocmask(SIG_SETMASK, &oset, nil);: スレッド作成後、元のシグナルマスクを復元します。これにより、スレッド作成のクリティカルセクションが終了し、通常のシグナルハンドリングに戻ります。
  • runtime·minit関数内の変更:
    • runtime·sigprocmask(SIG_SETMASK, &sigset_none, nil);: ランタイムの初期化処理の最後に、現在のスレッドのシグナルマスクをsigset_none(どのシグナルもブロックしない)に設定します。これは、ランタイムが完全に初期化された後、すべてのシグナルが正常に処理されることを保証するためです。

これらの変更は、GoランタイムがmacOS上でスレッドの生成と初期化を行う際のシグナルハンドリングの堅牢性を大幅に向上させ、潜在的な競合状態やクラッシュを防ぐことに貢献しています。

関連リンク

参考にした情報源リンク

  • POSIX sigprocmask man page: (OSのmanページを参照)
  • Unix Signals: https://en.wikipedia.org/wiki/Unix_signal
  • Go Runtime Source Code: (Goの公式GitHubリポジトリ)
  • Darwin/macOS System Calls: (関連するApple Developer DocumentationやXNUソースコード)

[インデックス 12172] ファイルの概要

このコミットは、Go言語のランタイムがmacOS (Darwin) 環境でシグナルを適切にマスクするよう修正するものです。特に、新しいOSスレッドを作成する際やランタイムの初期化時に、シグナルハンドリングの競合状態や予期せぬ動作を防ぐための重要な変更が含まれています。

コミット

commit 224f05ba8848d2ef897705a5d587f7918037d6b7
Author: Russ Cox <rsc@golang.org>
Date:   Thu Feb 23 14:44:06 2012 -0500

    runtime: darwin signal masking
    
    Fixes #3101 (darwin).
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/5693044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/224f05ba8848d2ef897705a5d587f7918037d6b7

元コミット内容

GoランタイムにおけるDarwin (macOS) 環境でのシグナルマスキングに関する修正。Issue #3101 (Darwin関連) を解決します。

変更の背景

このコミットは、GoランタイムがmacOS上で新しいOSスレッドを作成する際に発生する可能性のあるシグナル関連の問題に対処するために行われました。具体的には、GoのIssue #3101で報告されたバグを修正することを目的としています。

Goランタイムは、GoルーチンをOSスレッドにマッピングして実行します。新しいOSスレッドが作成される際、そのスレッドが予期せぬシグナルを受信したり、シグナルハンドラが正しく設定される前にシグナルが配送されたりすると、プログラムのクラッシュやデッドロックなど、予測不能な動作を引き起こす可能性があります。特に、シグナルハンドラの設定はスレッドセーフに行われる必要があり、その間にシグナルが配送されることを防ぐために、一時的にシグナルをブロック(マスク)するメカニズムが必要となります。

この修正は、GoランタイムがOSスレッドのライフサイクル管理において、より堅牢なシグナルハンドリングを保証するためのものです。

前提知識の解説

シグナル (Signals)

シグナルは、Unix系OSにおいてプロセス間通信や非同期イベント通知のために使用されるソフトウェア割り込みの一種です。特定のイベント(例: Ctrl+Cによる割り込み、不正なメモリアクセス、子プロセスの終了など)が発生した際に、OSがプロセスにシグナルを送信します。プロセスは、シグナルを受信すると、デフォルトの動作を実行するか、事前に登録されたシグナルハンドラ関数を実行するか、シグナルを無視するかを選択できます。

一般的なシグナルには以下のようなものがあります。

  • SIGINT: 割り込みシグナル(通常Ctrl+Cで発生)
  • SIGTERM: プロセス終了要求
  • SIGKILL: 強制終了(捕捉・無視・マスク不可)
  • SIGSEGV: セグメンテーション違反(不正なメモリアクセス)
  • SIGPIPE: パイプへの書き込み中に読み取り側が終了した場合

シグナルマスキング (Signal Masking)

シグナルマスキングとは、特定のシグナルがプロセスやスレッドに配送されるのを一時的にブロックするメカニズムです。シグナルマスクは、ブロックしたいシグナルの集合(ビットマスク)で表現されます。スレッドのシグナルマスクに特定のシグナルが含まれている場合、そのシグナルはブロックされ、配送が保留されます。シグナルマスクからシグナルが削除されると、保留されていたシグナルが配送されます。

シグナルマスキングは、クリティカルセクション(共有リソースへのアクセスなど、アトミックに実行されるべきコード領域)において、非同期に発生するシグナルによって処理が中断されるのを防ぐために重要です。例えば、シグナルハンドラを設定する際や、新しいスレッドを初期化する際に、一時的にすべてのシグナルをマスクすることで、競合状態を回避し、安全な操作を保証できます。

sigprocmask システムコール

sigprocmaskは、現在のスレッドのシグナルマスクを検査または変更するために使用されるPOSIX標準のシステムコールです。 そのプロトタイプは通常以下のようになります: int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

  • how: シグナルマスクの変更方法を指定します。
    • SIG_BLOCK: setで指定されたシグナルを現在のマスクに追加します。
    • SIG_UNBLOCK: setで指定されたシグナルを現在のマスクから削除します。
    • SIG_SETMASK: 現在のマスクをsetで指定されたシグナル集合に置き換えます。
  • set: 新しいシグナルマスクとして設定するシグナルの集合へのポインタ。
  • oldset: 変更前のシグナルマスクを格納するためのポインタ。不要な場合はNULLを指定します。

このシステムコールは、特にマルチスレッド環境において、スレッドごとのシグナルハンドリングの整合性を保つ上で不可欠です。

技術的詳細

このコミットは、GoランタイムがmacOS上で新しいOSスレッドを生成する際に、シグナルハンドリングの安全性を向上させることを目的としています。具体的には、sigprocmaskシステムコールをGoランタイムから呼び出せるようにし、新しいスレッドの作成前後にシグナルマスクを適切に設定することで、競合状態を防ぎます。

変更のポイントは以下の通りです。

  1. os_darwin.hの変更:

    • Sigset型(シグナル集合を表すuint32)が定義されました。これは、シグナルマスクをビットマスクとして扱うための型です。
    • runtime·sigprocmask関数のプロトタイプが追加されました。これは、GoランタイムがC言語のコードからsigprocmaskシステムコールを呼び出すための宣言です。
    • SIG_SETMASKマクロが定義されました。これはsigprocmaskシステムコールで使用されるhow引数の値(3)です。
  2. sys_darwin_386.sおよびsys_darwin_amd64.sの変更:

    • runtime·sigprocmask関数のアセンブリ実装が追加されました。これは、Goランタイムが直接sigprocmaskシステムコールを呼び出すためのラッパーです。
    • 32-bit (i386) および 64-bit (amd64) アーキテクチャの両方に対応する実装が提供されています。
    • これらのアセンブリコードは、適切なシステムコール番号(48 for sigprocmask on Darwin)を設定し、レジスタに引数をロードしてINT $0x80 (32-bit) または SYSCALL (64-bit) 命令を実行することで、カーネルにシステムコールを要求します。
  3. thread_darwin.cの変更:

    • sigset_allsigset_noneという静的変数が追加されました。これらはそれぞれ、すべてのシグナルをブロックするマスクと、どのシグナルもブロックしないマスクを表します。
    • runtime·newosproc関数(新しいOSスレッドを作成するGoランタイムの内部関数)内で、runtime·bsdthread_createを呼び出す前にsigset_allでシグナルを完全にマスクし、スレッド作成後に元のシグナルマスクに戻す処理が追加されました。これにより、新しいスレッドが初期化されるまでの間に予期せぬシグナルが配送されるのを防ぎます。
    • runtime·minit関数(Goランタイムの初期化関数)の最後に、sigset_noneでシグナルマスクをクリアする処理が追加されました。これは、ランタイムの初期化が完了した後に、すべてのシグナルが適切に配送されるようにするためです。

これらの変更により、GoランタイムはmacOS上でより安全かつ予測可能なシグナルハンドリングを実現し、特にスレッド作成時の競合状態による問題を回避できるようになります。

コアとなるコードの変更箇所

src/pkg/runtime/os_darwin.h

--- a/src/pkg/runtime/os_darwin.h
+++ b/src/pkg/runtime/os_darwin.h
@@ -20,6 +20,9 @@ uint32	runtime·mach_thread_self(void);
 uint32	runtime·mach_thread_self(void);
 int32	runtime·sysctl(uint32*, uint32, byte*, uintptr*, byte*, uintptr);
 
+typedef uint32 Sigset;
+void	runtime·sigprocmask(int32, Sigset*, Sigset*);
+
 struct Sigaction;
 void	runtime·sigaction(uintptr, struct Sigaction*, struct Sigaction*);
 void	runtime·setsig(int32, void(*)(int32, Siginfo*, void*, G*), bool);
@@ -35,3 +38,4 @@ void	runtime·raisesigpipe(void);
 
 #define	NSIG 32
 #define	SI_USER	0  /* empirically true, but not what headers say */
+#define	SIG_SETMASK 3

src/pkg/runtime/sys_darwin_386.s

--- a/src/pkg/runtime/sys_darwin_386.s
+++ b/src/pkg/runtime/sys_darwin_386.s
@@ -106,6 +106,13 @@ TEXT runtime·nanotime(SB), 7, $32
 	MOVL	DX, 4(DI)
 	RET
 
+TEXT runtime·sigprocmask(SB),7,$0
+	MOVL	$48, AX
+	INT	$0x80
+	JAE	2(PC)
+	CALL	runtime·notok(SB)
+	RET
+
 TEXT runtime·sigaction(SB),7,$0
 	MOVL	$46, AX
 	INT	$0x80

src/pkg/runtime/sys_darwin_amd64.s

--- a/src/pkg/runtime/sys_darwin_amd64.s
+++ b/src/pkg/runtime/sys_darwin_amd64.s
@@ -92,6 +92,16 @@ TEXT runtime·nanotime(SB), 7, $32
 	ADDQ	DX, AX
 	RET
 
+TEXT runtime·sigprocmask(SB),7,$0
+	MOVL	8(SP), DI
+	MOVQ	16(SP), SI
+	MOVQ	24(SP), DX
+	MOVL	$(0x2000000+48), AX	// syscall entry
+	SYSCALL
+	JCC	2(PC)
+	CALL	runtime·notok(SB)
+	RET
+
 TEXT runtime·sigaction(SB),7,$0
 	MOVL	8(SP), DI		// arg 1 sig
 	MOVQ	16(SP), SI		// arg 2 act

src/pkg/runtime/thread_darwin.c

--- a/src/pkg/runtime/thread_darwin.c
+++ b/src/pkg/runtime/thread_darwin.c
@@ -9,6 +9,9 @@
 
 extern SigTab runtime·sigtab[];
 
+static Sigset sigset_all = ~(Sigset)0;
+static Sigset sigset_none;
+
 static void
 unimplemented(int8 *name)
 {
@@ -70,13 +73,19 @@ void
 runtime·newosproc(M *m, G *g, void *stk, void (*fn)(void))
 {
 	int32 errno;
+	Sigset oset;
 
 	m->tls[0] = m->id;	// so 386 asm can find it
 	if(0){
 		runtime·printf("newosproc stk=%p m=%p g=%p fn=%p id=%d/%d ostk=%p\n",
 			stk, m, g, fn, m->id, m->tls[0], &m);
 	}
-	if((errno = runtime·bsdthread_create(stk, m, g, fn)) < 0) {
+
+	runtime·sigprocmask(SIG_SETMASK, &sigset_all, &oset);
+	errno = runtime·bsdthread_create(stk, m, g, fn);
+	runtime·sigprocmask(SIG_SETMASK, &oset, nil);
+	
+	if(errno < 0) {
 		runtime·printf("runtime: failed to create new OS thread (have %d already; errno=%d)\\n", runtime·mcount(), -errno);
 		runtime·throw("runtime.newosproc");
 	}
@@ -89,6 +98,7 @@ runtime·minit(void)
 	// Initialize signal handling.
 	m->gsignal = runtime·malg(32*1024);\t// OS X wants >=8K, Linux >=2K
 	runtime·signalstack(m->gsignal->stackguard - StackGuard, 32*1024);
+	runtime·sigprocmask(SIG_SETMASK, &sigset_none, nil);
 }
 
 // Mach IPC, to get at semaphores

コアとなるコードの解説

src/pkg/runtime/os_darwin.h

  • typedef uint32 Sigset;: シグナルマスクを表現するためのSigset型を定義しています。uint32は32ビットの符号なし整数であり、各ビットが特定のシグナルに対応します。
  • void runtime·sigprocmask(int32, Sigset*, Sigset*);: sigprocmaskシステムコールをGoランタイムから呼び出すためのC言語関数プロトタイプを宣言しています。これは、アセンブリで実装されるruntime·sigprocmask関数に対応します。
  • #define SIG_SETMASK 3: sigprocmaskhow引数に渡す定数を定義しています。SIG_SETMASKは、現在のシグナルマスクを完全に置き換えることを意味します。

src/pkg/runtime/sys_darwin_386.s および src/pkg/runtime/sys_darwin_amd64.s

これらのファイルは、それぞれ32ビットおよび64ビットのDarwinアーキテクチャ向けにruntime·sigprocmask関数のアセンブリ実装を提供します。

  • TEXT runtime·sigprocmask(SB),7,$0: runtime·sigprocmask関数の開始を宣言しています。SBはスタックベースレジスタ、7はフレームポインタのオフセット、$0はスタックフレームサイズを示します。
  • MOVL $48, AX (386) / MOVL $(0x2000000+48), AX (amd64): システムコール番号48AXレジスタにロードしています。Darwinでは、sigprocmaskのシステムコール番号は48です。64ビット版では、0x2000000はシステムコールエントリポイントのオフセットを示します。
  • INT $0x80 (386) / SYSCALL (amd64): システムコールを実行します。INT $0x80は32ビットLinux/Unix系システムで一般的なソフトウェア割り込みによるシステムコール呼び出し、SYSCALLは64ビットシステムで高速なシステムコール呼び出しに使用されます。
  • JAE 2(PC) / JCC 2(PC): システムコールが成功したかどうかをチェックします。JAE (Jump if Above or Equal) または JCC (Jump if Carry Clear) は、キャリーフラグがクリアされている(エラーがない)場合にジャンプします。
  • CALL runtime·notok(SB): システムコールが失敗した場合に、エラーハンドリング関数runtime·notokを呼び出します。
  • RET: 関数から戻ります。

これらのアセンブリコードは、Goランタイムが直接OSのsigprocmask機能を利用するための低レベルなインターフェースを提供します。

src/pkg/runtime/thread_darwin.c

  • static Sigset sigset_all = ~(Sigset)0;: すべてのシグナルをブロックするためのシグナルマスクsigset_allを初期化しています。~(Sigset)0は、すべてのビットがセットされた値(つまり、すべてのシグナルがマスクされる状態)を生成します。
  • static Sigset sigset_none;: どのシグナルもブロックしないためのシグナルマスクsigset_noneを宣言しています。これはデフォルトでゼロ初期化され、すべてのビットがクリアされた状態になります。
  • runtime·newosproc関数内の変更:
    • Sigset oset;: 変更前のシグナルマスクを保存するための変数osetを宣言しています。
    • runtime·sigprocmask(SIG_SETMASK, &sigset_all, &oset);: 新しいOSスレッドを作成する直前に、現在のスレッドのシグナルマスクをsigset_all(すべてのシグナルをブロック)に設定し、元のマスクをosetに保存します。これにより、runtime·bsdthread_createが実行されている間に、予期せぬシグナルが配送されるのを防ぎます。
    • errno = runtime·bsdthread_create(stk, m, g, fn);: 実際に新しいOSスレッドを作成する関数を呼び出します。
    • runtime·sigprocmask(SIG_SETMASK, &oset, nil);: スレッド作成後、元のシグナルマスクを復元します。これにより、スレッド作成のクリティカルセクションが終了し、通常のシグナルハンドリングに戻ります。
  • runtime·minit関数内の変更:
    • runtime·sigprocmask(SIG_SETMASK, &sigset_none, nil);: ランタイムの初期化処理の最後に、現在のスレッドのシグナルマスクをsigset_none(どのシグナルもブロックしない)に設定します。これは、ランタイムが完全に初期化された後、すべてのシグナルが正常に処理されることを保証するためです。

これらの変更は、GoランタイムがmacOS上でスレッドの生成と初期化を行う際のシグナルハンドリングの堅牢性を大幅に向上させ、潜在的な競合状態やクラッシュを防ぐことに貢献しています。

関連リンク

参考にした情報源リンク

  • POSIX sigprocmask man page: (OSのmanページを参照)
  • Unix Signals: https://en.wikipedia.org/wiki/Unix_signal
  • Go Runtime Source Code: (Goの公式GitHubリポジトリ)
  • Darwin/macOS System Calls: (関連するApple Developer DocumentationやXNUソースコード)
  • Go Issue #3101 (Darwin): コミットメッセージに記載されているIssue #3101の直接的なリンクは見つかりませんでしたが、GoランタイムにおけるDarwinでのシグナルハンドリングは、過去にいくつかの課題を抱えていました。例えば、Issue #31264「os/signal: test flaky on darwin」やIssue #22805「can't safely use signal handling on Darwin, missing support from Go runtime」などが挙げられます。これらのIssueは、GoランタイムがmacOSのシグナルメカニズムやC言語との相互運用性において、シグナルハンドリングの堅牢性を確保するための継続的な取り組みを示しています。