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

[インデックス 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オペレーティングシステム向けにgetcontextsigprocmaskを実装します。これらは、まもなくスレッドハンドリングコードによって使用される予定です。

また、NetBSD/386のシグナルハンドリングを修正します。sigreturnは存在せず、単にリターンすることでトランポリンに到達するようにします。

変更の背景

このコミットの主な目的は、GoランタイムがNetBSD上でより堅牢なスレッドハンドリングとシグナル処理を行えるようにするための基盤を構築することです。

  1. getcontextsigprocmaskの実装: Goのランタイムは、ゴルーチン(Goの軽量スレッド)のスケジューリングやシグナル処理において、OSが提供するコンテキスト管理やシグナルマスク操作の機能に依存しています。NetBSD環境でこれらの機能が不足していたため、GoランタイムがNetBSD上で適切に動作するためにこれらのシステムコールを実装する必要がありました。コミットメッセージにある「これらはまもなくスレッドハンドリングコードによって使用される予定です」という記述は、これらのシステムコールがGoのM(Machine)とG(Goroutine)のスケジューリングモデルにおいて、スレッドコンテキストの保存・復元やシグナルブロック・アンブロックといった低レベルな操作に不可欠であることを示唆しています。
  2. NetBSD/386シグナルハンドリングの修正: 386アーキテクチャのNetBSDにおけるシグナルハンドリングに問題があったようです。特に、シグナルハンドラからの復帰メカニズムが他のシステムと異なり、sigreturnシステムコールが存在しないことが原因でした。Goのランタイムはシグナルハンドラから元の実行コンテキストに安全に戻るために特定のメカニズム(通常はsigreturn)を期待しますが、NetBSD/386ではそれが利用できないため、代わりに単なるRET(リターン)命令を使用することで、Goランタイムが用意した「トランポリン」コードに制御を戻し、そこから適切な復帰処理を行うように修正されました。これにより、シグナルハンドラが正しく終了し、プログラムの実行が継続されるようになります。

これらの変更は、GoがNetBSDプラットフォームを完全にサポートし、その上で安定して動作するための重要なステップです。

前提知識の解説

このコミットを理解するためには、以下の概念について基本的な知識が必要です。

  1. Goランタイム (Go Runtime):

    • Goプログラムの実行を管理する低レベルな部分です。ゴルーチンのスケジューリング、メモリ管理(ガベージコレクション)、チャネル通信、システムコールインターフェース、シグナルハンドリングなどを担当します。
    • Goのランタイムは、OSのスレッド(M: Machine)上でゴルーチン(G: Goroutine)を多重化して実行します。シグナルハンドリングやコンテキストスイッチは、このMとGの協調動作において非常に重要です。
  2. システムコール (System Call):

    • ユーザー空間のプログラムがオペレーティングシステムカーネルのサービスを要求するためのインターフェースです。ファイルI/O、プロセス管理、メモリ管理、ネットワーク通信、シグナル処理など、OSの機能を利用する際に使用されます。
    • アセンブリコードでは、通常、特定のレジスタにシステムコール番号をセットし、INT 0x80(x86)やSYSCALL(x86-64)のような命令を実行することでカーネルに制御を渡します。
  3. getcontext() / setcontext():

    • POSIX標準で定義されている関数で、現在の実行コンテキスト(レジスタの状態、スタックポインタ、シグナルマスクなど)を保存・復元するために使用されます。
    • getcontext()は現在のコンテキストをucontext_t構造体に保存し、setcontext()はその構造体からコンテキストを復元し、そのコンテキストが保存された時点から実行を再開します。
    • これらは、ユーザーレベルのスレッドライブラリやコルーチン、非同期I/Oの実装など、プログラム内でコンテキストスイッチを行う際に利用されます。Goのランタイムにおけるゴルーチンのスケジューリングも、内部的にこのようなコンテキストスイッチのメカニズムを利用しています。
  4. sigprocmask():

    • POSIX標準で定義されている関数で、プロセスのシグナルマスク(ブロックされているシグナルのセット)を検査または変更するために使用されます。
    • シグナルマスクを変更することで、特定のシグナルがプロセスに配信されるのを一時的にブロックしたり、ブロックを解除したりできます。これは、クリティカルセクション(シグナルによって中断されたくないコード領域)を保護するために重要です。
    • Goランタイムでは、ガベージコレクションやスケジューリングなどの重要な処理中に、予期せぬシグナルによって中断されないように、一時的にシグナルをブロックするために使用されることがあります。
  5. sigreturn():

    • シグナルハンドラから復帰するためのシステムコールです。シグナルが配信されると、カーネルは現在の実行コンテキストを保存し、シグナルハンドラを実行します。シグナルハンドラが終了すると、sigreturnを呼び出すことで、保存されたコンテキストが復元され、シグナルが配信された時点からプログラムの実行が再開されます。
    • 一部のOSやアーキテクチャでは、sigreturnが明示的なシステムコールとして提供されない場合があります。その場合、カーネルがシグナルハンドラを呼び出す際に、復帰アドレスをスタックに設定するなど、別のメカニズムで復帰を処理します。
  6. トランポリン (Trampoline):

    • プログラミングにおいて、ある関数やコードブロックにジャンプする前に、追加の処理を行うための小さなコードスニペットを指します。
    • このコミットの文脈では、NetBSD/386のシグナルハンドラがsigreturnを呼び出さずに単にRETで戻る場合、その制御がGoランタイムが用意した「トランポリン」コードに渡され、そこでGoランタイムが必要な後処理(例えば、コンテキストの復元やスケジューラの再開)を行うと考えられます。
  7. NetBSD:

    • オープンソースのUnix系オペレーティングシステムで、高い移植性を特徴としています。様々なCPUアーキテクチャで動作します。
    • システムコールインターフェースやシグナル処理の挙動は、他のUnix系OS(LinuxやFreeBSDなど)と類似していますが、細部で異なる場合があります。

技術的詳細

このコミットは、GoランタイムがNetBSD上で低レベルなコンテキスト管理とシグナル処理を行うための具体的な実装を提供しています。

1. getcontextsigprocmaskのシステムコール番号

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·sigprocmaskruntime·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: システムコールが失敗した場合(エラーが発生した場合)、0xf10xf1に移動するという無効な操作を行い、プログラムをクラッシュさせます。これは、システムコールエラーを検出するための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: 最初の引数howDIにロードします。
  • MOVQ 16(SP), SI: 2番目の引数setSIにロードします。
  • MOVQ 24(SP), DX: 3番目の引数osetDXにロードします。
  • 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ランタイムが提供するgetcontextsigprocmask関数のプロトタイプ宣言です。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プログラムの安定性と機能性が向上します。

関連リンク

参考にした情報源リンク