[インデックス 19665] ファイルの概要
コミット
commit 12b990ba7d9969987e79d5d1e5e71e50d2cc2c06
Author: David Crawshaw <david.crawshaw@zentus.com>
Date: Thu Jul 3 16:14:34 2014 -0400
cmd/go, cmd/ld, runtime, os/user: TLS emulation for android
Based on cl/69170045 by Elias Naur.
There are currently several schemes for acquiring a TLS
slot to save the g register. None of them appear to work
for android. The closest are linux and darwin.
Linux uses a linker TLS relocation. This is not supported
by the android linker.
Darwin uses a fixed offset, and calls pthread_key_create
until it gets the slot it wants. As the runtime loads
late in the android process lifecycle, after an
arbitrary number of other libraries, we cannot rely on
any particular slot being available.
So we call pthread_key_create, take the first slot we are
given, and put it in runtime.tlsg, which we turn into a
regular variable in cmd/ld.
Makes android/arm cgo binaries work.
LGTM=minux
R=elias.naur, minux, dave, josharian
CC=golang-codereviews
https://golang.org/cl/106380043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/12b990ba7d9969987e79d5d1e5e71e50d2cc2c06
元コミット内容
cmd/go, cmd/ld, runtime, os/user: TLS emulation for android
Based on cl/69170045 by Elias Naur.
There are currently several schemes for acquiring a TLS
slot to save the g register. None of them appear to work
for android. The closest are linux and darwin.
Linux uses a linker TLS relocation. This is not supported
by the android linker.
Darwin uses a fixed offset, and calls pthread_key_create
until it gets the slot it wants. As the runtime loads
late in the android process lifecycle, after an
arbitrary number of other libraries, we cannot rely on
any particular slot being available.
So we call pthread_key_create, take the first slot we are
given, and put it in runtime.tlsg, which we turn into a
regular variable in cmd/ld.
Makes android/arm cgo binaries work.
LGTM=minux
R=elias.naur, minux, dave, josharian
CC=golang-codereviews
https://golang.org/cl/106380043
変更の背景
このコミットは、GoランタイムがAndroidプラットフォーム、特にARMアーキテクチャ上でCGO(C言語との相互運用)バイナリを適切に動作させるための重要な変更を導入しています。主な課題は、Goランタイムがゴルーチン(goroutine)のコンテキストを管理するために使用するg
レジスタ(現在のゴルーチンのg
構造体へのポインタ)をスレッドローカルストレージ(TLS: Thread Local Storage)に保存・復元する方法が、Androidの環境では既存のスキームでは機能しなかった点にあります。
具体的には、以下の問題がありました。
- LinuxのTLSスキーム: LinuxではリンカによるTLS再配置(relocation)を利用しますが、Androidのリンカはこの機能をサポートしていません。
- Darwin(macOS/iOS)のTLSスキーム: Darwinでは
pthread_key_create
を使用してTLSスロットを確保し、固定オフセットでアクセスします。しかし、AndroidではGoランタイムがプロセスのライフサイクルの比較的遅い段階でロードされるため、他の多数のライブラリが既にロードされている可能性があり、特定のTLSスロットが常に利用可能であるとは限りません。これにより、固定オフセットに依存するアプローチは信頼性が低いと判断されました。
これらの制約により、Android上でCGOを介してGoとCのコードが相互に呼び出しを行う際に、g
レジスタの保存と復元が正しく行えず、CGOバイナリが動作しないという問題が発生していました。このコミットは、この問題を解決し、Android/ARM環境でのCGOバイナリの互換性を確立することを目的としています。
前提知識の解説
スレッドローカルストレージ (TLS: Thread Local Storage)
TLSは、各スレッドがそれぞれ独立したデータを保持するためのメカニズムです。通常、グローバル変数や静的変数はプロセス内のすべてのスレッドで共有されますが、TLSに格納された変数は、各スレッドがその変数自身のコピーを持ち、他のスレッドのコピーに影響を与えることなくアクセスできます。Goランタイムでは、現在のゴルーチンを示すg
レジスタのようなスレッド固有の情報を保存するためにTLSが利用されます。CGO呼び出しのように、GoランタイムがCコードに制御を渡す際、CコードがGoのレジスタを上書きする可能性があるため、Goの重要なレジスタ(特にg
レジスタ)をTLSに保存し、Cコードからの復帰後に復元する必要があります。
g
レジスタ
Goランタイムにおいて、g
レジスタ(ARMアーキテクチャでは通常R10
)は、現在実行中のゴルーチン(runtime.g
構造体)へのポインタを保持する特別なレジスタです。Goのスケジューラは、このg
レジスタを使って現在のゴルーチンのスタック、状態、その他のコンテキスト情報を迅速に参照します。CGOを介してC関数を呼び出す際、CコードはGoランタイムのレジスタ使用規約を知らないため、g
レジスタを含むGoのレジスタを上書きしてしまう可能性があります。そのため、C関数を呼び出す前にg
レジスタの値をTLSに保存し、C関数から戻った後にTLSから元の値を復元することが不可欠です。
CGO (C Foreign Function Interface)
CGOは、GoプログラムからC言語のコードを呼び出したり、CプログラムからGoの関数を呼び出したりするためのGoの機能です。これにより、既存のCライブラリを利用したり、パフォーマンスが重要な部分をCで記述したりすることが可能になります。CGOを使用する際には、GoとCの異なる実行モデル(特にスタック管理やレジスタ使用規約)の間でコンテキストを適切に切り替える必要があります。この切り替えには、前述のg
レジスタの保存と復元が伴います。
pthread_key_create
pthread_key_create
はPOSIXスレッド(pthreads)ライブラリの一部であり、スレッド固有データ(TSD: Thread-Specific Data)のキーを作成するために使用されます。この関数は、新しいTSDキーを生成し、そのキーを介して各スレッドが独自のデータを関連付けられるようにします。キーが作成されると、pthread_setspecific
でスレッド固有の値を設定し、pthread_getspecific
でその値を取得できます。このコミットでは、Android環境でg
レジスタを保存するためのTLSスロットを動的に確保するためにこの関数が利用されています。
Androidのlibc
とpthread
AndroidのC標準ライブラリであるBionic libc
は、標準的なLinux libc
(glibcなど)とは異なる実装を持っています。Bionicはpthread
の一部を実装していますが、完全なlibpthread
を提供しているわけではありません。この違いが、GoランタイムがLinuxやDarwinで採用しているTLSスキームをAndroidに直接適用できない理由の一つとなっています。特に、リンカのTLS再配置のサポートや、pthread_key_create
によって割り当てられるTLSスロットの挙動に違いがあります。
技術的詳細
このコミットの核心は、Android環境におけるg
レジスタのTLS保存メカニズムを、既存のプラットフォーム固有の制約を回避するように再設計した点にあります。
-
runtime.tlsg
の扱い変更:- 従来のLinuxやDarwinでは、
runtime.tlsg
はリンカによって特別に扱われるTLSシンボルでした。しかし、Androidのリンカがこれをサポートしないため、このコミットではcmd/ld
(Goリンカ)がAndroid向けにruntime.tlsg
を通常の変数として扱うように変更されました。 - 具体的には、
src/cmd/ld/data.c
では、Androidの場合にR_TLS
(TLS再配置)をR_ADDR
(通常のアドレス再配置)に変換するロジックが追加されました。 src/cmd/ld/elf.c
では、ELFセクションのフラグ設定において、Androidの場合にはSHF_TLS
フラグ(TLSセクションを示す)を設定しないように変更されました。src/cmd/ld/symtab.c
では、シンボルテーブルのエントリ作成時に、Androidの場合にSTT_TLS
(TLSシンボル)ではなくSTT_OBJECT
(通常のデータオブジェクト)としてruntime.tlsg
を扱うように変更されました。
- 従来のLinuxやDarwinでは、
-
動的なTLSスロットの取得 (
inittls
関数):- Androidでは固定オフセットに依存できないため、
pthread_key_create
を使用して動的にTLSスロットを確保する新しいアプローチが導入されました。 src/pkg/runtime/cgo/gcc_android_arm.c
に新しく追加されたinittls
関数がこの役割を担います。inittls
はpthread_key_create
を呼び出して新しいキーを取得します。- 次に、取得したキーに一時的に
magic1
という特定の値を設定します(pthread_setspecific(k, (void*)magic1);
)。 - その後、
PTHREAD_KEYS_MAX
(通常128)までの範囲で、スレッドローカルストレージのベースポインタからオフセットを走査し、magic1
が設定されているスロットを見つけ出します。 - 見つかったスロットのインデックスに
sizeof(void *)
を乗算することで、runtime.tlsg
が使用すべきTLSオフセットを計算します。 - このオフセットは、
runtime.tlsg
が通常の変数として扱われるため、そのアドレスに加算されてg
レジスタの値を保存・復元するために使用されます。 - 最後に、一時的に設定した
magic1
をクリアします(pthread_setspecific(k, 0);
)。
- Androidでは固定オフセットに依存できないため、
-
CGO初期化フローの変更:
src/pkg/runtime/cgo/gcc_linux_arm.c
(Androidも含む)のx_cgo_init
関数が変更され、inittls
関数を呼び出すようになりました。これにより、Goランタイムの初期化時にAndroid固有のTLS設定が行われます。src/pkg/runtime/asm_arm.s
では、_rt0_go
(Goプログラムのエントリポイント)におけるCGO初期化の呼び出しが変更され、TLSベースポインタやruntime.tlsg
のアドレスを引数として渡すようになりました。src/pkg/runtime/tls_arm.s
が新しく追加され、Android向けに調整されたruntime·save_g
とruntime·load_g
関数が定義されました。これらの関数は、MRC 15, 0, R0, C13, C0, 3
命令(ARMのCP15レジスタからTLSベースポインタを取得)を使用してTLSベースポインタを取得し、その後にruntime.tlsg
のアドレス(Androidでは通常の変数として扱われる)を加算して、g
レジスタの値を保存・復元します。
-
ビルドプロセスの調整:
src/cmd/go/build.go
では、Android/ARM向けにビルドする際に-pie
(Position Independent Executable)フラグをリンカに渡すように変更されました。これは、Androidの実行環境で正確なシンボル解決を行うために必要です。src/cmd/ld/lib.c
では、Androidの場合に強制的に外部リンキング(LinkExternal)を使用するように設定されました。src/pkg/os/user/lookup_stubs.go
とsrc/pkg/os/user/lookup_unix.go
では、ビルドタグが調整され、Androidプラットフォームでのユーザー情報ルックアップの挙動が適切に制御されるようになりました。
これらの変更により、GoランタイムはAndroidのTLSの特殊性を吸収し、CGOを介したGoとCの相互運用が安定して行えるようになりました。
コアとなるコードの変更箇所
このコミットでは、Goのビルドツール、リンカ、ランタイム、および標準ライブラリの複数のファイルにわたる広範な変更が含まれています。
-
src/cmd/go/build.go
:- Android/ARMビルド時に
-pie
(Position Independent Executable)リンカフラグを適用する条件にgoos == "android"
が追加されました。
- Android/ARMビルド時に
-
src/cmd/ld/data.c
:relocsym
関数内で、Androidの場合にR_TLS
(TLS再配置)をR_ADDR
(通常のアドレス再配置)に変換するロジックが追加されました。これにより、リンカはruntime.tlsg
を通常の変数として扱います。
-
src/cmd/ld/elf.c
:elfshbits
関数内で、.tbss
セクション(TLS BSSセクション)のELFフラグを設定する際に、Androidの場合にはSHF_TLS
フラグ(TLSセクションを示す)を設定しないように変更されました。
-
src/cmd/ld/lib.c
:loadlib
関数内で、Androidの場合に強制的に外部リンキング(LinkExternal
)を使用するように設定されました。
-
src/cmd/ld/pobj.c
:- 軽微なコードフォーマットの変更。
-
src/cmd/ld/symtab.c
:asmelfsym
関数内で、Androidの場合にruntime.tlsg
シンボルをSTT_TLS
(TLSシンボル)ではなくSTT_OBJECT
(通常のデータオブジェクト)としてシンボルテーブルに登録するように変更されました。
-
src/pkg/os/user/lookup_stubs.go
:- ビルドタグに
android
が追加されました。
- ビルドタグに
-
src/pkg/os/user/lookup_unix.go
:- ビルドタグから
android
が除外され、!android,linux
という条件が追加されました。
- ビルドタグから
-
src/pkg/runtime/asm_arm.s
:runtime·save_g
とruntime·load_g
の古い実装が削除されました。_rt0_go
内のCGO初期化呼び出しが変更され、TLSベースポインタやruntime.tlsg
のアドレスを引数として渡すようになりました。
-
src/pkg/runtime/cgo/cgo.go
:- Android向けに
-llog
リンカフラグが追加され、Linux向けの-lpthread
フラグが!android,linux
という条件付きになりました。
- Android向けに
-
src/pkg/runtime/cgo/gcc_android_arm.c
:- 新規ファイル。
inittls
関数が定義され、pthread_key_create
を使用して動的にTLSスロットのオフセットを決定するロジックが含まれています。
- 新規ファイル。
-
src/pkg/runtime/cgo/gcc_linux_arm.c
:x_cgo_init
関数のシグネチャが変更され、tlsg
とtlsbase
のポインタを受け取るようになりました。x_cgo_inittls
が定義されている場合にそれを呼び出すロジックが追加されました。
-
src/pkg/runtime/tls_arm.s
:- 新規ファイル。Android向けに調整された
runtime·save_g
とruntime·load_g
関数が定義されました。これらの関数は、GOOS_android
マクロに基づいてruntime.tlsg
の参照方法を切り替えます。
- 新規ファイル。Android向けに調整された
コアとなるコードの解説
このコミットの最も重要な変更は、Android環境でのg
レジスタのTLS管理方法を根本的に変更した点です。
src/pkg/runtime/cgo/gcc_android_arm.c
の inittls
関数
この新規ファイルに導入されたinittls
関数は、AndroidにおけるTLSエミュレーションの心臓部です。
static void
inittls(void **tlsg, void **tlsbase)
{
pthread_key_t k;
int i, err;
err = pthread_key_create(&k, nil);
if(err != 0) {
fprintf(stderr, "runtime/cgo: pthread_key_create failed: %d\\n", err);
__android_log_print(ANDROID_LOG_FATAL, "runtime/cgo", "pthread_key_create failed: %d", err);
abort();
}
pthread_setspecific(k, (void*)magic1); // 一時的にマジック値を設定
for (i=0; i<PTHREAD_KEYS_MAX; i++) {
if (*(tlsbase+i) == (void*)magic1) { // マジック値を見つける
*tlsg = (void*)(i*sizeof(void *)); // オフセットを計算
pthread_setspecific(k, 0); // マジック値をクリア
return;
}
}
fprintf(stderr, "runtime/cgo: could not find pthread key\\n");
__android_log_print(ANDROID_LOG_FATAL, "runtime/cgo", "could not find pthread key");
abort();
}
この関数は、pthread_key_create
を呼び出して新しいスレッド固有データキーを作成します。その後、そのキーに一時的な「マジック値」(magic1
)を設定します。そして、tlsbase
(スレッドローカルストレージのベースアドレス)からPTHREAD_KEYS_MAX
の範囲でメモリを走査し、このマジック値が格納されている場所を探します。マジック値が見つかった場合、その位置からruntime.tlsg
が使用すべきTLSオフセットを計算します。この動的なオフセット計算により、Android環境で固定オフセットに依存することなく、g
レジスタを保存するためのTLSスロットを確実に特定できるようになります。
リンカの変更 (cmd/ld
)
src/cmd/ld/data.c
、src/cmd/ld/elf.c
、src/cmd/ld/symtab.c
における変更は、GoリンカがAndroid向けにruntime.tlsg
を通常の変数として扱うように強制するものです。これにより、AndroidのリンカがTLS再配置をサポートしないという制約を回避し、Goランタイムが独自のTLSエミュレーションスキームを実装できるようになります。
アセンブリコードの変更 (src/pkg/runtime/asm_arm.s
, src/pkg/runtime/tls_arm.s
)
src/pkg/runtime/tls_arm.s
に新しく追加されたruntime·save_g
とruntime·load_g
関数は、Android環境でのg
レジスタの保存と復元を担います。これらの関数は、ARMのCP15レジスタ(MRC 15, 0, R0, C13, C0, 3
)からTLSベースポインタを取得し、その後にruntime.tlsg
のアドレス(Androidでは通常の変数として扱われる)を加算して、g
レジスタの値をTLSに書き込んだり、TLSから読み込んだりします。これにより、CGO呼び出しの前後でg
レジスタのコンテキストが正しく維持されます。
これらの変更の組み合わせにより、GoランタイムはAndroidの特殊なTLS環境に適応し、CGOを介したGoとCの相互運用が安定して行えるようになり、Android/ARMプラットフォームでのGoバイナリの機能性が向上しました。
関連リンク
- Go GitHub Commit: cmd/go, cmd/ld, runtime, os/user: TLS emulation for android
- Go Code Review 106380043
- Go Code Review 69170045 (Based on)
参考にした情報源リンク
- Go's runtime on Android utilizes Thread Local Storage (TLS) to manage the
g
register - In the context of CGO (C foreign function interface),
pthread_key_create
plays a crucial role in optimizing performance when Go functions are invoked from C threads. - The
inittls
function within the Go runtime's CGO source code (e.g.,src/runtime/cgo/gcc_android_386.c
) usespthread_key_create
to determine the correct offset for theg
register in TLS - Additionally,
src/runtime/cgo/callbacks.go
referencespthread_key_create
for creating a dummy thread key and registering a destructor when Go-exported functions are called from C. - It's important to note that Android's
libc
includes a partialpthread
implementation, even without a dedicatedlibpthread
. - However, there have been historical and ongoing challenges related to Go's TLS implementation on Android, including conflicts with Android's Bionic libc regarding
pthread
key placement - CL 106380043 is a Go code review titled "cmd/go, cmd/ld, runtime, os/user: TLS emulation for android".