[インデックス 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ランタイムから呼び出せるようにし、新しいスレッドの作成前後にシグナルマスクを適切に設定することで、競合状態を防ぎます。
変更のポイントは以下の通りです。
-
os_darwin.h
の変更:Sigset
型(シグナル集合を表すuint32
)が定義されました。これは、シグナルマスクをビットマスクとして扱うための型です。runtime·sigprocmask
関数のプロトタイプが追加されました。これは、GoランタイムがC言語のコードからsigprocmask
システムコールを呼び出すための宣言です。SIG_SETMASK
マクロが定義されました。これはsigprocmask
システムコールで使用されるhow
引数の値(3)です。
-
sys_darwin_386.s
およびsys_darwin_amd64.s
の変更:runtime·sigprocmask
関数のアセンブリ実装が追加されました。これは、Goランタイムが直接sigprocmask
システムコールを呼び出すためのラッパーです。- 32-bit (i386) および 64-bit (amd64) アーキテクチャの両方に対応する実装が提供されています。
- これらのアセンブリコードは、適切なシステムコール番号(
48
forsigprocmask
on Darwin)を設定し、レジスタに引数をロードしてINT $0x80
(32-bit) またはSYSCALL
(64-bit) 命令を実行することで、カーネルにシステムコールを要求します。
-
thread_darwin.c
の変更:sigset_all
とsigset_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
:sigprocmask
のhow
引数に渡す定数を定義しています。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): システムコール番号48
をAX
レジスタにロードしています。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上でスレッドの生成と初期化を行う際のシグナルハンドリングの堅牢性を大幅に向上させ、潜在的な競合状態やクラッシュを防ぐことに貢献しています。
関連リンク
- Go Issue #3101: https://code.google.com/p/go/issues/detail?id=3101 (古いGoogle Codeのリンクですが、当時のIssueトラッカーです)
- Go CL 5693044: https://golang.org/cl/5693044 (Goのコードレビューシステムへのリンク)
参考にした情報源リンク
- 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ランタイムから呼び出せるようにし、新しいスレッドの作成前後にシグナルマスクを適切に設定することで、競合状態を防ぎます。
変更のポイントは以下の通りです。
-
os_darwin.h
の変更:Sigset
型(シグナル集合を表すuint32
)が定義されました。これは、シグナルマスクをビットマスクとして扱うための型です。runtime·sigprocmask
関数のプロトタイプが追加されました。これは、GoランタイムがC言語のコードからsigprocmask
システムコールを呼び出すための宣言です。SIG_SETMASK
マクロが定義されました。これはsigprocmask
システムコールで使用されるhow
引数の値(3)です。
-
sys_darwin_386.s
およびsys_darwin_amd64.s
の変更:runtime·sigprocmask
関数のアセンブリ実装が追加されました。これは、Goランタイムが直接sigprocmask
システムコールを呼び出すためのラッパーです。- 32-bit (i386) および 64-bit (amd64) アーキテクチャの両方に対応する実装が提供されています。
- これらのアセンブリコードは、適切なシステムコール番号(
48
forsigprocmask
on Darwin)を設定し、レジスタに引数をロードしてINT $0x80
(32-bit) またはSYSCALL
(64-bit) 命令を実行することで、カーネルにシステムコールを要求します。
-
thread_darwin.c
の変更:sigset_all
とsigset_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
:sigprocmask
のhow
引数に渡す定数を定義しています。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): システムコール番号48
をAX
レジスタにロードしています。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上でスレッドの生成と初期化を行う際のシグナルハンドリングの堅牢性を大幅に向上させ、潜在的な競合状態やクラッシュを防ぐことに貢献しています。
関連リンク
- Go CL 5693044: https://golang.org/cl/5693044 (Goのコードレビューシステムへのリンク)
参考にした情報源リンク
- 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言語との相互運用性において、シグナルハンドリングの堅牢性を確保するための継続的な取り組みを示しています。