[インデックス 13076] ファイルの概要
このコミットは、GoランタイムにおけるNetBSDオペレーティングシステム向けのgetcontext
およびsigprocmask
システムコールの実装、およびNetBSD/386アーキテクチャにおけるシグナルハンドリングの修正に関するものです。
変更されたファイルは以下の通りです。
src/pkg/runtime/os_netbsd.h
: NetBSD固有のランタイムヘッダーファイル。src/pkg/runtime/sys_netbsd_386.s
: NetBSD/386アーキテクチャ向けのシステムコール実装アセンブリファイル。src/pkg/runtime/sys_netbsd_amd64.s
: NetBSD/AMD64アーキテクチャ向けのシステムコール実装アセンブリファイル。
コミット
commit efa67b2c55b1a61ff780c04486ba9f901d8fb0a6
Author: Joel Sing <jsing@google.com>
Date: Wed May 16 04:32:49 2012 +1000
runtime: implement getcontext and sigprocmask for netbsd
Implement getcontext and sigprocmask for NetBSD - these will soon be
used by the thread handling code.
Also fix netbsd/386 signal handling - there is no sigreturn, just
return so that we hit the trampoline.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6215049
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/efa67b2c55b1a61ff780c04486ba9f901d8fb0a6
元コミット内容
Goランタイムにおいて、NetBSDオペレーティングシステム向けにgetcontext
とsigprocmask
を実装します。これらは、まもなくスレッドハンドリングコードによって使用される予定です。
また、NetBSD/386のシグナルハンドリングを修正します。sigreturn
は存在せず、単にリターンすることでトランポリンに到達するようにします。
変更の背景
このコミットの主な目的は、GoランタイムがNetBSD上でより堅牢なスレッドハンドリングとシグナル処理を行えるようにするための基盤を構築することです。
getcontext
とsigprocmask
の実装: Goのランタイムは、ゴルーチン(Goの軽量スレッド)のスケジューリングやシグナル処理において、OSが提供するコンテキスト管理やシグナルマスク操作の機能に依存しています。NetBSD環境でこれらの機能が不足していたため、GoランタイムがNetBSD上で適切に動作するためにこれらのシステムコールを実装する必要がありました。コミットメッセージにある「これらはまもなくスレッドハンドリングコードによって使用される予定です」という記述は、これらのシステムコールがGoのM(Machine)とG(Goroutine)のスケジューリングモデルにおいて、スレッドコンテキストの保存・復元やシグナルブロック・アンブロックといった低レベルな操作に不可欠であることを示唆しています。- NetBSD/386シグナルハンドリングの修正: 386アーキテクチャのNetBSDにおけるシグナルハンドリングに問題があったようです。特に、シグナルハンドラからの復帰メカニズムが他のシステムと異なり、
sigreturn
システムコールが存在しないことが原因でした。Goのランタイムはシグナルハンドラから元の実行コンテキストに安全に戻るために特定のメカニズム(通常はsigreturn
)を期待しますが、NetBSD/386ではそれが利用できないため、代わりに単なるRET
(リターン)命令を使用することで、Goランタイムが用意した「トランポリン」コードに制御を戻し、そこから適切な復帰処理を行うように修正されました。これにより、シグナルハンドラが正しく終了し、プログラムの実行が継続されるようになります。
これらの変更は、GoがNetBSDプラットフォームを完全にサポートし、その上で安定して動作するための重要なステップです。
前提知識の解説
このコミットを理解するためには、以下の概念について基本的な知識が必要です。
-
Goランタイム (Go Runtime):
- Goプログラムの実行を管理する低レベルな部分です。ゴルーチンのスケジューリング、メモリ管理(ガベージコレクション)、チャネル通信、システムコールインターフェース、シグナルハンドリングなどを担当します。
- Goのランタイムは、OSのスレッド(M: Machine)上でゴルーチン(G: Goroutine)を多重化して実行します。シグナルハンドリングやコンテキストスイッチは、このMとGの協調動作において非常に重要です。
-
システムコール (System Call):
- ユーザー空間のプログラムがオペレーティングシステムカーネルのサービスを要求するためのインターフェースです。ファイルI/O、プロセス管理、メモリ管理、ネットワーク通信、シグナル処理など、OSの機能を利用する際に使用されます。
- アセンブリコードでは、通常、特定のレジスタにシステムコール番号をセットし、
INT 0x80
(x86)やSYSCALL
(x86-64)のような命令を実行することでカーネルに制御を渡します。
-
getcontext()
/setcontext()
:- POSIX標準で定義されている関数で、現在の実行コンテキスト(レジスタの状態、スタックポインタ、シグナルマスクなど)を保存・復元するために使用されます。
getcontext()
は現在のコンテキストをucontext_t
構造体に保存し、setcontext()
はその構造体からコンテキストを復元し、そのコンテキストが保存された時点から実行を再開します。- これらは、ユーザーレベルのスレッドライブラリやコルーチン、非同期I/Oの実装など、プログラム内でコンテキストスイッチを行う際に利用されます。Goのランタイムにおけるゴルーチンのスケジューリングも、内部的にこのようなコンテキストスイッチのメカニズムを利用しています。
-
sigprocmask()
:- POSIX標準で定義されている関数で、プロセスのシグナルマスク(ブロックされているシグナルのセット)を検査または変更するために使用されます。
- シグナルマスクを変更することで、特定のシグナルがプロセスに配信されるのを一時的にブロックしたり、ブロックを解除したりできます。これは、クリティカルセクション(シグナルによって中断されたくないコード領域)を保護するために重要です。
- Goランタイムでは、ガベージコレクションやスケジューリングなどの重要な処理中に、予期せぬシグナルによって中断されないように、一時的にシグナルをブロックするために使用されることがあります。
-
sigreturn()
:- シグナルハンドラから復帰するためのシステムコールです。シグナルが配信されると、カーネルは現在の実行コンテキストを保存し、シグナルハンドラを実行します。シグナルハンドラが終了すると、
sigreturn
を呼び出すことで、保存されたコンテキストが復元され、シグナルが配信された時点からプログラムの実行が再開されます。 - 一部のOSやアーキテクチャでは、
sigreturn
が明示的なシステムコールとして提供されない場合があります。その場合、カーネルがシグナルハンドラを呼び出す際に、復帰アドレスをスタックに設定するなど、別のメカニズムで復帰を処理します。
- シグナルハンドラから復帰するためのシステムコールです。シグナルが配信されると、カーネルは現在の実行コンテキストを保存し、シグナルハンドラを実行します。シグナルハンドラが終了すると、
-
トランポリン (Trampoline):
- プログラミングにおいて、ある関数やコードブロックにジャンプする前に、追加の処理を行うための小さなコードスニペットを指します。
- このコミットの文脈では、NetBSD/386のシグナルハンドラが
sigreturn
を呼び出さずに単にRET
で戻る場合、その制御がGoランタイムが用意した「トランポリン」コードに渡され、そこでGoランタイムが必要な後処理(例えば、コンテキストの復元やスケジューラの再開)を行うと考えられます。
-
NetBSD:
- オープンソースのUnix系オペレーティングシステムで、高い移植性を特徴としています。様々なCPUアーキテクチャで動作します。
- システムコールインターフェースやシグナル処理の挙動は、他のUnix系OS(LinuxやFreeBSDなど)と類似していますが、細部で異なる場合があります。
技術的詳細
このコミットは、GoランタイムがNetBSD上で低レベルなコンテキスト管理とシグナル処理を行うための具体的な実装を提供しています。
1. getcontext
とsigprocmask
のシステムコール番号
NetBSDでは、各システムコールに一意の番号が割り当てられています。
sys_getcontext
のシステムコール番号は307
です。sys_sigprocmask
のシステムコール番号は293
です。
これらの番号は、アセンブリコード内でAX
レジスタにロードされ、INT $0x80
(386)またはSYSCALL
(AMD64)命令によってカーネルにシステムコールを要求します。
2. src/pkg/runtime/os_netbsd.h
の変更
SIG_BLOCK
,SIG_UNBLOCK
,SIG_SETMASK
といったsigprocmask
の操作モードを定義するマクロが追加されました。これらは、sigprocmask
システムコールに渡される引数として使用されます。runtime·sigprocmask
とruntime·getcontext
の関数プロトタイプが追加されました。これにより、Goのランタイムコードからこれらの関数を呼び出せるようになります。- 既存の関数プロトタイプ(
runtime·sigpanic
,runtime·sigaltstack
,runtime·sigaction
,runtime·raisesigpipe
など)の順序が整理されましたが、機能的な変更はありません。
3. src/pkg/runtime/sys_netbsd_386.s
の変更
runtime·getcontext
の実装 (386)
TEXT runtime·getcontext(SB),7,$-4
MOVL $307, AX // sys_getcontext
INT $0x80
JAE 2(PC)
MOVL $0xf1, 0xf1 // crash
RET
MOVL $307, AX
:AX
レジスタにsys_getcontext
のシステムコール番号307
をロードします。INT $0x80
: ソフトウェア割り込み0x80
を発生させ、カーネルにシステムコールを要求します。JAE 2(PC)
: システムコールが成功した場合(キャリーフラグがクリアされている場合)、次の命令をスキップします。MOVL $0xf1, 0xf1
: システムコールが失敗した場合(エラーが発生した場合)、0xf1
を0xf1
に移動するという無効な操作を行い、プログラムをクラッシュさせます。これは、システムコールエラーを検出するためのGoランタイムの一般的なパターンです。RET
: 関数からリターンします。
runtime·sigprocmask
の実装 (386)
TEXT runtime·sigprocmask(SB),7,$-4
MOVL $293, AX // sys_sigprocmask
INT $0x80
JAE 2(PC)
MOVL $0xf1, 0xf1 // crash
RET
MOVL $293, AX
:AX
レジスタにsys_sigprocmask
のシステムコール番号293
をロードします。- 残りの部分は
getcontext
と同様に、システムコールを実行し、成功した場合はリターン、失敗した場合はクラッシュさせます。
NetBSD/386シグナルハンドリングの修正
runtime·sigtramp
関数(シグナルハンドラのトランポリン)から、明示的なsigreturn
システムコール呼び出しが削除されました。
- // call sigreturn
- MOVL context+8(FP), AX
- MOVL $0, 0(SP) // syscall gap
- MOVL AX, 4(SP) // arg 1 - sigcontext
- MOVL $103, AX // sys_sigreturn
- INT $0x80
- MOVL $0xf1, 0xf1 // crash
RET
- 以前は、シグナルハンドラが終了した後、
sys_sigreturn
(システムコール番号103
)を呼び出して元のコンテキストに復帰しようとしていました。 - このコミットでは、この
sigreturn
の呼び出しが完全に削除され、代わりに単なるRET
命令で関数から戻るようになっています。これは、NetBSD/386ではsigreturn
システムコールが存在しないため、カーネルがシグナルハンドラを呼び出す際に、既に適切な復帰メカニズム(例えば、スタックに復帰アドレスを設定するなど)を確立していることを前提としています。RET
命令によって、Goランタイムが期待する「トランポリン」コードに制御が戻り、そこからGoのスケジューラが適切な処理を継続します。
4. src/pkg/runtime/sys_netbsd_amd64.s
の変更
runtime·getcontext
の実装 (AMD64)
TEXT runtime·getcontext(SB),7,$-8
MOVQ 8(SP), DI // arg 1 - context
MOVL $307, AX // sys_getcontext
SYSCALL
JCC 2(PC)
MOVL $0xf1, 0xf1 // crash
RET
MOVQ 8(SP), DI
: AMD64のSystem V ABIでは、最初の引数はDI
レジスタで渡されます。ここでは、スタックポインタSP
から8バイトオフセットにある値(context
引数)をDI
にロードしています。MOVL $307, AX
:AX
レジスタにsys_getcontext
のシステムコール番号307
をロードします。SYSCALL
: AMD64アーキテクチャでシステムコールを呼び出すための命令です。JCC 2(PC)
: システムコールが成功した場合(キャリーフラグがクリアされている場合)、次の命令をスキップします。- 残りの部分は386版と同様です。
runtime·sigprocmask
の実装 (AMD64)
TEXT runtime·sigprocmask(SB),7,$0
MOVL 8(SP), DI // arg 1 - how
MOVQ 16(SP), SI // arg 2 - set
MOVQ 24(SP), DX // arg 3 - oset
MOVL $293, AX // sys_sigprocmask
SYSCALL
JCC 2(PC)
MOVL $0xf1, 0xf1 // crash
RET
MOVL 8(SP), DI
: 最初の引数how
をDI
にロードします。MOVQ 16(SP), SI
: 2番目の引数set
をSI
にロードします。MOVQ 24(SP), DX
: 3番目の引数oset
をDX
にロードします。MOVL $293, AX
:AX
レジスタにsys_sigprocmask
のシステムコール番号293
をロードします。- 残りの部分は
getcontext
と同様です。
AMD64版のsigtramp
には、386版のようなsigreturn
の明示的な呼び出しは元々含まれていませんでした。これは、AMD64アーキテクチャのNetBSDにおけるシグナルハンドリングの復帰メカニズムが、386とは異なるか、またはGoランタイムが既に適切な方法で処理していたためと考えられます。
コアとなるコードの変更箇所
src/pkg/runtime/os_netbsd.h
--- a/src/pkg/runtime/os_netbsd.h
+++ b/src/pkg/runtime/os_netbsd.h
@@ -5,17 +5,22 @@
#define SIG_DFL ((void*)0)
#define SIG_IGN ((void*)1)
+#define SIG_BLOCK 1
+#define SIG_UNBLOCK 2
+#define SIG_SETMASK 3
+
struct sigaction;
-void runtime·sigpanic(void);
-void runtime·sigaltstack(Sigaltstack*, Sigaltstack*);
-void runtime·sigaction(int32, struct sigaction*, struct sigaction*);
+void runtime·raisesigpipe(void);
void runtime·setsig(int32, void(*)(int32, Siginfo*, void*, G*), bool);
void runtime·sighandler(int32 sig, Siginfo *info, void *context, G *gp);
+void runtime·sigpanic(void);
+
+void runtime·sigaction(int32, struct sigaction*, struct sigaction*);
+void runtime·sigaltstack(Sigaltstack*, Sigaltstack*);
void runtime·setitimer(int32, Itimerval*, Itimerval*);
+void runtime·sigprocmask(int32, Sigset*, Sigset*);
int32 runtime·sysctl(uint32*, uint32, byte*, uintptr*, byte*, uintptr);
-void runtime·raisesigpipe(void);
-
#define NSIG 33
#define SI_USER 0
src/pkg/runtime/sys_netbsd_386.s
--- a/src/pkg/runtime/sys_netbsd_386.s
+++ b/src/pkg/runtime/sys_netbsd_386.s
@@ -122,12 +122,26 @@ TEXT runtime·nanotime(SB),7,$32
IMULL $1000, BX
ADDL BX, AX
ADCL $0, DX
-
+
MOVL ret+0(FP), DI
MOVL AX, 0(DI)
MOVL DX, 4(DI)
RET
+TEXT runtime·getcontext(SB),7,$-4
+ MOVL $307, AX // sys_getcontext
+ INT $0x80
+ JAE 2(PC)
+ MOVL $0xf1, 0xf1 // crash
+ RET
+
+TEXT runtime·sigprocmask(SB),7,$-4
+ MOVL $293, AX // sys_sigprocmask
+ INT $0x80
+ JAE 2(PC)
+ MOVL $0xf1, 0xf1 // crash
+ RET
+
TEXT runtime·sigreturn_tramp(SB),7,$0
LEAL 140(SP), AX // Load address of ucontext
MOVL AX, 4(SP)
@@ -166,7 +180,7 @@ TEXT runtime·sigtramp(SB),7,$44
// save g
MOVL g(CX), DI
MOVL DI, 20(SP)
-
+
// g = m->gsignal
MOVL m_gsignal(BX), BX
MOVL BX, g(CX)
@@ -186,14 +200,6 @@ TEXT runtime·sigtramp(SB),7,$44
get_tls(CX)
MOVL 20(SP), BX
MOVL BX, g(CX)
--
-- // call sigreturn
-- MOVL context+8(FP), AX
-- MOVL $0, 0(SP) // syscall gap
-- MOVL AX, 4(SP) // arg 1 - sigcontext
-- MOVL $103, AX // sys_sigreturn
-- INT $0x80
-- MOVL $0xf1, 0xf1 // crash
RET
// int32 rfork_thread(int32 flags, void *stack, M *m, G *g, void (*fn)(void));
@@ -255,7 +261,7 @@ TEXT runtime·rfork_thread(SB),7,$8
CALL runtime·settls(SB)
POPL AX
POPAL
-
+
// Now segment is established. Initialize m, g.
get_tls(AX)
MOVL DX, g(AX)
src/pkg/runtime/sys_netbsd_amd64.s
--- a/src/pkg/runtime/sys_netbsd_amd64.s
+++ b/src/pkg/runtime/sys_netbsd_amd64.s
@@ -163,6 +163,24 @@ TEXT runtime·nanotime(SB),7,$32
ADDQ DX, AX
RET
+TEXT runtime·getcontext(SB),7,$-8
+ MOVQ 8(SP), DI // arg 1 - context
+ MOVL $307, AX // sys_getcontext
+ SYSCALL
+ JCC 2(PC)
+ MOVL $0xf1, 0xf1 // crash
+ RET
+
+TEXT runtime·sigprocmask(SB),7,$0
+ MOVL 8(SP), DI // arg 1 - how
+ MOVQ 16(SP), SI // arg 2 - set
+ MOVQ 24(SP), DX // arg 3 - oset
+ MOVL $293, AX // sys_sigprocmask
+ SYSCALL
+ JCC 2(PC)
+ MOVL $0xf1, 0xf1 // crash
+ RET
+
TEXT runtime·sigreturn_tramp(SB),7,$-8
MOVQ R15, DI // Load address of ucontext
MOVQ $308, AX // sys_setcontext
@@ -186,7 +204,7 @@ TEXT runtime·sigaction(SB),7,$-8
TEXT runtime·sigtramp(SB),7,$64
get_tls(BX)
-
+
// check that m exists
MOVQ m(BX), BP
CMPQ BP, $0
@@ -196,16 +214,16 @@ TEXT runtime·sigtramp(SB),7,$64
// save g
MOVQ g(BX), R10
MOVQ R10, 40(SP)
-
+
// g = m->signal
MOVQ m_gsignal(BP), BP
MOVQ BP, g(BX)
-
+
MOVQ DI, 0(SP)
MOVQ SI, 8(SP)
MOVQ DX, 16(SP)
MOVQ R10, 24(SP)
-
+
CALL runtime·sighandler(SB)
// restore g
コアとなるコードの解説
src/pkg/runtime/os_netbsd.h
このヘッダーファイルは、GoランタイムがNetBSD上でシステムコールやシグナル処理を行うために必要な定数と関数プロトタイプを定義しています。
#define SIG_BLOCK 1
など:sigprocmask
システムコールで使用される操作モード(シグナルをブロック、アンブロック、またはマスクを設定)を数値で定義しています。これにより、GoのCコードやアセンブリコードからこれらの定数を参照できるようになります。void runtime·getcontext(void);
およびvoid runtime·sigprocmask(int32, Sigset*, Sigset*);
: これらの行は、Goランタイムが提供するgetcontext
とsigprocmask
関数のプロトタイプ宣言です。Goのランタイムは、OSが提供するシステムコールを直接呼び出すためのラッパー関数をアセンブリで実装し、それをGoのコードから利用できるようにします。
src/pkg/runtime/sys_netbsd_386.s
このファイルは、NetBSD/386アーキテクチャ向けのGoランタイムの低レベルなアセンブリコードを含んでいます。
-
TEXT runtime·getcontext(SB),7,$-4
:TEXT
はGoのアセンブリにおける関数定義のキーワードです。SB
はシンボルベースレジスタで、グローバルシンボルを参照します。7
はスタックフレームのサイズ(ここでは使用されないが慣例的に指定)、$-4
は引数のサイズを示します。MOVL $307, AX
:AX
レジスタにNetBSDのgetcontext
システムコール番号307
をロードします。INT $0x80
: x86アーキテクチャでシステムコールを呼び出すためのソフトウェア割り込み命令です。これにより、カーネルに制御が渡され、AX
レジスタの番号に対応するシステムコールが実行されます。JAE 2(PC)
:JAE
は"Jump if Above or Equal"(キャリーフラグがクリアされている場合にジャンプ)を意味します。システムコールが成功した場合、キャリーフラグはクリアされます。この場合、2(PC)
(現在のプログラムカウンタから2バイト先)にジャンプし、エラー処理コードをスキップします。MOVL $0xf1, 0xf1
: システムコールが失敗した場合(キャリーフラグがセットされる)、この命令が実行されます。これはGoランタイムでエラー時にパニックを引き起こすための慣用的な方法です。RET
: 関数からリターンします。
-
TEXT runtime·sigprocmask(SB),7,$-4
:MOVL $293, AX
:AX
レジスタにNetBSDのsigprocmask
システムコール番号293
をロードします。- 残りのロジックは
getcontext
と同様で、システムコールを実行し、成功/失敗に応じて処理を分岐します。
-
runtime·sigtramp
からのsigreturn
削除:- 元のコードには、シグナルハンドラが終了した後に
sys_sigreturn
システムコール(番号103
)を呼び出す部分がありました。 - このコミットでは、この
sigreturn
の呼び出しが完全に削除され、代わりにRET
命令のみが残されています。これは、NetBSD/386のカーネルがシグナルハンドラからの復帰を自動的に処理するか、またはGoランタイムが用意した別の「トランポリン」コードに制御を戻すことで、適切な復帰処理が行われることを前提としています。これにより、GoランタイムはNetBSD/386のシグナルハンドリングの特性に適合します。
- 元のコードには、シグナルハンドラが終了した後に
src/pkg/runtime/sys_netbsd_amd64.s
このファイルは、NetBSD/AMD64アーキテクチャ向けのGoランタイムの低レベルなアセンブリコードを含んでいます。
-
TEXT runtime·getcontext(SB),7,$-8
:MOVQ 8(SP), DI
: AMD64のSystem V ABIでは、最初の引数はDI
レジスタで渡されます。8(SP)
はスタックポインタSP
から8バイトオフセットにあるメモリ位置(context
引数)を参照します。MOVL $307, AX
:AX
レジスタにgetcontext
システムコール番号307
をロードします。SYSCALL
: AMD64アーキテクチャでシステムコールを呼び出すための命令です。INT 0x80
よりも高速です。JCC 2(PC)
:JCC
は"Jump if Carry Clear"(キャリーフラグがクリアされている場合にジャンプ)を意味します。JAE
と同じく、システムコール成功時にエラー処理をスキップします。- 残りのロジックは386版と同様です。
-
TEXT runtime·sigprocmask(SB),7,$0
:MOVL 8(SP), DI
,MOVQ 16(SP), SI
,MOVQ 24(SP), DX
: AMD64のSystem V ABIでは、最初の3つの引数はそれぞれDI
,SI
,DX
レジスタで渡されます。ここでは、スタックからこれらの引数をロードしています。MOVL $293, AX
:AX
レジスタにsigprocmask
システムコール番号293
をロードします。- 残りのロジックは
getcontext
と同様です。
AMD64版のsigtramp
には、386版のようなsigreturn
の明示的な呼び出しは元々存在しませんでした。これは、AMD64のNetBSDにおけるシグナルハンドリングの復帰メカニズムが、386とは異なるか、またはGoランタイムが既に適切な方法で処理していたためと考えられます。
これらのアセンブリコードの変更により、GoランタイムはNetBSD上でゴルーチンのコンテキストスイッチやシグナルマスクの操作を正確に行えるようになり、NetBSDプラットフォームでのGoプログラムの安定性と機能性が向上します。
関連リンク
参考にした情報源リンク
- Go Assembly Language
- System V Application Binary Interface AMD64 Architecture Processor Supplement
- NetBSD System Calls (具体的なシステムコール番号はNetBSDのバージョンやアーキテクチャによって異なる場合がありますが、一般的な情報源として)
- POSIX
getcontext
andsetcontext
- POSIX
sigprocmask
- Go Runtime Source Code (Goのランタイムの内部動作を理解するための主要な情報源)
- Understanding Go's Runtime Scheduler (Goのスケジューラとランタイムの概念を理解するため)
- Linux x86 System Call Convention (x86システムコール呼び出し規約の一般的な理解のため)
- NetBSD
sigreturn
man page (NetBSDにおけるsigreturn
の有無や挙動を確認するため) - Go issue 3920: runtime: implement getcontext and sigprocmask for netbsd (このコミットに関連する可能性のあるGoのIssue)
- Go issue 3921: runtime: fix netbsd/386 signal handling (このコミットに関連する可能性のあるGoのIssue)