[インデックス 13147] ファイルの概要
このコミットは、Go言語のランタイムがNetBSD/amd64アーキテクチャ上で正しく動作するようにするための重要な変更を含んでいます。具体的には、スレッド管理と同期のメカニズムを、従来のrfork
ベースのアプローチからNetBSDのネイティブな軽量プロセス(LWP: Lightweight Process)APIへと移行しています。これにより、Goのゴルーチン(goroutine)スケジューラがNetBSD環境でより効率的かつ安定して機能するようになります。
コミット
commit 5a043de746a0aa230862318e2f249d71de1e1fe3
Author: Joel Sing <jsing@google.com>
Date: Thu May 24 11:33:11 2012 +1000
runtime: make go work on netbsd/amd64
R=golang-dev, rsc, devon.odell
CC=golang-dev
https://golang.org/cl/6222044
---
src/pkg/runtime/os_netbsd.h | 2 +-\
src/pkg/runtime/signal_netbsd_amd64.c | 12 ++++++\
src/pkg/runtime/sys_netbsd_amd64.s | 61 +++++++++++------------------
src/pkg/runtime/thread_netbsd.c | 74 +++++++++++++++++++++++++----------
4 files changed, 88 insertions(+), 61 deletions(-)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5a043de746a0aa230862318e2f249d71de1e1fe3
元コミット内容
このコミットの目的は、「runtime: make go work on netbsd/amd64」(ランタイム: NetBSD/amd64でGoを動作させる)です。これは、Go言語がNetBSDオペレーティングシステム上のAMD64アーキテクチャで適切に動作するための基盤となる変更を意味します。
変更の背景
Go言語のランタイムは、ゴルーチンと呼ばれる軽量な並行処理単位を効率的に管理するために、OSのスレッド機能を利用します。初期のGoランタイムは、様々なUNIX系OSでスレッドを作成・管理するために、fork
システムコールに似たrfork
(Linuxのclone
に相当する概念)のような汎用的なメカニズムや、POSIXスレッド(pthreads)を抽象化して利用していました。
しかし、NetBSDのような特定のOSでは、そのOSが提供するネイティブなスレッド管理API(この場合はLWP: Lightweight Process)を利用する方が、パフォーマンス、安定性、そしてOSのセマンティクスとの整合性の点で優れている場合があります。
このコミット以前のGoランタイムのNetBSD実装は、おそらくrfork
のような汎用的なスレッド作成メカニズムに依存していたと考えられます。しかし、NetBSDのLWPは、より軽量で効率的なスレッド管理を提供し、GoのM:Nスケジューラ(多数のゴルーチンを少数のOSスレッドにマッピングする)の要件により適しています。
この変更の背景には、NetBSD/amd64環境でのGoの安定性とパフォーマンスを向上させるという明確な目標がありました。特に、シグナルハンドリング、スレッドの生成、スレッド間の同期といった低レベルなOSインタラクションが、NetBSDのLWPモデルに最適化される必要がありました。
前提知識の解説
このコミットを理解するためには、以下の概念についての知識が役立ちます。
-
Goランタイムとゴルーチン (Goroutines):
- Go言語の並行処理の基本単位はゴルーチンです。ゴルーチンはOSスレッドよりもはるかに軽量で、数百万個を同時に実行することも可能です。
- Goランタイムには、ゴルーチンをOSスレッドにマッピングし、実行をスケジュールするM:Nスケジューラが組み込まれています。M個のゴルーチンをN個のOSスレッドで実行します(M >= N)。
- このスケジューラが効率的に機能するためには、OSスレッドの生成、破棄、一時停止、再開といった操作が高速である必要があります。
-
NetBSDと軽量プロセス (LWP: Lightweight Process):
- NetBSDは、UNIX系のオープンソースオペレーティングシステムです。
- NetBSDにおけるLWPは、カーネルが直接管理する実行単位であり、POSIXスレッド(pthreads)の実装基盤となっています。LWPは、プロセス内の独立した実行コンテキストを提供し、CPUスケジューリングの対象となります。
- LWPは、従来のプロセスよりも軽量で、プロセス間でリソースを共有しやすいため、マルチスレッドアプリケーションに適しています。
-
rfork
システムコール (Linuxclone
の概念):rfork
は、Plan 9オペレーティングシステムに由来するシステムコールで、プロセスを複製する際に、親プロセスと子プロセスが共有するリソース(メモリ空間、ファイルディスクリプタなど)を細かく制御できるのが特徴です。- Linuxの
clone
システムコールも同様の機能を提供し、スレッドの実装によく使われます。Goの初期のランタイムでは、このようなfork
系のシステムコールを抽象化してスレッドを作成していました。
-
システムコール (Syscall):
- アプリケーションがOSのカーネル機能を利用するためのインターフェースです。各システムコールには一意の番号が割り当てられており、アセンブリ言語で直接呼び出すことができます。
- このコミットでは、
sys_rfork
、sys_getthrid
、sys_threxit
といった古いシステムコールから、sys__lwp_create
、sys__lwp_exit
、sys__lwp_park
、sys__lwp_unpark
といったLWP関連の新しいシステムコールへの移行が見られます。
-
アセンブリ言語 (Assembly Language):
- CPUが直接実行できる機械語に非常に近い低レベルなプログラミング言語です。OSのカーネルやランタイムの非常に低レベルな部分(スレッドのコンテキストスイッチ、システムコール呼び出しなど)では、パフォーマンスやOSとの直接的なインタラクションのためにアセンブリ言語が使用されることがあります。
- Goランタイムの多くの部分はGoで書かれていますが、OSとの境界部分やパフォーマンスがクリティカルな部分では、C言語やアセンブリ言語が使われます。
-
シグナルハンドリング (Signal Handling):
- OSがプロセスに非同期イベント(例: 割り込み、エラー、タイマー満了)を通知するメカニズムです。Goランタイムは、デッドロック検出やプロファイリングなどのためにシグナルを利用します。
- シグナルハンドリングは、スレッドのコンテキストと密接に関連しており、OSスレッドの切り替えや状態保存に影響を与えます。
技術的詳細
このコミットの核心は、GoランタイムがNetBSD上でスレッドを管理する方法を根本的に変更した点にあります。
1. rfork
ベースのスレッド作成からLWP APIへの移行:
- 以前は、
runtime·rfork_thread
という関数がsys_rfork
システムコール(NetBSDにおけるrfork
は、Linuxのclone
に似た機能を提供し、スレッドのような軽量な実行コンテキストを作成するために使用されることがあります)を使用して新しいOSスレッドを作成していました。 - このコミットでは、
runtime·rfork_thread
が削除され、代わりにruntime·lwp_create
が導入されました。runtime·lwp_create
は、NetBSDのネイティブなLWP作成システムコールであるsys__lwp_create
(システムコール番号309)を呼び出します。 - 新しいLWPを作成する際には、
runtime·lwp_mcontext_init
関数が導入され、新しいLWPの実行コンテキスト(レジスタの状態、スタックポインタ、命令ポインタなど)を初期化します。これにより、LWPがGoのランタイムが期待する状態で起動できるようになります。 - LWPの終了には、
sys__lwp_exit
(システムコール番号310)が使用されるようになりました。
2. スレッド同期メカニズムの変更:
- Goランタイムのセマフォ実装(
runtime·semasleep
とruntime·semawakeup
)は、スレッドを一時停止・再開するために使用されます。 - 以前は、
runtime·thrsleep
とruntime·thrwakeup
という関数が、それぞれsys_thrsleep
(システムコール番号300)とsys_thrwakeup
(システムコール番号301)を使用していました。これらは、NetBSDの古いスレッド同期メカニズムの一部であった可能性があります。 - このコミットでは、これらの関数が削除され、代わりに
runtime·lwp_park
とruntime·lwp_unpark
が導入されました。これらは、NetBSDのLWP APIの一部であるsys__lwp_park
(システムコール番号434)とsys__lwp_unpark
(システムコール番号321)を呼び出します。 lwp_park
はLWPを一時停止させ、lwp_unpark
は一時停止中のLWPを再開させます。これは、Goのセマフォ実装において、ゴルーチンがリソースを待機する際にOSスレッドを効率的にブロック・アンブロックするために利用されます。runtime·semasleep
のコードには、TODO(jsing)
コメントで「潜在的なデッドロック」の可能性が指摘されています。これは、lwp_park()
を呼び出す前にwaitsemalock
ミューテックスを解放する必要があるため、別のスレッドがlwp_unpark()
を呼び出すタイミングによっては、現在のスレッドが永久にスリープしてしまう可能性があるという問題です。これは、LWPのパーキングメカニズムとユーザー空間のミューテックスの同期における複雑さを示しています。
3. アセンブリコードの変更:
src/pkg/runtime/sys_netbsd_amd64.s
ファイルは、AMD64アーキテクチャ向けのアセンブリコードを含んでいます。- このファイルでは、
rfork_thread
、thrsleep
、thrwakeup
に関連するアセンブリルーチンが削除され、lwp_create
、lwp_park
、lwp_unpark
、そして新しいLWPの開始点となるlwp_tramp
(LWPトランポリン)ルーチンが追加されました。 lwp_tramp
は、新しいLWPが起動した際に最初に実行されるコードであり、GoランタイムのM(マシン)とG(ゴルーチン)のコンテキストをセットアップし、最終的にGoの関数(fn
)を呼び出す役割を担います。
4. シグナルハンドリングの調整:
src/pkg/runtime/signal_netbsd_amd64.c
では、runtime·lwp_mcontext_init
の宣言と実装が追加され、LWPのコンテキスト初期化がシグナルハンドリングと連携して行われるようになりました。src/pkg/runtime/thread_netbsd.c
のruntime·minit
関数では、runtime·sigprocmask(SIG_SETMASK, &sigset_none, nil);
が追加されています。これは、メインスレッドのシグナルマスクをクリアし、すべてのシグナルがブロックされないようにするためのものです。これにより、Goランタイムがシグナルを適切に処理できるようになります。
これらの変更は、GoランタイムがNetBSDのカーネルとより密接に連携し、そのネイティブなスレッドモデルを最大限に活用できるようにするためのものです。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、主に以下の4つのファイルに分散しています。
-
src/pkg/runtime/os_netbsd.h
:runtime·setitimer
とruntime·sigaction
の関数宣言の順序が入れ替わっています。これは機能的な変更ではなく、おそらくコードの整理またはコンパイル時の警告を避けるためのものです。
-
src/pkg/runtime/signal_netbsd_amd64.c
:runtime·lwp_tramp
関数の外部宣言が追加されました。これは、アセンブリで実装されるLWPの開始点となる関数です。runtime·lwp_mcontext_init
関数が追加されました。この関数は、LWPの機械コンテキスト(McontextT
)を初期化し、命令ポインタ(REG_RIP
)をruntime·lwp_tramp
に、スタックポインタ(REG_RSP
)を新しいスタックに設定し、GoのM(マシン)、G(ゴルーチン)、および実行する関数(fn
)をレジスタに渡します。
-
src/pkg/runtime/sys_netbsd_amd64.s
:runtime·rfork_thread
の削除とruntime·lwp_create
への置き換え:- 古い
rfork_thread
ルーチン(sys_rfork
システムコールを使用)が完全に削除されました。 - 新しい
runtime·lwp_create
ルーチンが追加され、sys__lwp_create
システムコール(番号309)を呼び出してLWPを作成します。
- 古い
runtime·lwp_tramp
の追加:- 新しいLWPが起動した際に実行されるアセンブリルーチンです。このルーチンは、TLS(Thread Local Storage)を設定し、GoのMとGのポインタをTLSに保存し、最終的にLWPの開始関数(
R12
レジスタに格納されているfn
)を呼び出します。
- 新しいLWPが起動した際に実行されるアセンブリルーチンです。このルーチンは、TLS(Thread Local Storage)を設定し、GoのMとGのポインタをTLSに保存し、最終的にLWPの開始関数(
runtime·thrsleep
の削除とruntime·lwp_park
への置き換え:- 古い
thrsleep
ルーチン(sys_thrsleep
システムコールを使用)が削除されました。 - 新しい
runtime·lwp_park
ルーチンが追加され、sys__lwp_park
システムコール(番号434)を呼び出してLWPを一時停止させます。
- 古い
runtime·thrwakeup
の削除とruntime·lwp_unpark
への置き換え:- 古い
thrwakeup
ルーチン(sys_thrwakeup
システムコールを使用)が削除されました。 - 新しい
runtime·lwp_unpark
ルーチンが追加され、sys__lwp_unpark
システムコール(番号321)を呼び出して一時停止中のLWPを再開させます。
- 古い
-
src/pkg/runtime/thread_netbsd.c
:rfork
関連のマクロ定義(RFPROC
,RFMEM
,RFNOWAIT
,RFTHREAD
)が削除されました。runtime·rfork_thread
,runtime·thrsleep
,runtime·thrwakeup
の外部宣言が削除されました。runtime·getcontext
,runtime·lwp_create
,runtime·lwp_mcontext_init
,runtime·lwp_park
,runtime·lwp_unpark
の外部宣言が追加されました。runtime·semasleep
の変更:- セマフォの待機処理において、
runtime·thrsleep
の代わりにruntime·lwp_park
を使用するように変更されました。 TODO(jsing)
コメントで、lwp_park
を呼び出す前にミューテックスを解放する必要があることによる潜在的なデッドロックの問題が指摘されています。
- セマフォの待機処理において、
runtime·semawakeup
の変更:- セマフォの通知処理において、
runtime·thrwakeup
の代わりにruntime·lwp_unpark
を使用するように変更されました。 - ここでも
TODO(jsing)
コメントで、semasleep()
と同様の潜在的なデッドロックの問題が指摘されています。
- セマフォの通知処理において、
runtime·newosproc
の変更:- 新しいOSスレッド(LWP)を作成する際に、
runtime·rfork_thread
の代わりにruntime·getcontext
、runtime·lwp_mcontext_init
、runtime·lwp_create
を使用するように変更されました。これにより、既存のコンテキストを基に新しいLWPのコンテキストを初期化し、LWPを作成するプロセスが明確になりました。
- 新しいOSスレッド(LWP)を作成する際に、
runtime·osinit
関数にm->procid = 1;
が追加され、メインスレッドのLWP IDが1に設定されるようになりました。runtime·minit
関数にruntime·sigprocmask(SIG_SETMASK, &sigset_none, nil);
が追加され、初期化時にシグナルマスクがクリアされるようになりました。
コアとなるコードの解説
このコミットの最も重要な変更は、GoランタイムがNetBSD上でOSスレッドを扱うための低レベルなプリミティブを、NetBSDのネイティブなLWP APIに完全に切り替えた点です。
-
スレッド作成のパラダイムシフト: 以前の
rfork_thread
は、rfork
システムコールを使って新しい実行コンテキストを作成していました。これは、プロセスを複製しつつ、特定の資源(メモリ空間など)を共有するという、より汎用的なメカニズムです。しかし、NetBSDにはLWPという、より軽量でスレッド管理に特化したカーネルプリミティブが存在します。このコミットは、sys__lwp_create
システムコールを直接利用することで、GoのOSスレッド(M)がNetBSDのLWPに直接対応するようにしました。これにより、スレッドの生成と管理がより効率的になります。 -
コンテキスト初期化の明確化:
runtime·lwp_mcontext_init
の導入は、新しいLWPが起動する際の初期状態をGoランタイムが完全に制御できるようになったことを意味します。特に、LWPの命令ポインタをruntime·lwp_tramp
というアセンブリルーチンに設定することで、GoランタイムはLWPが起動した直後に必要な初期化(TLSの設定、MとGのポインタのロードなど)を実行し、その後でGoの関数を実行するという、Goのスケジューラモデルに合致したフローを確立できます。 -
セマフォ同期の最適化と課題:
runtime·lwp_park
とruntime·lwp_unpark
への移行は、Goのセマフォ(runtime·semasleep
とruntime·semawakeup
)がNetBSDのLWPパーキングメカニズムを直接利用するようになったことを示します。これは、ゴルーチンがブロックされた際に、対応するOSスレッドを効率的に一時停止させ、ゴルーチンが再開可能になった際にOSスレッドを効率的に再開させるために重要です。 しかし、TODO(jsing)
コメントが示すように、ユーザー空間のミューテックス(m->waitsemalock
)とカーネルのLWPパーキングメカニズムの間の同期には注意が必要です。lwp_park
を呼び出す前にミューテックスを解放する必要があるため、その間にlwp_unpark
が呼び出されると、スレッドが永久にスリープしてしまう「競合状態」が発生する可能性があります。これは、低レベルなOSインタラクションにおける同期の複雑さと、Goランタイムが直面する課題を示しています。この問題は、後のコミットで解決されたか、あるいは特定の条件下でのみ発生する稀なケースとして許容された可能性があります。 -
アセンブリコードの役割:
sys_netbsd_amd64.s
におけるアセンブリコードの変更は、GoランタイムがNetBSDカーネルのLWP APIと直接対話するための「接着剤」の役割を果たしています。システムコールを直接呼び出し、レジスタを操作することで、GoランタイムはOSの低レベルな機能に最大限にアクセスし、パフォーマンスを最適化しています。
このコミットは、Go言語がNetBSDという特定のOS環境で、その並行処理モデルを効率的に実現するための、OS固有の深い最適化を行った証拠と言えます。
関連リンク
- Go言語の公式ドキュメント: Go言語のランタイム、ゴルーチン、スケジューラに関する詳細な情報が提供されています。
- NetBSDプロジェクトの公式ウェブサイト: NetBSDのLWPやシステムコールに関するドキュメントが見つかる可能性があります。
- GoのCL (Change List) 6222044: このコミットに対応するGoのコードレビューシステム(Gerrit)の変更リスト。より詳細な議論や関連する変更履歴が含まれている可能性があります。
参考にした情報源リンク
- NetBSDのLWPに関する情報:
- https://man.netbsd.org/lwp.7 (LWPに関するmanページ)
- https://man.netbsd.org/lwp_create.2 (
lwp_create
システムコールに関するmanページ) - https://man.netbsd.org/lwp_park.2 (
lwp_park
システムコールに関するmanページ)
- Goランタイムの内部に関する記事やプレゼンテーション:
- Goのスケジューラやランタイムの内部構造について解説している記事は多数存在します。例えば、「Go's work-stealing scheduler」などで検索すると良いでしょう。
- UNIX系OSのシステムコールとスレッドに関する一般的な情報:
- オペレーティングシステムに関する教科書やオンラインリソース。
- Goのソースコード:
- GoのGitHubリポジトリ: https://github.com/golang/go
- 特に
src/runtime
ディレクトリは、ランタイムのコア部分が含まれています。
- NetBSDのソースコード:
- NetBSDのGitHubリポジトリや公式のソースコードリポジトリで、LWP関連のシステムコール実装やヘッダファイルを確認できます。