[インデックス 15212] ファイルの概要
このコミットは、Go言語のビルドシステムとランタイムにおいて、NetBSD/ARMアーキテクチャでのCgoサポートを追加するものです。これにより、GoプログラムがC言語のコードを呼び出す機能が、NetBSDオペレーティングシステムのARMプロセッサ上で利用可能になります。
コミット
commit 2fdd60b9b66f26720ea571345785285d4037dce5
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Wed Feb 13 01:06:52 2013 +0800
go/build, runtime/cgo: cgo support for NetBSD/ARM
R=golang-dev, rsc, dave
CC=golang-dev
https://golang.org/cl/7229082
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2fdd60b9b66f26720ea571345785285d4037dce5
元コミット内容
go/build, runtime/cgo: cgo support for NetBSD/ARM
R=golang-dev, rsc, dave
CC=golang-dev
https://golang.org/cl/7229082
変更の背景
Go言語は、その設計思想としてクロスプラットフォーム対応を重視しています。Cgoは、GoプログラムからC言語の関数を呼び出すためのメカニズムを提供し、既存のCライブラリとの連携や、特定のプラットフォームの低レベル機能へのアクセスを可能にします。
このコミットが作成された背景には、Go言語がNetBSDオペレーティングシステムのARMアーキテクチャ上でCgoをサポートする必要性がありました。NetBSDは、移植性の高さで知られるUNIX系OSであり、ARMはモバイルデバイスや組み込みシステムで広く利用されるプロセッサアーキテクチャです。これらの組み合わせでCgoを機能させるためには、GoのビルドシステムがNetBSD/ARMをCgoが有効なプラットフォームとして認識し、かつGoランタイムがCgo呼び出しを適切に処理するためのプラットフォーム固有のコード(特にスレッド管理やレジスタの保存・復元)を提供する必要がありました。
特に、Goランタイムの重要な要素であるg
(goroutine) と m
(machine/OSスレッド) ポインタの管理は、Cgo呼び出し時に複雑になります。Cコードが実行されている間、Goランタイムはこれらのポインタを安全に保存し、CコードからGoコードに戻る際に正確に復元する必要があります。外部のCコードが予期せぬシグナル(例: SIGSEGV
)を発生させた場合でも、Goランタイムがクラッシュしないように、これらのポインタを堅牢に管理するメカニズムが求められました。このコミットは、NetBSD/ARM環境におけるこれらの課題を解決することを目的としています。
前提知識の解説
- Cgo: Go言語の機能の一つで、GoプログラムからC言語の関数を呼び出したり、C言語のコードからGoの関数を呼び出したりすることを可能にします。これにより、既存のCライブラリをGoプロジェクトで再利用したり、OSの低レベルAPIにアクセスしたりできます。
- NetBSD: オープンソースのUNIX系オペレーティングシステムの一つで、非常に高い移植性を持つことで知られています。多くの異なるハードウェアアーキテクチャで動作します。
- ARMアーキテクチャ: Advanced RISC Machineの略で、主にモバイルデバイス、組み込みシステム、IoTデバイスなどで広く使用されているRISC(Reduced Instruction Set Computer)ベースのプロセッサアーキテクチャです。低消費電力と高性能を両立しています。
- TLS (Thread Local Storage): スレッドローカルストレージは、各スレッドが独自のデータコピーを持つことができるメモリ領域です。グローバル変数とは異なり、TLSに格納されたデータはスレッド間で共有されず、各スレッドが独立してアクセスできます。Cgoのコンテキストでは、Goランタイムの
g
とm
ポインタをスレッドごとに安全に保存・復元するために利用されます。 pthread
(POSIX Threads): POSIXスレッドは、UNIX系オペレーティングシステムにおけるスレッドAPIの標準です。マルチスレッドプログラミングにおいて、スレッドの生成、同期、管理などを行うための関数を提供します。Cgoは、GoランタイムがOSスレッドを管理する際にpthread
を使用することがあります。SIGSEGV
(Segmentation Fault): セグメンテーション違反は、プログラムがアクセスを許可されていないメモリ領域にアクセスしようとしたときに発生するシグナルです。CgoでCコードを実行中にこのようなエラーが発生した場合、Goランタイムが適切に処理しないと、プログラム全体がクラッシュする可能性があります。- Goランタイムの
g
とm
:g
(goroutine): Go言語の軽量スレッドであるゴルーチンを表す構造体へのポインタです。各ゴルーチンは独自のスタックを持ち、Goランタイムによってスケジューリングされます。m
(machine/OS thread): オペレーティングシステムのスレッドを表す構造体へのポインタです。Goランタイムは、m
を介してOSスレッドを管理し、その上でゴルーチンを実行します。 Cgo呼び出しでは、GoランタイムがCコードに制御を渡す際に、現在のg
とm
のコンテキストを保存し、Cコードの実行が完了した後にそれらを復元する必要があります。
- AEABI (ARM Embedded Application Binary Interface): ARMプロセッサ上で動作するソフトウェアのためのバイナリインターフェース標準です。関数呼び出し規約、データ型のアライメント、レジスタの使用方法などを定義しており、異なるコンパイラやツールチェーンで生成されたコード間の互換性を保証します。
__aeabi_read_tp
は、AEABIの一部として定義される可能性のある、スレッドポインタ(Thread Pointer)を読み取るための関数です。
技術的詳細
このコミットの主要な技術的課題は、NetBSD/ARM環境でCgoがGoランタイムのg
とm
ポインタを安全に管理することです。特に、外部のCコードがSIGSEGV
のようなシグナルをトリガーした場合でも、Goランタイムがこれらのポインタを正しく認識し、クラッシュを防ぐ必要があります。
この解決策として、TLS (Thread Local Storage) を利用してg
とm
ポインタを保存・復元するアプローチが採用されています。ARMアーキテクチャでは、通常、スレッドポインタを保持するための専用レジスタ(例えば、TPIDRURW
レジスタ)や、mrc p15
命令のようなシステム制御コプロセッサ命令を使用してTLS領域にアクセスします。
コミットで追加されたsrc/pkg/runtime/cgo/gcc_netbsd_arm.c
ファイルには、以下の重要なアセンブリ関数が含まれています。
__aeabi_read_tp
: この関数は、ARMのシステム制御コプロセッサ(CP15)のレジスタc13, c0, 3
(通常はTLSベースアドレスを保持する)を読み取り、スレッドポインタ(TP)を取得します。この関数はr0
レジスタのみを破壊することが許されており、AEABIの規約に従っています。_lwp_getprivate
システムコール(NetBSDの軽量プロセスプライベートデータ取得)も使用されており、これはTLS領域へのアクセス方法の一つです。cgo_tls_get_gm
: この関数は、__aeabi_read_tp
を呼び出してスレッドポインタを取得した後、そのポインタからオフセット8
と12
に格納されているg
(R10) とm
(R9) ポインタを読み込み、それぞれのレジスタにロードします。cgo_tls_set_gm
:cgo_tls_get_gm
と同様にスレッドポインタを取得した後、現在のR10
とR9
レジスタに格納されているg
とm
ポインタを、スレッドポインタからのオフセット8
と12
に書き込みます。
これらのアセンブリ関数は、GoランタイムがCgo呼び出しを行う前後に、現在のゴルーチンとOSスレッドのコンテキスト(g
とm
ポインタ)をTLSに保存し、Cコードから戻った後に復元するために使用されます。これにより、Cコードの実行中にGoランタイムのコンテキストが破壊されることを防ぎ、堅牢性を高めます。
また、xinitcgo
関数は、初期スレッドのg
とm
ポインタをTLSに保存するためにcgo_tls_set_gm()
を呼び出します。libcgo_sys_thread_start
関数は、新しいOSスレッドをpthread_create
で作成し、そのスレッドでGoのコードを実行するためのエントリポイントとしてthreadentry
を設定します。threadentry
内では、crosscall_arm2
を呼び出してGoの関数を実行します。スタックガードの調整も行われ、スタックオーバーフローからの保護を強化しています。
コアとなるコードの変更箇所
このコミットでは、主に以下の2つのファイルが変更されています。
src/pkg/go/build/build.go
cgoEnabled
マップに"netbsd/arm": true,
のエントリが追加されました。これにより、GoのビルドシステムがNetBSD/ARMプラットフォームでCgoを有効にするようになります。
src/pkg/runtime/cgo/gcc_netbsd_arm.c
- このファイルは新規作成されました。NetBSD/ARM環境でのCgoサポートに必要なプラットフォーム固有のCコードとアセンブリコードが含まれています。
- TLS (Thread Local Storage) を利用してGoランタイムの
g
(goroutine) とm
(machine/OSスレッド) ポインタを保存・復元するためのアセンブリ関数 (__aeabi_read_tp
,cgo_tls_get_gm
,cgo_tls_set_gm
)。 - GoランタイムがCgo呼び出しを行う際に使用する
cgo_load_gm
とcgo_save_gm
関数ポインタの初期化。 - 初期スレッドのCgo初期化を行う
xinitcgo
関数。 - 新しいOSスレッドを生成し、Goランタイムのコンテキストで実行するための
libcgo_sys_thread_start
関数とthreadentry
関数。
- TLS (Thread Local Storage) を利用してGoランタイムの
- このファイルは新規作成されました。NetBSD/ARM環境でのCgoサポートに必要なプラットフォーム固有のCコードとアセンブリコードが含まれています。
コアとなるコードの解説
src/pkg/go/build/build.go
の変更
var cgoEnabled = map[string]bool{
// ... 既存のエントリ ...
"linux/arm": true,
"netbsd/386": true,
"netbsd/amd64": true,
"netbsd/arm": true, // 追加された行
"openbsd/386": true,
"openbsd/amd64": true,
"windows/386": true,
// ...
}
この変更は、Goのビルドシステムに対して、netbsd/arm
というターゲットプラットフォームがCgoをサポートしていることを明示的に伝えます。これにより、GoコンパイラはNetBSD/ARM向けにCgoを有効にしたバイナリを生成できるようになります。
src/pkg/runtime/cgo/gcc_netbsd_arm.c
の新規追加
このファイルは、NetBSD/ARM環境におけるCgoの低レベルな実装を提供します。
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include <sys/types.h>
#include <pthread.h>
#include <signal.h>
#include <string.h>
#include "libcgo.h"
static void *threadentry(void*);
// We have to resort to TLS variable to save g(R10) and
// m(R9). One reason is that external code might trigger
// SIGSEGV, and our runtime.sigtramp don't even know we
// are in external code, and will continue to use R10/R9,
// this might as well result in another SIGSEGV.
// Note: all three functions will clobber R0, and the last
// two can be called from 5c ABI code.
void __aeabi_read_tp(void) __attribute__((naked));
void cgo_tls_set_gm(void) __attribute__((naked));
void cgo_tls_get_gm(void) __attribute__((naked));
void __aeabi_read_tp(void) {
// this function is only allowed to clobber r0
__asm__ __volatile__ (
"mrc p15, 0, r0, c13, c0, 3\n\t" // Read TPIDRURW (Thread ID Register, User Read/Write)
"cmp r0, #0\n\t"
"movne pc, lr\n\t"
"push {r1,r2,r3,r12}\n\t"
"svc 0x00a0013c\n\t" // _lwp_getprivate (NetBSD specific system call for thread private data)
"pop {r1,r2,r3,r12}\n\t"
"mov pc, lr\n\t"
);
}
// g (R10) at 8(TP), m (R9) at 12(TP)
void cgo_tls_get_gm(void) {
__asm__ __volatile__ (
"push {lr}\n\t"
"bl __aeabi_read_tp\n\t"
"ldr r10, [r0, #8]\n\t" // Load g from TP + 8 into R10
"ldr r9, [r0, #12]\n\t" // Load m from TP + 12 into R9
"pop {pc}\n\t"
);
}
void cgo_tls_set_gm(void) {
__asm__ __volatile__ (
"push {lr}\n\t"
"bl __aeabi_read_tp\n\t"
"str r10, [r0, #8]\n\t" // Store g from R10 to TP + 8
"str r9, [r0, #12]\n\t" // Store m from R9 to TP + 12
"pop {pc}\n\t"
);
}
// both cgo_tls_{get,set}_gm can be called from runtime
void (*cgo_load_gm)(void) = cgo_tls_get_gm;
void (*cgo_save_gm)(void) = cgo_tls_set_gm;
static void
xinitcgo(G *g)
{
pthread_attr_t attr;
size_t size;
cgo_tls_set_gm(); // save g and m for the initial thread
pthread_attr_init(&attr);
pthread_attr_getstacksize(&attr, &size);
g->stackguard = (uintptr)&attr - size + 4096;
pthread_attr_destroy(&attr);
}
void (*initcgo)(G*) = xinitcgo;
void
libcgo_sys_thread_start(ThreadStart *ts)
{
pthread_attr_t attr;
sigset_t ign, oset;
pthread_t p;
size_t size;
int err;
sigfillset(&ign);
sigprocmask(SIG_SETMASK, &ign, &oset); // Block all signals temporarily
pthread_attr_init(&attr);
pthread_attr_getstacksize(&attr, &size);
ts->g->stackguard = size; // Set stackguard for the new goroutine
err = pthread_create(&p, &attr, threadentry, ts); // Create new OS thread
sigprocmask(SIG_SETMASK, &oset, nil); // Restore signal mask
if (err != 0) {
fprintf(stderr, "runtime/cgo: pthread_create failed: %s\n", strerror(err));
abort();
}
}
extern void crosscall_arm2(void (*fn)(void), void *g, void *m);
static void*
threadentry(void *v)
{
ThreadStart ts;
ts = *(ThreadStart*)v;
free(v);
ts.g->stackbase = (uintptr)&ts;
/*
* libcgo_sys_thread_start set stackguard to stack size;
* change to actual guard pointer.
*/
ts.g->stackguard = (uintptr)&ts - ts.g->stackguard + 4096 * 2; // Adjust stackguard
crosscall_arm2(ts.fn, (void *)ts.g, (void *)ts.m); // Call Go function
return nil;
}
- TLSへの
g
とm
の保存・復元:__aeabi_read_tp
は、ARMのシステムレジスタ(CP15のC13レジスタ)からスレッドポインタ(Thread Pointer)を読み取ります。これは、各スレッドが自身のプライベートデータにアクセスするための基点となるアドレスです。NetBSD固有の_lwp_getprivate
システムコールも使用され、TLS領域へのアクセスを補助します。cgo_tls_get_gm
とcgo_tls_set_gm
は、このスレッドポインタを基点として、オフセット8
と12
にGoランタイムのg
とm
ポインタをそれぞれロード(取得)またはストア(保存)します。Goランタイムは、Cgo呼び出しを行う際にこれらの関数を呼び出し、Goのコンテキストを安全に退避・復元します。これにより、Cコードの実行中にGoランタイムの重要なポインタが破壊されることを防ぎます。
- スレッドの初期化と管理:
xinitcgo
は、Goプログラムの初期スレッドが起動する際に呼び出され、そのスレッドのg
とm
ポインタをTLSに保存します。また、スタックガードの設定も行い、スタックオーバーフローを検出できるようにします。libcgo_sys_thread_start
は、Goランタイムが新しいOSスレッドを必要とする際に呼び出されます。pthread_create
を使用して新しいスレッドを作成し、そのスレッドの開始ルーチンとしてthreadentry
を設定します。スレッド作成中にシグナルがブロックされることで、競合状態を防ぎます。threadentry
は、新しく作成されたOSスレッドのエントリポイントです。GoランタイムのThreadStart
構造体から必要な情報を取得し、スタックベースとスタックガードを適切に設定します。最終的に、crosscall_arm2
を呼び出して、Goランタイムが指定したGo関数を新しいOSスレッド上で実行できるようにします。
これらの変更により、NetBSD/ARM環境でCgoが安定して動作するための基盤が確立され、GoプログラムがC言語のライブラリやシステムコールをより堅牢に利用できるようになりました。
関連リンク
参考にした情報源リンク
- Go Programming Language
- NetBSD Project
- ARM Architecture
- POSIX Threads (pthread)
- Thread-local storage
- ARM Embedded Application Binary Interface (AEABI)
- Go runtime source code (一般的なGoランタイムの理解のため)
- Cgo documentation (一般的なCgoの理解のため)
- NetBSD man pages (特に
_lwp_getprivate
システムコールについて) - ARM Architecture Reference Manual (ARMアセンブリ命令とCP15レジスタについて)