[インデックス 14307] ファイルの概要
このコミットは、Go言語がFreeBSDオペレーティングシステム上のARMアーキテクチャをサポートするためのCGO(C言語との相互運用)機能を追加するものです。具体的には、FreeBSD/ARM環境でのCGOのビルドとランタイムの挙動を調整し、GoプログラムがC言語で書かれたライブラリを適切に呼び出せるようにします。これは、GoがFreeBSD/ARMプラットフォームで完全に機能するための最終段階の変更の一つとされています。
コミット
- コミットハッシュ:
31f8b07e55d6f712315eea021ee84bff73f87c51 - Author: Shenghou Ma minux.ma@gmail.com
- Date: Sat Nov 3 02:22:37 2012 +0800
- コミットメッセージ:
runtime/cgo, go/build: cgo support for FreeBSD/ARM This is the last CL for FreeBSD/ARM support. Also update cmd/ld/doc.go for 5l support of -Hfreebsd. R=rsc CC=golang-dev https://golang.org/cl/6650051
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/31f8b07e55d6f712315eea021ee84bff73f87c51
元コミット内容
runtime/cgo, go/build: cgo support for FreeBSD/ARM
This is the last CL for FreeBSD/ARM support.
Also update cmd/ld/doc.go for 5l support of -Hfreebsd.
R=rsc
CC=golang-dev
https://golang.org/cl/6650051
変更の背景
Go言語は、その設計当初からクロスプラットフォーム対応を重視しており、様々なオペレーティングシステムとアーキテクチャの組み合わせで動作することを目指しています。このコミットが行われた2012年当時、GoはFreeBSD上での動作をサポートしていましたが、特にARMアーキテクチャとの組み合わせ(FreeBSD/ARM)におけるCGOのサポートが不足していました。
CGOは、GoプログラムがC言語で書かれた関数やライブラリを呼び出すための重要な機能です。OSのシステムコールや特定のハードウェア機能にアクセスする場合、あるいは既存のCライブラリを利用する場合にはCGOが不可欠となります。FreeBSD/ARM環境でCGOが適切に機能しない場合、Goプログラムの利用範囲が大幅に制限されてしまいます。
このコミットは、GoがFreeBSD/ARMプラットフォームで完全に機能するための「最後の変更リスト(CL)」と明記されており、このプラットフォームでのGoの成熟度を高めるための重要なステップでした。特に、GoランタイムがC言語のコードと連携する際の、スレッド管理、スタック管理、そしてGoの内部的なg(goroutine)とm(machine/thread)ポインタの扱いに関する低レベルな調整が必要とされました。
前提知識の解説
Go言語のCGO機能
CGOは、GoプログラムからC言語の関数を呼び出したり、C言語の型を使用したりするためのGoの機能です。Goのソースコード内にimport "C"と記述することでC言語のコードを埋め込み、GoとCの間でデータをやり取りできます。CGOは、OSのAPI呼び出し、既存のCライブラリの利用、パフォーマンスが重要な部分でのC言語コードの活用など、様々な場面で利用されます。CGOを使用すると、Goのビルドプロセス中にCコンパイラ(通常はGCC)が呼び出され、CコードがコンパイルされてGoの実行可能ファイルにリンクされます。
FreeBSDオペレーティングシステム
FreeBSDは、UNIX系のオープンソースオペレーティングシステムです。堅牢性、高性能、セキュリティの高さで知られ、サーバー、組み込みシステム、デスクトップなど幅広い用途で利用されています。Linuxと同様に、カーネルとユーザーランドが一体となって開発されており、独自のシステムコールやライブラリのセットを持っています。
ARMアーキテクチャ
ARM(Advanced RISC Machine)は、モバイルデバイス、組み込みシステム、IoTデバイス、最近ではサーバーやデスクトップPCなど、幅広い分野で利用されているRISC(Reduced Instruction Set Computer)ベースのプロセッサアーキテクチャです。低消費電力と高い性能効率が特徴です。Go言語は、ARMを含む複数のCPUアーキテクチャをサポートしています。
Goランタイムにおけるgとm(goroutineとmachine/thread)
Goランタイムは、Goプログラムの実行を管理する重要なコンポーネントです。その中核には、Goの並行処理モデルを支えるg(goroutine)とm(machine/thread)という概念があります。
g(goroutine): Go言語の軽量な並行処理単位です。OSのスレッドよりもはるかに軽量で、数百万のgoroutineを同時に実行できます。各goroutineは独自のスタックを持ちます。m(machine/thread): OSのスレッドを表します。Goランタイムは、複数のgoroutineを少数のOSスレッド(m)に多重化して実行します。mはCPUコア上で実際にコードを実行するエンティティです。
CGO呼び出しが行われる際、Goランタイムは現在のgoroutine(g)をOSスレッド(m)に「固定」し、Cコードが実行されている間は他のgoroutineがそのm上で実行されないようにします。これは、CコードがGoランタイムのスケジューラに認識されないため、Goのスタックやスケジューリングに干渉しないようにするためです。CGO呼び出しから戻ると、gとmはGoランタイムの管理下に戻されます。
TLS (Thread Local Storage)
TLS(Thread Local Storage)は、各スレッドが独自のデータコピーを持つことを可能にするメカニズムです。グローバル変数や静的変数はプロセス内のすべてのスレッドで共有されますが、TLS変数に格納されたデータは、その変数にアクセスする各スレッドに対して一意のコピーが提供されます。これにより、スレッド間でデータを共有する際のスレッドセーフティの問題を回避したり、スレッド固有の状態を管理したりするのに役立ちます。CGOの文脈では、GoランタイムのgとmポインタをCコードから安全にアクセス・保存するためにTLSが利用されることがあります。
AEABI (ARM Embedded Application Binary Interface)
AEABI(ARM Embedded Application Binary Interface)は、ARMアーキテクチャ上で動作するソフトウェアのバイナリ互換性を定義する標準です。これには、関数呼び出し規約、データ型のアライメント、例外処理、スレッドローカルストレージのアクセス方法などが含まれます。AEABIに準拠することで、異なるコンパイラやツールチェーンで生成されたコードが相互に連携できるようになります。__aeabi_read_tpのような関数は、AEABIの一部として定義された、スレッドポインタ(Thread Pointer)レジスタを読み取るための標準的な方法を提供します。
Goリンカ (cmd/ld)
Goのリンカ(cmd/ld)は、コンパイルされたGoのオブジェクトファイルとCGOによって生成されたCのオブジェクトファイルを結合し、最終的な実行可能ファイルを生成するツールです。リンカは、異なるOSやアーキテクチャに対応するために、ターゲットプラットフォーム固有のオプション(例: -Hlinux, -Hfreebsd)をサポートしています。これらのオプションは、生成される実行可能ファイルのヘッダ形式(ELF, Mach-Oなど)や、OS固有のリンキング要件を決定します。
Goビルドシステム (go/build)
Goのビルドシステム(go/buildパッケージ)は、Goのソースコードをコンパイルし、実行可能ファイルを生成するプロセスを管理します。これには、依存関係の解決、プラットフォーム固有のファイルの選択、CGOの有効化/無効化の判断などが含まれます。go/buildパッケージは、GOOS(オペレーティングシステム)とGOARCH(アーキテクチャ)の組み合わせに基づいて、CGOがサポートされているかどうかを判断するためのロジックを含んでいます。
技術的詳細
このコミットの技術的な核心は、FreeBSD/ARM環境におけるCGOの低レベルな挙動、特にGoランタイムのgとmポインタの管理と、C言語スレッドとの連携にあります。
Goランタイムは、g(現在のgoroutine)とm(現在のOSスレッド)のポインタを特定のレジスタ(ARMではR10とR9)に保持しています。しかし、CGOを介してCコードが実行される際、Cコードはこれらのレジスタを自由に利用する可能性があり、Goランタイムの期待する値が破壊される恐れがあります。また、Cコードがシグナルハンドラをトリガーした場合、Goのシグナルハンドラが誤ったg/mポインタを参照し、クラッシュを引き起こす可能性もあります。
この問題を解決するため、このコミットではTLS(Thread Local Storage)を利用してgとmポインタを保存・復元するメカニズムを導入しています。具体的には、gcc_freebsd_arm.cファイルで以下の処理が行われます。
-
TLSへの
g/mの保存と復元:__aeabi_read_tp関数は、ARMのAEABIに準拠した方法でスレッドポインタ(TP)レジスタを読み取ります。FreeBSD/ARMでは、TPレジスタは通常、TLSブロックのベースアドレスを指します。cgo_tls_get_gmとcgo_tls_set_gm関数は、このTPレジスタから得られたアドレスを基に、TLS内の特定のオフセット(8バイトと12バイト)にgとmポインタを読み書きします。これにより、Cコードがレジスタを破壊しても、GoランタイムはTLSから正しいg/mポインタを復元できます。- これらの関数はアセンブリ言語で書かれており、レジスタ操作を直接行います。
-
CGOスレッドの初期化と管理:
xinitcgo関数は、Goランタイムが初期化される際に呼び出され、初期スレッドのgとmポインタをTLSに保存します。また、スタックガード(スタックオーバーフロー検出のための境界)を設定します。libcgo_sys_thread_start関数は、CGOが新しいOSスレッドを作成する際に呼び出されます。pthread_createを使用して新しいスレッドを生成し、そのスレッドの開始関数としてthreadentryを設定します。threadentry関数は、新しく作成されたCスレッドのエントリポイントです。この関数内で、Goランタイムのcrosscall_arm2関数を呼び出し、GoのgoroutineがCスレッド上で実行されるようにします。ここでもスタックガードの調整が行われます。
これらの変更により、GoランタイムはCGOを介したCコードの実行中も、gとmポインタの整合性を保ち、安定した動作を保証できるようになります。
また、ビルドシステムとリンカの変更も重要です。
src/pkg/go/build/build.goの変更は、GoのビルドシステムにFreeBSD/ARM環境でのCGOサポートを明示的に追加します。これにより、go buildコマンドがFreeBSD/ARMターゲットでCGOを有効にしてコンパイルできるようになります。src/cmd/ld/doc.goの変更は、Goリンカのドキュメントを更新し、-Hfreebsdオプションが5l(ARMアーキテクチャ向けのGoリンカ)でも利用可能であることを反映しています。これは、FreeBSD/ARM向けの実行可能ファイルを正しくリンクするために必要な情報です。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は以下の3つのファイルにわたります。
-
src/cmd/ld/doc.go:--- a/src/cmd/ld/doc.go +++ b/src/cmd/ld/doc.go @@ -33,7 +33,7 @@ Options new in this version: Write Apple Mach-O binaries (default when $GOOS is darwin) -Hlinux Write Linux ELF binaries (default when $GOOS is linux) - -Hfreebsd (only in 6l/8l) + -Hfreebsd Write FreeBSD ELF binaries (default when $GOOS is freebsd) -Hnetbsd (only in 6l/8l) Write NetBSD ELF binaries (default when $GOOS is netbsd)-Hfreebsdオプションの記述から「(only in 6l/8l)」という制限が削除されました。これは、ARMアーキテクチャ向けのリンカ(5l)でもこのオプションがサポートされるようになったことを示唆しています。 -
src/pkg/go/build/build.go:--- a/src/pkg/go/build/build.go +++ b/src/pkg/go/build/build.go @@ -216,6 +216,7 @@ var cgoEnabled = map[string]bool{ "darwin/amd64": true, "freebsd/386": true, "freebsd/amd64": true, + "freebsd/arm": true, "linux/386": true, "linux/amd64": true, "linux/arm": true,cgoEnabledマップに"freebsd/arm": trueが追加されました。これにより、GoのビルドシステムがFreeBSD/ARM環境でCGOを有効にすることを認識します。 -
src/pkg/runtime/cgo/gcc_freebsd_arm.c: (新規ファイル) このファイルは、FreeBSD/ARM環境でのCGOランタイムサポートの核心をなすもので、109行のC言語コードが追加されています。主な内容は以下の通りです。- TLS関連のアセンブリ関数:
__aeabi_read_tp: ARMのAEABIに準拠してスレッドポインタを読み取るアセンブリ関数。cgo_tls_get_gm: TLSからgとmポインタを読み取り、レジスタR10とR9に設定するアセンブリ関数。cgo_tls_set_gm: レジスタR10とR9からgとmポインタを読み取り、TLSに保存するアセンブリ関数。
- CGO初期化関数:
xinitcgo: Goランタイムの初期化時に呼び出され、初期スレッドのg/mをTLSに保存し、スタックガードを設定します。
- CGOスレッド開始関数:
libcgo_sys_thread_start: CGOが新しいOSスレッドを開始する際に呼び出され、pthread_createを使用してスレッドを作成します。
- スレッドエントリポイント:
threadentry:pthread_createによって作成された新しいスレッドのエントリポイント。Goランタイムのcrosscall_arm2を呼び出してGoコードの実行を開始します。
- TLS関連のアセンブリ関数:
コアとなるコードの解説
src/cmd/ld/doc.go の変更
この変更は、Goリンカのドキュメントを更新するものです。以前は-Hfreebsdオプションが6l(amd64向けリンカ)と8l(386向けリンカ)でのみサポートされていると記載されていましたが、このコミットによりARMアーキテクチャ向けのリンカ(5l)でもサポートされるようになったため、その制限を示す記述が削除されました。これは、FreeBSD/ARM環境でGoの実行可能ファイルを正しく生成するために必要なリンカの機能が整ったことを意味します。
src/pkg/go/build/build.go の変更
cgoEnabledマップは、特定のGOOS/GOARCHの組み合わせでCGOが有効であるかどうかをGoのビルドシステムに伝える役割を果たします。"freebsd/arm": trueが追加されたことで、GoのツールチェーンはFreeBSD/ARMターゲットでCGOをサポートしていると認識し、CGOを使用するGoプログラムをコンパイルする際にCコンパイラを適切に呼び出すようになります。これにより、開発者はFreeBSD/ARM上でCGOを利用したGoアプリケーションをビルドできるようになります。
src/pkg/runtime/cgo/gcc_freebsd_arm.c の新規追加
このファイルは、FreeBSD/ARM環境におけるCGOの低レベルなランタイムサポートを提供します。
-
TLS変数の利用: Goランタイムは、現在のgoroutine (
g) とOSスレッド (m) のポインタを特定のレジスタに保持しています。しかし、CGOを介してCコードが実行されると、これらのレジスタがCコードによって上書きされる可能性があります。この問題を回避するため、このファイルではTLS(Thread Local Storage)を利用します。TLSは、各スレッドが独自のデータコピーを持つことを可能にするメカニズムです。__aeabi_read_tp: ARMのAEABI(Application Binary Interface)に準拠した方法で、スレッドポインタ(Thread Pointer)レジスタの値を読み取ります。このレジスタは、TLSブロックのベースアドレスを指します。cgo_tls_get_gm:__aeabi_read_tpで取得したTLSベースアドレスを基に、TLS内の特定のオフセット(8バイトと12バイト)からgとmポインタを読み出し、ARMのレジスタR10とR9に設定します。これは、CGO呼び出しからGoコードに戻る際に、Goランタイムが正しいgとmのコンテキストを復元するために使用されます。cgo_tls_set_gm: 逆に、レジスタR10とR9からgとmポインタを読み取り、TLS内の対応するオフセットに保存します。これは、CGO呼び出しに入る前や、Goランタイムが新しいスレッドを初期化する際に、現在のgとmの状態を保存するために使用されます。 これらの関数はアセンブリ言語で書かれており、レジスタとメモリ間の直接的な操作を行います。
-
xinitcgo関数: Goランタイムが初期化される際に呼び出される関数です。cgo_tls_set_gm()を呼び出し、初期スレッドのgとmポインタをTLSに保存します。これは、GoプログラムのメインスレッドがCGOを呼び出す準備をするためです。pthread_attr_getstacksizeを使用して現在のスレッドのスタックサイズを取得し、それに基づいてg->stackguard(スタックオーバーフロー検出のための境界)を設定します。これにより、GoのスタックがCコードの実行中にオーバーフローするのを防ぎます。
-
libcgo_sys_thread_start関数: CGOが新しいOSスレッドを作成する必要がある場合にGoランタイムから呼び出される関数です。pthread_attr_initとpthread_attr_getstacksizeを使用して、新しいスレッドの属性(特にスタックサイズ)を設定します。pthread_createを使用して新しいPOSIXスレッドを作成します。このスレッドの開始関数としてthreadentryを指定し、ThreadStart構造体へのポインタを引数として渡します。- スレッド作成に失敗した場合はエラーメッセージを出力し、プログラムを異常終了させます。
-
threadentry関数:libcgo_sys_thread_startによって作成された新しいCスレッドのエントリポイントです。- 引数として渡された
ThreadStart構造体から、新しいgoroutine (ts.g) とその関連情報を取り出します。 ts.g->stackbaseを現在のスタックポインタの近くに設定します。ts.g->stackguardを再計算し、新しいスレッドのスタックガードを設定します。これは、Goのスタック管理がCスレッドのスタック上で正しく機能するために重要です。crosscall_arm2(ts.fn, (void *)ts.g, (void *)ts.m)を呼び出します。これはGoランタイムの内部関数で、CスレッドからGoの関数(ts.fn)を呼び出し、指定されたgとmのコンテキストでGoコードの実行を開始します。これにより、Cスレッド上でGoのgoroutineが実行されるようになります。
- 引数として渡された
これらの変更により、FreeBSD/ARM環境でCGOが安定して動作するための基盤が確立され、GoプログラムがC言語のライブラリやシステムコールを安全かつ効率的に利用できるようになりました。
関連リンク
- Go言語公式ドキュメント: https://go.dev/
- Go言語のCGOに関するドキュメント: https://go.dev/blog/c-go-is-not-c (CGOの一般的な情報)
- FreeBSDプロジェクト: https://www.freebsd.org/
- ARMアーキテクチャに関する情報: https://developer.arm.com/
参考にした情報源リンク
- Go言語のCGOに関するブログ記事や公式ドキュメント (一般的なCGOの仕組み理解のため)
- FreeBSDのシステムプログラミングに関する資料 (pthreadやシステムコールに関する一般的な知識のため)
- ARMアーキテクチャとAEABIに関する資料 (アセンブリコードとTLSの理解のため)
- Goランタイムの内部構造に関する資料 (gとm、スタック管理の理解のため)
- Goのソースコード(特に
runtimeパッケージとcmd/ld、go/build) - https://golang.org/cl/6650051 (元の変更リストのレビューコメントなど、追加のコンテキストを得るため)
- https://go.dev/src/runtime/cgo/gcc_freebsd_arm.c (現在のGoソースコードと比較するため)
- https://go.dev/src/cmd/ld/doc.go
- https://go.dev/src/go/build/build.go
- https://en.wikipedia.org/wiki/Thread-local_storage (TLSに関する一般的な情報)
- https://en.wikipedia.org/wiki/ARM_Embedded_Application_Binary_Interface (AEABIに関する一般的な情報)