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

[インデックス 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モデルに最適化される必要がありました。

前提知識の解説

このコミットを理解するためには、以下の概念についての知識が役立ちます。

  1. Goランタイムとゴルーチン (Goroutines):

    • Go言語の並行処理の基本単位はゴルーチンです。ゴルーチンはOSスレッドよりもはるかに軽量で、数百万個を同時に実行することも可能です。
    • Goランタイムには、ゴルーチンをOSスレッドにマッピングし、実行をスケジュールするM:Nスケジューラが組み込まれています。M個のゴルーチンをN個のOSスレッドで実行します(M >= N)。
    • このスケジューラが効率的に機能するためには、OSスレッドの生成、破棄、一時停止、再開といった操作が高速である必要があります。
  2. NetBSDと軽量プロセス (LWP: Lightweight Process):

    • NetBSDは、UNIX系のオープンソースオペレーティングシステムです。
    • NetBSDにおけるLWPは、カーネルが直接管理する実行単位であり、POSIXスレッド(pthreads)の実装基盤となっています。LWPは、プロセス内の独立した実行コンテキストを提供し、CPUスケジューリングの対象となります。
    • LWPは、従来のプロセスよりも軽量で、プロセス間でリソースを共有しやすいため、マルチスレッドアプリケーションに適しています。
  3. rforkシステムコール (Linux cloneの概念):

    • rforkは、Plan 9オペレーティングシステムに由来するシステムコールで、プロセスを複製する際に、親プロセスと子プロセスが共有するリソース(メモリ空間、ファイルディスクリプタなど)を細かく制御できるのが特徴です。
    • Linuxのcloneシステムコールも同様の機能を提供し、スレッドの実装によく使われます。Goの初期のランタイムでは、このようなfork系のシステムコールを抽象化してスレッドを作成していました。
  4. システムコール (Syscall):

    • アプリケーションがOSのカーネル機能を利用するためのインターフェースです。各システムコールには一意の番号が割り当てられており、アセンブリ言語で直接呼び出すことができます。
    • このコミットでは、sys_rforksys_getthridsys_threxitといった古いシステムコールから、sys__lwp_createsys__lwp_exitsys__lwp_parksys__lwp_unparkといったLWP関連の新しいシステムコールへの移行が見られます。
  5. アセンブリ言語 (Assembly Language):

    • CPUが直接実行できる機械語に非常に近い低レベルなプログラミング言語です。OSのカーネルやランタイムの非常に低レベルな部分(スレッドのコンテキストスイッチ、システムコール呼び出しなど)では、パフォーマンスやOSとの直接的なインタラクションのためにアセンブリ言語が使用されることがあります。
    • Goランタイムの多くの部分はGoで書かれていますが、OSとの境界部分やパフォーマンスがクリティカルな部分では、C言語やアセンブリ言語が使われます。
  6. シグナルハンドリング (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·semasleepruntime·semawakeup)は、スレッドを一時停止・再開するために使用されます。
  • 以前は、runtime·thrsleepruntime·thrwakeupという関数が、それぞれsys_thrsleep(システムコール番号300)とsys_thrwakeup(システムコール番号301)を使用していました。これらは、NetBSDの古いスレッド同期メカニズムの一部であった可能性があります。
  • このコミットでは、これらの関数が削除され、代わりにruntime·lwp_parkruntime·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_threadthrsleepthrwakeupに関連するアセンブリルーチンが削除され、lwp_createlwp_parklwp_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.cruntime·minit関数では、runtime·sigprocmask(SIG_SETMASK, &sigset_none, nil);が追加されています。これは、メインスレッドのシグナルマスクをクリアし、すべてのシグナルがブロックされないようにするためのものです。これにより、Goランタイムがシグナルを適切に処理できるようになります。

これらの変更は、GoランタイムがNetBSDのカーネルとより密接に連携し、そのネイティブなスレッドモデルを最大限に活用できるようにするためのものです。

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

このコミットにおけるコアとなるコードの変更は、主に以下の4つのファイルに分散しています。

  1. src/pkg/runtime/os_netbsd.h:

    • runtime·setitimerruntime·sigactionの関数宣言の順序が入れ替わっています。これは機能的な変更ではなく、おそらくコードの整理またはコンパイル時の警告を避けるためのものです。
  2. 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)をレジスタに渡します。
  3. 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)を呼び出します。
    • 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を再開させます。
  4. 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·getcontextruntime·lwp_mcontext_initruntime·lwp_createを使用するように変更されました。これにより、既存のコンテキストを基に新しいLWPのコンテキストを初期化し、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_parkruntime·lwp_unparkへの移行は、Goのセマフォ(runtime·semasleepruntime·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に関する情報:
  • Goランタイムの内部に関する記事やプレゼンテーション:
    • Goのスケジューラやランタイムの内部構造について解説している記事は多数存在します。例えば、「Go's work-stealing scheduler」などで検索すると良いでしょう。
  • UNIX系OSのシステムコールとスレッドに関する一般的な情報:
    • オペレーティングシステムに関する教科書やオンラインリソース。
  • Goのソースコード:
    • GoのGitHubリポジトリ: https://github.com/golang/go
    • 特にsrc/runtimeディレクトリは、ランタイムのコア部分が含まれています。
  • NetBSDのソースコード:
    • NetBSDのGitHubリポジトリや公式のソースコードリポジトリで、LWP関連のシステムコール実装やヘッダファイルを確認できます。