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

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

このコミットは、Go言語のリンカ(liblink)とランタイム(runtime)におけるplan9/amd64アーキテクチャのサポートを初期的に導入するものです。特に、Plan 9オペレーティングシステム上でのGoプログラムの起動処理と、スレッドローカルストレージ(TLS)の扱いを改善し、コードの標準化と簡素化を図っています。

コミット

commit decd810945e22642df5db99a616af3cc5d53bf8d
Author: Aram Hăvărneanu <aram@mgk.ro>
Date:   Wed Jul 2 21:04:10 2014 +1000

    liblink, runtime: preliminary support for plan9/amd64
    
    A TLS slot is reserved by _rt0_.*_plan9 as an automatic and
    its address (which is static on Plan 9) is saved in the
    global _privates symbol. The startup linkage now is exactly
    like that from Plan 9 libc, and the way we access g is
    exactly as if we'd have used privalloc(2).
    
    Aside from making the code more standard, this change
    drastically simplifies it, both for 386 and for amd64, and
    makes the Plan 9 code in liblink common for both 386 and
    amd64.
    
    The amd64 runtime code was cleared of nxm assumptions, and
    now runs on the standard Plan 9 kernel.
    
    Note handling fixes will follow in a separate CL.
    
    LGTM=rsc
    R=golang-codereviews, rsc, bradfitz, dave
    CC=0intro, ality, golang-codereviews, jas, minux.ma, mischief
    https://golang.org/cl/101510049

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

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

元コミット内容

liblink, runtime: preliminary support for plan9/amd64

このコミットは、liblink(Goリンカ)とruntime(Goランタイム)にplan9/amd64アーキテクチャの初期サポートを追加します。

_rt0_.*_plan9によってTLS(Thread Local Storage)スロットが自動的に予約され、そのアドレス(Plan 9では静的)がグローバルシンボル_privatesに保存されます。これにより、起動時のリンケージはPlan 9のlibcと全く同じになり、g(goroutine構造体へのポインタ)へのアクセスはprivalloc(2)を使用したかのように行われます。

この変更は、コードをより標準的なものにするだけでなく、386とamd64の両方で大幅に簡素化し、liblink内のPlan 9関連コードを386とamd64で共通化します。

amd64ランタイムコードはnxm(non-executable memory)の仮定から解放され、標準のPlan 9カーネル上で動作するようになります。

ノートハンドリングの修正は、別の変更リスト(CL)で対応されます。

変更の背景

このコミットの主な背景は、Go言語がより多くのプラットフォームで動作するように、特にPlan 9オペレーティングシステムにおけるamd64アーキテクチャのサポートを強化することにあります。

Goのランタイムは、各オペレーティングシステムとアーキテクチャの組み合わせに対して、低レベルの起動コード、システムコールインターフェース、メモリ管理などを実装する必要があります。Plan 9は、その独特な設計思想とマイクロカーネルアーキテクチャにより、他のUnix系システムとは異なるアプローチを必要とします。

以前のGoのPlan 9サポートは、主に386アーキテクチャに焦点を当てていましたが、amd64アーキテクチャへの対応が求められていました。このコミットは、amd64上でのGoランタイムの動作を安定させ、Plan 9の標準的な慣習に合わせることを目的としています。

具体的には、以下の点が課題となっていました。

  1. TLS(Thread Local Storage)の扱い: Goのランタイムは、現在のgoroutine(g)へのポインタを効率的に取得するためにTLSを利用します。Plan 9におけるTLSの管理方法は、他のOSとは異なるため、これに適応させる必要がありました。
  2. 起動リンケージの標準化: Goプログラムの起動時、OSから制御を受け取った後の初期化プロセスは、OSの慣習に沿っている必要があります。Plan 9のlibc(標準Cライブラリ)の起動リンケージに合わせることで、より堅牢で互換性のある起動プロセスを実現します。
  3. nxm(non-executable memory)の仮定の排除: 一部のOSでは、データセグメントが実行不可能(non-executable)であるという仮定(nxm)に基づいてコードが書かれることがあります。Plan 9カーネルは必ずしもこの仮定を満たさない場合があるため、amd64ランタイムコードからこの仮定を排除し、より汎用的に動作するようにする必要がありました。
  4. コードの簡素化と共通化: 386amd64の両方でPlan 9をサポートするにあたり、重複するコードを減らし、共通のロジックを使用することで、保守性を向上させ、コードベースを簡素化することが望まれました。

これらの課題に対処することで、GoがPlan 9/amd64環境でより安定して動作し、将来的な開発が容易になる基盤を築くことが、このコミットの背景にあります。

前提知識の解説

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

1. Plan 9オペレーティングシステム

  • 概要: Plan 9 from Bell Labsは、Unixの後継として設計された分散オペレーティングシステムです。すべてのリソース(ファイル、デバイス、ネットワーク接続など)をファイルとして表現し、ファイルシステムを通じてアクセスするという「すべてはファイルである」という強力な哲学を持っています。
  • 特徴:
    • ファイルシステム中心: すべてのリソースがファイルとして扱われ、標準的なファイル操作(open, read, write, close)でアクセスできます。
    • 名前空間: 各プロセスは独自の「名前空間」を持ち、異なるリソースをマウントできます。
    • ユニオンマウント: 複数のディレクトリを重ね合わせて一つの論理的なディレクトリとして扱うことができます。
    • プロトコル: 9Pという独自のネットワークプロトコルを使用し、分散環境でのファイルアクセスを可能にします。
  • TLS(Thread Local Storage)の扱い: Plan 9では、スレッドローカルストレージは通常、_privatesというグローバルシンボルを通じてアクセスされます。これは、各プロセスが持つプライベートなデータ領域へのポインタであり、privalloc(2)システムコールによって割り当てられます。Goランタイムがg(現在のgoroutine構造体へのポインタ)を効率的に取得するために、この_privatesの仕組みを利用します。
  • 起動プロセス: Plan 9におけるプログラムの起動は、他のUnix系システムとは異なる場合があります。特に、初期スタックフレームの構造や、引数・環境変数の渡し方、そしてTLSの初期化方法に違いがあります。

2. スレッドローカルストレージ (TLS)

  • 概要: TLSは、マルチスレッド環境において、各スレッドが自分専用のデータを持つための仕組みです。グローバル変数や静的変数は通常、すべてのスレッドで共有されますが、TLSに格納された変数は各スレッドが独立したコピーを持ちます。
  • GoにおけるTLSとg: Goランタイムでは、現在のgoroutineのコンテキスト(g構造体)へのポインタを非常に頻繁に参照します。このgポインタを効率的に取得するために、TLSが利用されることがあります。特定のアーキテクチャやOSでは、CPUのレジスタ(例: FSまたはGSセグメントレジスタ)や、OSが提供するTLSメカニズムを通じてgポインタを格納し、高速にアクセスできるようにします。
  • _privatesシンボル: Plan 9では、TLSの概念は_privatesというグローバルシンボルによって実現されます。これは、プロセスごとに一意のデータ領域を指すポインタであり、privalloc(2)システムコールによって割り当てられます。Goランタイムは、この_privatesを利用してgポインタを管理します。
  • 概要: liblinkは、Go言語のコンパイラが出力したオブジェクトファイルを結合し、実行可能ファイルを生成するGoの内部リンカです。アセンブリコードの処理、シンボル解決、セクションの配置、そしてOS固有の起動コードのリンクなどを担当します。
  • 役割: OSやアーキテクチャに特化したリンケージ(起動コードの結合方法)を理解し、適切なシステムコールやTLSアクセス方法を埋め込む必要があります。

4. Goランタイム (runtime)

  • 概要: Goランタイムは、Goプログラムの実行を管理する低レベルのコード群です。ガベージコレクション、スケジューラ(goroutineの管理)、メモリ割り当て、システムコールインターフェース、スタック管理など、Goプログラムが動作するために必要なすべての基盤を提供します。
  • OS/アーキテクチャ固有のコード: ランタイムの多くの部分は、OSやCPUアーキテクチャに依存します。例えば、システムコールの呼び出し規約、メモリ保護の仕組み、スレッドの作成と管理などは、OSごとに異なる実装が必要です。

5. rt0 (ランタイム起動コード)

  • 概要: rt0(runtime zero)は、Goプログラムのエントリポイントとなるアセンブリコードです。OSから制御が移された直後に実行され、Goランタイムの初期化、コマンドライン引数の処理、そしてmain関数の呼び出しを行います。
  • 役割: rt0は、OS固有の起動規約に厳密に従う必要があります。スタックの設定、TLSの初期化、そしてGoランタイムのC言語部分へのジャンプなどを行います。

6. nxm (Non-eXecutable Memory)

  • 概要: nxmは、メモリ領域が実行不可能であることを示すプロパティです。これは、データ領域に悪意のあるコードが注入されて実行されるのを防ぐためのセキュリティ機能です。
  • Plan 9とnxm: Plan 9カーネルは、必ずしもすべてのメモリ領域でnxmを厳密に強制するわけではありません。そのため、nxmの仮定に依存して書かれたコードは、Plan 9上で問題を引き起こす可能性があります。このコミットでは、amd64ランタイムコードからnxmの仮定を排除し、より柔軟なメモリ保護ポリシーを持つPlan 9カーネル上でも正しく動作するように修正しています。

これらの前提知識を理解することで、コミットがGoのPlan 9/amd64サポートにおいてどのような技術的課題を解決し、どのような改善をもたらしたのかを深く把握することができます。

技術的詳細

このコミットの技術的詳細は、主にPlan 9におけるGoランタイムの起動プロセスとTLSの管理方法の変更に集約されます。

1. TLSスロットの予約と_privatesシンボルの利用

  • 変更前: 以前のGoランタイムのPlan 9実装では、TLSの扱いがPlan 9の標準的な慣習と完全に一致していなかった可能性があります。特に、g(現在のgoroutine構造体へのポインタ)へのアクセス方法が、Plan 9のprivalloc(2)システムコールが提供するメカニズムと直接的に連携していなかったかもしれません。
  • 変更後:
    • _rt0_.*_plan9(Plan 9向けの起動アセンブリコード)が、TLSスロットを「自動的に」予約するようになりました。これは、OSがプロセス起動時に提供する特定のメモリ領域やレジスタを利用して、スレッドローカルなデータを格納する準備をすることを意味します。
    • この予約されたTLSスロットのアドレス(Plan 9では静的、つまりプログラムの実行中に変化しない)が、グローバルシンボル_privatesに保存されます。_privatesは、Plan 9においてプロセスごとのプライベートなデータ領域を指す標準的なシンボルです。
    • これにより、Goランタイムがgポインタにアクセスする方法が、Plan 9のlibcがprivalloc(2)を通じてプライベートデータを取得する方法と「全く同じ」になりました。これは、GoランタイムがPlan 9のOSインターフェースとより密接に統合され、標準的な慣習に従うようになったことを意味します。

2. 起動リンケージの標準化

  • 変更前: Goプログラムの起動リンケージ(OSから制御を受け取った後の初期化シーケンス)が、Plan 9のlibcのそれと異なっていた可能性があります。これにより、互換性の問題や、OSの低レベルな挙動との不一致が生じる可能性がありました。
  • 変更後: 起動リンケージがPlan 9のlibcと「全く同じ」になるように修正されました。これは、_rt0_plan9_386.s_rt0_plan9_amd64.sのアセンブリコードが、Plan 9の標準的なエントリポイントの規約、スタックフレームのセットアップ、引数の渡し方などに合わせて調整されたことを示唆しています。この標準化により、GoプログラムがPlan 9環境でより安定して動作し、OSの期待する挙動に沿うようになります。

3. nxm(non-executable memory)仮定の排除

  • 変更前: amd64ランタイムコードの一部が、データセグメントが実行不可能であるというnxmの仮定に依存して書かれていた可能性があります。しかし、すべてのPlan 9カーネルがこのnxmを厳密に強制するわけではないため、特定の環境で問題が発生する可能性がありました。
  • 変更後: amd64ランタイムコードからnxmの仮定が「クリア」されました。これは、コードがデータ領域の実行可能性に依存しないように修正されたことを意味します。具体的には、src/pkg/runtime/sys_plan9_amd64.sなどのアセンブリコードから、不必要なメモリ保護関連の命令や、nxmを前提とした最適化が削除されたと考えられます。これにより、Goランタイムはより広範なPlan 9カーネルバージョンで動作するようになります。

4. コードの簡素化と共通化

  • この変更は、386amd64の両方でPlan 9関連のコードを大幅に簡素化しました。特に、liblink内のPlan 9コードが両アーキテクチャで共通化されたと述べられています。これは、TLSアクセスや起動リンケージの標準化によって、アーキテクチャ固有の差異が吸収され、より抽象化された共通のロジックで対応できるようになったことを示唆しています。
  • 例えば、src/liblink/asm6.csrc/liblink/asm8.c(それぞれ386とamd64のアセンブラコード生成)におけるTLS関連の処理が、_privatesシンボルを使用するように統一されています。また、src/liblink/sym.cからTLSオフセットの計算が削除されているのは、_privatesシンボルを通じてアドレスが直接取得されるようになったため、リンカ側でオフセットを計算する必要がなくなったことを示しています。

5. memclr_plan9_amd64.sの変更

  • src/pkg/runtime/memclr_plan9_amd64.sの変更は、メモリクリア(memclr)関数の実装を簡素化しています。以前のバージョンは、クリアするバイト数に応じて複数の分岐を持っていましたが、新しいバージョンではREP STOSQREP STOSB命令を組み合わせて、より汎用的なループでメモリをクリアするように変更されています。これは、アセンブリコードの効率化と簡素化の一例です。

6. nanotimeの実装変更

  • src/pkg/runtime/os_plan9.cruntime·nanotime関数が追加され、src/pkg/runtime/time_plan9_386.cが削除されています。これは、ナノ秒単位の時刻を取得する機能が、OS固有のCコードに統合され、より効率的かつ標準的な方法で実装されたことを示しています。_dev_bintimeというPlan 9のデバイスファイルから時刻情報を読み取ることで、高精度な時刻取得を実現しています。

これらの技術的変更は、GoがPlan 9/amd64環境でより堅牢に、効率的に、そしてPlan 9の慣習に沿って動作するための重要なステップです。

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

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

  1. include/link.h:

    • LSym* plan9tos;LSym* plan9privates; に変更されています。これは、Plan 9におけるTLS関連のシンボル参照が、_tos(Top Of Stack)から_privatesに切り替わったことを示しています。
  2. src/liblink/asm6.c および src/liblink/asm8.c:

    • Hplan9(Plan 9ヘッダタイプ)におけるTLSアクセスロジックが変更されています。
    • 以前はctxt->plan9tosを参照していた箇所が、ctxt->plan9privatesを参照するように変更されています。
    • _privatesシンボルをリンカがルックアップし、そのアドレスをTLSのベースとして使用するコードが追加されています。
    • asm6.cでは、prefixof関数からHplan9のケースが削除され、TLSベースの決定ロジックがHplan9の新しいケースとして追加されています。これは、TLSのアクセス方法がより標準的な_privatesシンボル経由になったため、特定のセグメントレジスタ(FS)を使用する必要がなくなったことを示唆しています。
  3. src/liblink/obj6.c および src/liblink/obj8.c:

    • canuselocaltls関数でHplan9がコメントアウトから有効化されています。これは、Plan 9でもローカルTLSが利用可能になったことを示唆しています。
    • addstacksplit関数内で、ctxt->plan9privatesnilの場合に_privatesシンボルをルックアップする処理が追加されています。
  4. src/liblink/sym.c:

    • Hplan9におけるctxt->tlsoffsetの設定が削除されています。これは、TLSオフセットをリンカが直接計算するのではなく、_privatesシンボルを通じてアドレスが直接取得されるようになったため、このオフセットが不要になったことを示しています。
  5. src/pkg/runtime/arch_amd64.h:

    • RuntimeGogoBytesの定義にGOOS_plan9の条件が追加され、amd64におけるPlan 9のRuntimeGogoBytesが80バイトに設定されています。
  6. src/pkg/runtime/defs_plan9_amd64.h:

    • PAGESIZEの定義が0x200000ULLから0x1000に変更されています。これは、Plan 9/amd64におけるページサイズがより一般的な4KB(0x1000)に調整されたことを示しています。
  7. src/pkg/runtime/memclr_plan9_amd64.s:

    • runtime·memclrのアセンブリ実装が大幅に簡素化されています。以前はバイト数に応じて複数の分岐を持っていましたが、REP STOSQREP STOSB命令を組み合わせて、より汎用的なループでメモリをクリアするように変更されています。
  8. src/pkg/runtime/os_plan9.c:

    • runtime·nanotime関数が新しく追加されています。この関数は/dev/bintimeデバイスファイルから高精度な時刻情報を読み取ることで、ナノ秒単位の時刻を取得します。
  9. src/pkg/runtime/rt0_plan9_386.s および src/pkg/runtime/rt0_plan9_amd64.s:

    • Plan 9向けの起動アセンブリコードが大幅に修正されています。
    • _tos(Top Of Stack)シンボルへの設定に加え、_privatesシンボルへのアドレス設定が追加されています。
    • スタックのセットアップ、引数の渡し方、そして_rt0_goへのジャンプロジックが、Plan 9の標準的な起動リンケージに合うように調整されています。特に、amd64版では$24バイトのスタックフレームが確保されています。
    • _privates_nprivatesという新しいグローバルシンボルが定義されています。
  10. src/pkg/runtime/sys_plan9_386.s および src/pkg/runtime/sys_plan9_amd64.s:

    • システムコール呼び出しのアセンブリコードが簡素化されています。特に、amd64版では、システムコール呼び出し前にAXレジスタに0x8000を設定する命令が削除されています。これは、Plan 9のシステムコール規約が変更されたか、またはGoランタイムがより直接的な方法でシステムコールを呼び出すようになったことを示唆しています。
    • runtime·rfork関数内で、m_procidの初期化が_tos(SB)から行われるように変更されています。
    • sys_plan9_amd64.sからruntime·nanotimeのアセンブリ実装が削除されています(Cコードに移行したため)。
  11. src/pkg/runtime/time_plan9_386.c:

    • このファイルは削除されています。runtime·nanotimeの実装がos_plan9.cに移動したためです。

これらの変更は、GoのPlan 9/amd64サポートをより堅牢で、標準に準拠し、効率的なものにするための基盤を形成しています。

コアとなるコードの解説

このコミットのコアとなる変更は、GoランタイムがPlan 9上でどのように起動し、どのようにスレッドローカルなデータ(特に現在のgoroutineへのポインタg)にアクセスするかというメカニズムの根本的な見直しにあります。

1. _privatesシンボルへの移行とTLSアクセス

最も重要な変更は、Plan 9におけるTLSの管理方法を_tos(Top Of Stack)から_privatesシンボルへと移行した点です。

  • _tosから_privates: 以前は、Goランタイムが_tosシンボル(スタックの最上位を指す)を利用してTLSのような機能を実現しようとしていた可能性があります。しかし、Plan 9の標準的な慣習では、プロセスごとのプライベートなデータは_privatesというグローバルシンボルを通じてアクセスされます。この_privatesは、privalloc(2)システムコールによって割り当てられる領域を指します。
  • rt0における_privatesの初期化:
    • src/pkg/runtime/rt0_plan9_386.ssrc/pkg/runtime/rt0_plan9_amd64.sの起動アセンブリコードで、_privatesシンボルが初期化されるようになりました。具体的には、スタック上の特定のアドレス(LEAL 8(SP), AXLEAQ 16(SP), AX)を_privates(SB)に格納しています。これは、Goランタイムが起動時に、Plan 9が提供するプライベートデータ領域へのポインタを_privatesシンボルに保存することを意味します。
    • _nprivatesというシンボルも1に設定されており、これはプライベートデータ領域が有効であることを示唆しています。
  • リンカ(liblink)の変更:
    • include/link.hplan9tosplan9privatesに置き換えられました。
    • src/liblink/asm6.csrc/liblink/asm8.csrc/liblink/obj6.csrc/liblink/obj8.cでは、TLS関連の処理がctxt->plan9privatesシンボルを参照するように変更されました。これにより、リンカは_privatesシンボルを解決し、Goプログラムがこのシンボルを通じてTLSデータにアクセスできるようにします。
    • src/liblink/sym.cからctxt->tlsoffsetの設定が削除されたのは、_privatesシンボルが直接アドレスを提供するため、リンカがオフセットを計算する必要がなくなったためです。

この変更により、GoランタイムはPlan 9のOSインターフェースとより密接に連携し、gポインタへのアクセスがPlan 9の標準的な方法に準拠するようになりました。これは、コードの標準化と、将来的な互換性の向上に寄与します。

2. nxm仮定の排除とシステムコール呼び出しの簡素化

  • src/pkg/runtime/sys_plan9_amd64.sの変更:
    • このファイルでは、runtime·open, runtime·pread, runtime·pwriteなどのシステムコールを呼び出すアセンブリコードから、MOVQ $0x8000, AXという命令が削除されています。
    • この0x8000は、特定のメモリ保護フラグや、nxm(non-executable memory)に関連する仮定を示していた可能性があります。Plan 9カーネルは必ずしもnxmを厳密に強制しないため、この仮定を排除することで、Goランタイムがより広範なPlan 9環境で動作するようになります。
    • この変更は、システムコール呼び出しのオーバーヘッドをわずかに削減し、コードを簡素化する効果もあります。

3. memclr関数の最適化

  • src/pkg/runtime/memclr_plan9_amd64.sにおけるruntime·memclr関数の変更は、メモリをゼロクリアするアセンブリコードの最適化です。
  • 以前のバージョンは、クリアするバイト数に応じて複数の分岐(1バイト、2バイト、4バイト、8バイト、16バイトなど)を持っていましたが、新しいバージョンではREP STOSQ(8バイト単位でストア)とREP STOSB(1バイト単位でストア)命令を組み合わせて、より汎用的なループでメモリをクリアするように変更されています。
  • この変更は、コードの簡素化と、特定のケースでのパフォーマンス向上に寄与します。

4. nanotimeの実装変更

  • src/pkg/runtime/time_plan9_386.cが削除され、runtime·nanotimeの実装がsrc/pkg/runtime/os_plan9.cに移動しました。
  • 新しいruntime·nanotimeは、Plan 9の/dev/bintimeデバイスファイルから時刻情報を読み取ることで、ナノ秒単位の高精度な時刻を取得します。これは、OS固有のデバイスを利用することで、より正確で効率的な時刻取得を実現しています。また、ファイルディスクリプタを静的にキャッシュすることで、繰り返し呼び出し時のオーバーヘッドを削減しています。

これらのコアとなる変更は、GoがPlan 9/amd64環境でより効率的かつ堅牢に動作するための基盤を強化し、Plan 9のシステム設計思想に沿った実装へと進化させています。

関連リンク

参考にした情報源リンク

  • Go言語のコミットメッセージと差分
  • Plan 9のドキュメント(privalloc(2)など)
  • Go言語のランタイムに関する一般的な知識
  • アセンブリ言語(x86-64)の知識
  • オペレーティングシステムの低レベルな概念(TLS、起動プロセスなど)
  • GoのIssue Trackerやメーリングリストでの関連議論(もしあれば)

(注:具体的なWeb検索結果のURLは、検索時の状況によって変動するため、ここでは一般的な情報源のカテゴリを記載しています。) I have generated the detailed technical explanation for the commit.