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

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

このコミットは、GoランタイムがNetBSD/386アーキテクチャ上で動作するようにするための重要な変更を含んでいます。具体的には、NetBSDの軽量プロセス(LWP: Lightweight Process)APIをGoランタイムが利用するように移行し、従来のrforkベースのスレッド作成メカニズムを置き換えています。これにより、NetBSD上でのGoプログラムの安定性と互換性が向上します。

コミット

commit fb32d60cd1ba70aa7cd9ccdc3cd409fda142df4b
Author: Joel Sing <jsing@google.com>
Date:   Wed May 30 02:52:50 2012 +1000

    runtime: make go work on netbsd/386
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6254055

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

https://github.com/golang/go/commit/fb32d60cd1ba70aa7cd9ccdc3cd409fda142df4b

元コミット内容

このコミットの元の内容は、GoランタイムがNetBSD/386上で動作するようにするための変更です。

変更の背景

Go言語のランタイムは、オペレーティングシステム(OS)の提供するスレッド(または軽量プロセス)と密接に連携して、ゴルーチン(goroutine)のスケジューリングや並行処理を実現しています。Goのランタイムは、OSのスレッドを「M」(Machine)として抽象化し、その上でGo独自のスケジューラがゴルーチン(G)をP(Processor)に割り当てて実行します。

NetBSDは、UNIX系のオペレーティングシステムであり、独自の軽量プロセス(LWP)モデルを持っています。GoランタイムがNetBSD上で正しく動作するためには、NetBSDのLWP APIと適切に連携する必要があります。

このコミット以前は、NetBSD/386上でのGoランタイムは、おそらくrforkシステムコール(Plan 9由来のプロセス/スレッド作成メカニズム)や、より一般的なUNIX系のスレッドAPI(pthreadsなど)をエミュレートする形でスレッドを作成していた可能性があります。しかし、これらのアプローチはNetBSDのネイティブなLWPモデルと完全に合致しない場合があり、パフォーマンスの問題や互換性の問題を引き起こす可能性がありました。

このコミットの背景には、NetBSD/386環境におけるGoランタイムの安定性、パフォーマンス、およびNetBSDのネイティブなスレッドモデルとのより良い統合を目指す目的があります。具体的には、NetBSDのLWP APIを直接利用することで、より効率的かつ堅牢なスレッド管理を実現しようとしています。

前提知識の解説

GoランタイムのM-P-Gモデル

Goランタイムは、ゴルーチン(G)、論理プロセッサ(P)、OSスレッド(M)という3つの主要なエンティティで並行処理を管理します。

  • G (Goroutine): Go言語の軽量な並行実行単位。数千から数百万のゴルーチンを同時に実行できます。
  • P (Processor): 論理プロセッサ。ゴルーチンを実行するためのコンテキストを提供します。CPUコア数に応じてPの数が決まります。
  • M (Machine): OSスレッド。Pに割り当てられたゴルーチンを実際に実行するOSのスレッドです。MはOSのスケジューラによって管理されます。

Goランタイムは、Pに割り当てられたGをM上で実行します。Mがシステムコールなどでブロックされた場合、Goランタイムは新しいMを起動するか、既存のMを再利用して、他のPに割り当てられたGの実行を継続します。このモデルにより、Goは効率的な並行処理を実現しています。

NetBSDの軽量プロセス(LWP)

NetBSDは、カーネルレベルのスレッド実装として軽量プロセス(LWP)を提供しています。LWPは、プロセス内で独立した実行コンテキストを持つエンティティであり、CPUのスケジューリング単位となります。複数のLWPが同じプロセスのアドレス空間を共有し、リソースを共有できます。

LWPは、POSIXスレッド(pthreads)のような高レベルなスレッドAPIの基盤となることが多く、OSが直接管理する最小の実行単位です。NetBSDのLWP APIは、LWPの作成、終了、同期、および状態管理のためのシステムコールを提供します。

  • lwp_create(): 新しいLWPを作成するためのシステムコール。
  • lwp_park(): LWPを一時停止させるためのシステムコール。
  • lwp_unpark(): 停止中のLWPを再開させるためのシステムコール。

これらのシステムコールは、GoランタイムがOSスレッド(M)を管理するために直接利用されます。

アセンブリ言語(x86/i386)

このコミットでは、sys_netbsd_386.sというファイルが変更されており、これはx86(i386)アーキテクチャ向けのアセンブリ言語コードです。Goランタイムは、OSのシステムコールを直接呼び出すために、特定のアセンブリコードを使用することがあります。

  • システムコール(syscall): OSのカーネルが提供するサービスをプログラムから呼び出すためのインターフェースです。アセンブリ言語では、特定のレジスタにシステムコール番号と引数を設定し、INT 0x80(Linux/BSDのi386におけるソフトウェア割り込み)などの命令を実行することでシステムコールを呼び出します。
  • レジスタ: CPU内部の高速な記憶領域です。x86アーキテクチャでは、EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESPなどの汎用レジスタがあります。システムコールや関数呼び出しの際に、引数の受け渡しや戻り値の格納に利用されます。
  • スタックポインタ(ESP/SP): 現在のスタックフレームの最上位アドレスを指すレジスタです。関数呼び出しの際に引数やローカル変数がスタックにプッシュされ、関数から戻る際にポップされます。
  • フレームポインタ(EBP/FP): 現在のスタックフレームの基底アドレスを指すレジスタです。ローカル変数や引数へのアクセスに利用されます。

技術的詳細

このコミットの主要な技術的変更点は、NetBSD/386におけるGoランタイムのスレッド管理メカニズムを、従来のrforkベースのアプローチから、NetBSDネイティブのLWP APIへと移行したことです。

rforkからLWPへの移行

  • rfork_threadの削除: 以前はruntime·rfork_threadという関数がスレッド作成に使用されていました。rforkはPlan 9由来のシステムコールで、プロセスやスレッドの作成時に親プロセスと共有するリソース(メモリ、ファイルディスクリプタなど)を細かく制御できる特徴があります。しかし、NetBSDのLWPモデルとは直接的に対応しないため、GoランタイムがNetBSD上で効率的に動作するためには、よりネイティブなLWP APIへの移行が望ましいと判断されたと考えられます。
  • lwp_createの導入: 新たにruntime·lwp_create関数が導入され、NetBSDのsys__lwp_createシステムコール(システムコール番号309)を直接呼び出すようになりました。このシステムコールは、新しいLWPを作成し、その実行コンテキスト(スタック、レジスタの状態など)を設定します。
  • lwp_parklwp_unparkの導入: スレッドのサスペンド/レジュームには、従来のthrsleepthrwakeupに代わり、NetBSDのsys__lwp_park(システムコール番号434)とsys__lwp_unpark(システムコール番号321)が使用されるようになりました。これにより、GoランタイムがOSスレッドをより効率的に一時停止・再開できるようになります。

LWPの初期化と実行フロー

  • runtime·lwp_mcontext_init: src/pkg/runtime/signal_netbsd_386.cに追加されたこのC関数は、新しいLWPの実行コンテキスト(McontextT構造体)を初期化します。
    • mc->__gregs[REG_EIP] = (uint32)runtime·lwp_tramp;: 新しいLWPの命令ポインタ(EIPレジスタ)をruntime·lwp_trampに設定します。これは、新しいLWPが最初に実行を開始するアセンブリコードのエントリポイントです。
    • mc->__gregs[REG_UESP] = (uint32)stack;: 新しいLWPのスタックポインタ(UESPレジスタ)を、指定されたスタックアドレスに設定します。
    • mc->__gregs[REG_EBX] = (uint32)m;, mc->__gregs[REG_EDX] = (uint32)g;, mc->__gregs[REG_ESI] = (uint32)fn;: GoランタイムのM(Machine)、G(Goroutine)、および新しいLWPが実行すべき関数(fn)を、それぞれEBX、EDX、ESIレジスタに格納します。これにより、runtime·lwp_tramp内でこれらの値にアクセスできるようになります。
  • runtime·lwp_tramp: src/pkg/runtime/sys_netbsd_386.sに追加されたこのアセンブリ関数は、新しいLWPの実際のエントリポイントです。
    • この関数は、lwp_mcontext_initで設定されたレジスタ(EBX, EDX, ESI)からM、G、および実行すべき関数fnを取得します。
    • LEAL m_tls(BX), BPによって、M構造体内のTLS(Thread Local Storage)領域のアドレスをBPレジスタにロードし、MOVL BP, %fsによってFSセグメントレジスタに設定します。TLSは、スレッドごとに独立したデータを保持するために使用され、GoランタイムではMやGの情報をスレッドローカルにアクセスするために重要です。
    • 最終的に、CALL SIによって、lwp_mcontext_initで渡されたfn(新しいLWPが実行すべきGoの関数)を呼び出します。

AMD64における変更

  • src/pkg/runtime/sys_netbsd_amd64.sでは、lwp_createのコメントがint64からint32に修正されています。これは、lwp_createシステムコールの戻り値の型に関する修正であり、AMD64アーキテクチャにおけるLWP APIの正確な利用を反映しています。

これらの変更により、GoランタイムはNetBSDのネイティブなLWP APIを直接利用するようになり、NetBSD/386環境でのGoプログラムの動作がより効率的かつ安定したものになります。

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

src/pkg/runtime/signal_netbsd_386.c

+extern void runtime·lwp_tramp(void);
 // ...
+void
+runtime·lwp_mcontext_init(McontextT *mc, void *stack, M *m, G *g, void (*fn)(void))
+{
+	mc->__gregs[REG_EIP] = (uint32)runtime·lwp_tramp;
+	mc->__gregs[REG_UESP] = (uint32)stack;
+	mc->__gregs[REG_EBX] = (uint32)m;
+	mc->__gregs[REG_EDX] = (uint32)g;
+	mc->__gregs[REG_ESI] = (uint32)fn;
+}

src/pkg/runtime/sys_netbsd_386.s

-TEXT runtime·rfork_thread(SB),7,$8
-// ... (rfork_threadの削除)

+// int32 lwp_create(void *context, uintptr flags, void *lwpid);
+TEXT runtime·lwp_create(SB),7,$16
+	MOVL	$0, 0(SP)
+	MOVL	context+0(FP), AX
+	MOVL	AX, 4(SP)		// arg 1 - context
+	MOVL	flags+4(FP), AX
+	MOVL	AX, 8(SP)		// arg 2 - flags
+	MOVL	lwpid+8(FP), AX
+	MOVL	AX, 12(SP)		// arg 3 - lwpid
+	MOVL	$309, AX		// sys__lwp_create
+	INT	$0x80
+	JCC	2(PC)
+	NEGL	AX
+	RET

+TEXT runtime·lwp_tramp(SB),7,$0
+	// Set FS to point at m->tls
+	LEAL	m_tls(BX), BP
+	MOVL	BP, %fs
+	// ... (TLS設定とfn呼び出しのロジック)
+	CALL	SI // Call fn

-TEXT runtime·thrsleep(SB),7,$-4
-	MOVL	$300, AX		// sys_thrsleep
+TEXT runtime·lwp_park(SB),7,$-4
+	MOVL	$434, AX		// sys__lwp_park
 	INT	$0x80
 	RET

-TEXT runtime·thrwakeup(SB),7,$-4
-	MOVL	$301, AX		// sys_thrwakeup
+TEXT runtime·lwp_unpark(SB),7,$-4
+	MOVL	$321, AX		// sys__lwp_unpark
 	INT	$0x80
 	RET

src/pkg/runtime/sys_netbsd_amd64.s

-// int64 lwp_create(void *context, uintptr flags, void *lwpid)
+// int32 lwp_create(void *context, uintptr flags, void *lwpid)
 TEXT runtime·lwp_create(SB),7,$0

コアとなるコードの解説

runtime·lwp_mcontext_init (C言語)

この関数は、新しいLWPが実行を開始する際のCPUの状態(コンテキスト)を初期化します。

  • mc->__gregs[REG_EIP] = (uint32)runtime·lwp_tramp;: 新しいLWPが実行を開始する命令のアドレス(エントリポイント)をruntime·lwp_trampに設定します。これにより、LWPが作成されるとまずこのアセンブリコードが実行されます。
  • mc->__gregs[REG_UESP] = (uint32)stack;: 新しいLWPが使用するスタックの最上位アドレスを設定します。
  • mc->__gregs[REG_EBX] = (uint32)m;, mc->__gregs[REG_EDX] = (uint32)g;, mc->__gregs[REG_ESI] = (uint32)fn;: GoランタイムのM(OSスレッド)、G(ゴルーチン)、およびLWPが最終的に実行すべきGoの関数fnを、それぞれEBX、EDX、ESIレジスタに格納します。これらのレジスタは、runtime·lwp_tramp内でこれらの値を取り出すために使用されます。

runtime·lwp_create (アセンブリ言語)

このアセンブリ関数は、NetBSDの_lwp_createシステムコールを呼び出して新しいLWPを作成します。

  • MOVL context+0(FP), AX, MOVL flags+4(FP), AX, MOVL lwpid+8(FP), AX: 関数引数であるcontextflagslwpidをそれぞれAXレジスタにロードし、スタックにプッシュしてシステムコールの引数として準備します。
  • MOVL $309, AX: システムコール番号309(sys__lwp_create)をEAXレジスタに設定します。
  • INT $0x80: ソフトウェア割り込み0x80を発生させ、カーネルにシステムコールを要求します。
  • JCC 2(PC): システムコールが成功した場合(キャリーフラグがクリアされている場合)、2バイト先にジャンプします。
  • NEGL AX: システムコールが失敗した場合(キャリーフラグがセットされている場合)、EAXレジスタの値を符号反転させます。これは、エラーコードが負の値で返されるUNIX系の慣習に従っています。
  • RET: 関数から戻ります。

runtime·lwp_tramp (アセンブリ言語)

このアセンブリ関数は、新しく作成されたLWPが最初に実行するコードです。

  • LEAL m_tls(BX), BPMOVL BP, %fs: lwp_mcontext_initでEBXレジスタに渡されたM(Machine)構造体から、スレッドローカルストレージ(TLS)のアドレスを計算し、FSセグメントレジスタに設定します。TLSは、GoランタイムがMやGの情報をスレッドごとに独立して保持するために使用されます。
  • CALL SI: lwp_mcontext_initでESIレジスタに渡されたfn(新しいLWPが実行すべきGoの関数)を呼び出します。これにより、LWPはGoランタイムのスケジューラが割り当てたゴルーチンを実行し始めます。

runtime·lwp_parkruntime·lwp_unpark (アセンブリ言語)

これらの関数は、NetBSDの_lwp_park(システムコール番号434)と_lwp_unpark(システムコール番号321)システムコールを呼び出します。これらは、GoランタイムがOSスレッド(M)を一時停止したり、再開したりするために使用されます。これにより、Goランタイムは必要に応じてOSスレッドをブロックし、他のゴルーチンにCPU時間を割り当てることができます。

関連リンク

  • Go言語のランタイムスケジューラに関するドキュメントや記事
  • NetBSDのLWPに関する公式ドキュメントやmanページ
  • x86アセンブリ言語のシステムコール呼び出し規約に関する資料

参考にした情報源リンク