[インデックス 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の標準的な慣習に合わせることを目的としています。
具体的には、以下の点が課題となっていました。
- TLS(Thread Local Storage)の扱い: Goのランタイムは、現在のgoroutine(
g
)へのポインタを効率的に取得するためにTLSを利用します。Plan 9におけるTLSの管理方法は、他のOSとは異なるため、これに適応させる必要がありました。 - 起動リンケージの標準化: Goプログラムの起動時、OSから制御を受け取った後の初期化プロセスは、OSの慣習に沿っている必要があります。Plan 9のlibc(標準Cライブラリ)の起動リンケージに合わせることで、より堅牢で互換性のある起動プロセスを実現します。
nxm
(non-executable memory)の仮定の排除: 一部のOSでは、データセグメントが実行不可能(non-executable)であるという仮定(nxm
)に基づいてコードが書かれることがあります。Plan 9カーネルは必ずしもこの仮定を満たさない場合があるため、amd64
ランタイムコードからこの仮定を排除し、より汎用的に動作するようにする必要がありました。- コードの簡素化と共通化:
386
とamd64
の両方で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
ポインタを管理します。
3. liblink
(Goリンカ)
- 概要:
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. コードの簡素化と共通化
- この変更は、
386
とamd64
の両方でPlan 9関連のコードを大幅に簡素化しました。特に、liblink
内のPlan 9コードが両アーキテクチャで共通化されたと述べられています。これは、TLSアクセスや起動リンケージの標準化によって、アーキテクチャ固有の差異が吸収され、より抽象化された共通のロジックで対応できるようになったことを示唆しています。 - 例えば、
src/liblink/asm6.c
とsrc/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 STOSQ
とREP STOSB
命令を組み合わせて、より汎用的なループでメモリをクリアするように変更されています。これは、アセンブリコードの効率化と簡素化の一例です。
6. nanotime
の実装変更
src/pkg/runtime/os_plan9.c
にruntime·nanotime
関数が追加され、src/pkg/runtime/time_plan9_386.c
が削除されています。これは、ナノ秒単位の時刻を取得する機能が、OS固有のCコードに統合され、より効率的かつ標準的な方法で実装されたことを示しています。_dev_bintime
というPlan 9のデバイスファイルから時刻情報を読み取ることで、高精度な時刻取得を実現しています。
これらの技術的変更は、GoがPlan 9/amd64環境でより堅牢に、効率的に、そしてPlan 9の慣習に沿って動作するための重要なステップです。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、主に以下のファイルに集中しています。
-
include/link.h
:LSym* plan9tos;
がLSym* plan9privates;
に変更されています。これは、Plan 9におけるTLS関連のシンボル参照が、_tos
(Top Of Stack)から_privates
に切り替わったことを示しています。
-
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)を使用する必要がなくなったことを示唆しています。
-
src/liblink/obj6.c
およびsrc/liblink/obj8.c
:canuselocaltls
関数でHplan9
がコメントアウトから有効化されています。これは、Plan 9でもローカルTLSが利用可能になったことを示唆しています。addstacksplit
関数内で、ctxt->plan9privates
がnil
の場合に_privates
シンボルをルックアップする処理が追加されています。
-
src/liblink/sym.c
:Hplan9
におけるctxt->tlsoffset
の設定が削除されています。これは、TLSオフセットをリンカが直接計算するのではなく、_privates
シンボルを通じてアドレスが直接取得されるようになったため、このオフセットが不要になったことを示しています。
-
src/pkg/runtime/arch_amd64.h
:RuntimeGogoBytes
の定義にGOOS_plan9
の条件が追加され、amd64
におけるPlan 9のRuntimeGogoBytes
が80バイトに設定されています。
-
src/pkg/runtime/defs_plan9_amd64.h
:PAGESIZE
の定義が0x200000ULL
から0x1000
に変更されています。これは、Plan 9/amd64におけるページサイズがより一般的な4KB(0x1000)に調整されたことを示しています。
-
src/pkg/runtime/memclr_plan9_amd64.s
:runtime·memclr
のアセンブリ実装が大幅に簡素化されています。以前はバイト数に応じて複数の分岐を持っていましたが、REP STOSQ
とREP STOSB
命令を組み合わせて、より汎用的なループでメモリをクリアするように変更されています。
-
src/pkg/runtime/os_plan9.c
:runtime·nanotime
関数が新しく追加されています。この関数は/dev/bintime
デバイスファイルから高精度な時刻情報を読み取ることで、ナノ秒単位の時刻を取得します。
-
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
という新しいグローバルシンボルが定義されています。
-
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コードに移行したため)。
- システムコール呼び出しのアセンブリコードが簡素化されています。特に、
-
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.s
とsrc/pkg/runtime/rt0_plan9_amd64.s
の起動アセンブリコードで、_privates
シンボルが初期化されるようになりました。具体的には、スタック上の特定のアドレス(LEAL 8(SP), AX
やLEAQ 16(SP), AX
)を_privates(SB)
に格納しています。これは、Goランタイムが起動時に、Plan 9が提供するプライベートデータ領域へのポインタを_privates
シンボルに保存することを意味します。_nprivates
というシンボルも1
に設定されており、これはプライベートデータ領域が有効であることを示唆しています。
- リンカ(
liblink
)の変更:include/link.h
でplan9tos
がplan9privates
に置き換えられました。src/liblink/asm6.c
、src/liblink/asm8.c
、src/liblink/obj6.c
、src/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言語の公式ドキュメント: https://golang.org/doc/
- Plan 9 from Bell Labs: https://9p.io/plan9/
- Goのソースコードリポジトリ: https://github.com/golang/go
- Goの変更リスト(CL)システム: https://go-review.googlesource.com/
参考にした情報源リンク
- Go言語のコミットメッセージと差分
- Plan 9のドキュメント(
privalloc(2)
など) - Go言語のランタイムに関する一般的な知識
- アセンブリ言語(x86-64)の知識
- オペレーティングシステムの低レベルな概念(TLS、起動プロセスなど)
- GoのIssue Trackerやメーリングリストでの関連議論(もしあれば)
(注:具体的なWeb検索結果のURLは、検索時の状況によって変動するため、ここでは一般的な情報源のカテゴリを記載しています。) I have generated the detailed technical explanation for the commit.