[インデックス 10727] ファイルの概要
このコミットは、Go言語のランタイムがNetBSDオペレーティングシステムをサポートするために必要な変更を導入するものです。具体的には、NetBSD上でのGoプログラムの実行を可能にするための、システムコールインターフェース、シグナルハンドリング、メモリ管理、スレッド作成などの低レベルなOS固有の機能が追加されています。
コミット
commit 26089cfe257dd84d293f63550b3b89351106c478
Author: Christopher Nielsen <m4dh4tt3r@gmail.com>
Date: Mon Dec 12 18:10:11 2011 -0500
runtime: Changes to the runtime to support NetBSD.
R=rsc
CC=golang-dev
https://golang.org/cl/5477052
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/26089cfe257dd84d293f63550b3b89351106c478
元コミット内容
このコミットの元の内容は、GoランタイムがNetBSDをサポートするための変更です。具体的には、src/pkg/runtime/netbsd/
以下に、386およびamd64アーキテクチャ向けのNetBSD固有のランタイムファイルが多数追加されています。これには、システムコールラッパー、シグナルハンドリング、メモリ管理、スレッド関連のコードが含まれます。
変更の背景
Go言語は、クロスプラットフォーム対応を重視しており、様々なオペレーティングシステム上で動作するように設計されています。このコミットが作成された2011年12月時点では、GoランタイムはまだNetBSDを完全にサポートしていませんでした。この変更の背景には、GoプログラムがNetBSD環境でネイティブに動作できるようにするための、基本的なOSインターフェースの実装が必要であったという点があります。これにより、NetBSDユーザーもGo言語を利用できるようになり、Goのエコシステムがさらに拡大することが期待されました。
前提知識の解説
このコミットを理解するためには、以下の前提知識が役立ちます。
- Goランタイム (Go Runtime): Go言語のプログラムは、Goランタイムと呼ばれる軽量な実行環境上で動作します。ランタイムは、ガベージコレクション、スケジューリング、メモリ管理、システムコールインターフェース、シグナルハンドリングなど、プログラムの実行に必要な低レベルな機能を提供します。これらの機能はOSに依存するため、各OS向けに最適化された実装が必要です。
- システムコール (System Call): オペレーティングシステムが提供するサービスをプログラムが利用するためのインターフェースです。ファイルI/O、メモリ割り当て、プロセス管理、ネットワーク通信など、OSのカーネルが提供する機能にアクセスするために使用されます。Goランタイムは、OS固有のシステムコールを直接呼び出すためのラッパーを提供します。
- シグナル (Signal): オペレーティングシステムがプロセスに送信する非同期の通知です。プログラムの異常終了(セグメンテーション違反、浮動小数点例外など)、外部からの割り込み(Ctrl+C)、タイマーイベントなど、様々なイベントをプロセスに伝えるために使用されます。Goランタイムは、これらのシグナルを捕捉し、適切に処理するためのメカニズムを持っています。
- メモリ管理 (Memory Management): プログラムがメモリを効率的に利用するための仕組みです。Goランタイムは独自のガベージコレクタを持ちますが、OSからメモリを確保したり解放したりする際には、
mmap
やmunmap
といったOSのメモリ管理機能を利用します。 - スレッドとプロセス (Threads and Processes):
- プロセス: 実行中のプログラムのインスタンスであり、独立したメモリ空間とリソースを持ちます。
- スレッド: プロセス内で実行される実行単位であり、プロセス内のメモリ空間を共有します。Go言語のゴルーチンは、OSスレッド上で多重化されて実行される軽量なスレッドです。Goランタイムは、ゴルーチンをOSスレッドにマッピングし、スケジューリングする役割を担います。
- NetBSD: オープンソースのUnix系オペレーティングシステムの一つで、移植性の高さが特徴です。様々なハードウェアアーキテクチャで動作します。
- アセンブリ言語 (.sファイル): CPUが直接実行できる機械語に近い低レベルなプログラミング言語です。OSの起動処理やシステムコール呼び出しなど、OSに密接に関わる部分ではアセンブリ言語が使用されることがあります。Goランタイムも、OSとのインターフェース部分でアセンブリ言語を使用しています。
rfork_thread
(NetBSD固有): NetBSDにおけるfork
の拡張版で、プロセスやスレッドの作成をより細かく制御できるシステムコールです。特にRFTHREAD
フラグを使用することで、新しいスレッドを作成することができます。Goランタイムは、新しいOSスレッドを作成するためにこれを利用します。sysctl
(NetBSD固有): カーネルの様々なパラメータを読み書きするためのシステムコールです。このコミットでは、CPUの数を取得するためにhw.ncpu
というパラメータを読み取るために使用されています。mmap
/munmap
: メモリマップドファイルや匿名メモリ領域を操作するためのシステムコールです。mmap
はメモリ領域をプロセスのアドレス空間にマッピングし、munmap
はマッピングを解除します。Goランタイムは、ヒープ領域の確保などにこれらを使用します。sigaction
: シグナルハンドラを設定するためのシステムコールです。シグナルが発生した際にどの関数を呼び出すか、どのようなオプションで処理するかなどを指定します。
技術的詳細
このコミットは、GoランタイムがNetBSD上で動作するために必要な、以下の主要な技術的側面をカバーしています。
-
OS固有の定数とデータ構造の定義 (
defs.h
,defs.go
):src/pkg/runtime/netbsd/defs.go
は、Cgoを使用してNetBSDのシステムヘッダから必要な定数(PROT_NONE
,MAP_ANON
,SA_SIGINFO
など)やデータ構造(Sigaltstack
,Siginfo
,Sigcontext
,Timespec
,Timeval
,Itimerval
など)をGoの型として定義しています。- これらのGoの定義は、
godefs
ツールによってsrc/pkg/runtime/netbsd/386/defs.h
およびsrc/pkg/runtime/netbsd/amd64/defs.h
というCヘッダファイルに変換されます。これらのヘッダファイルは、C言語で書かれたランタイムコードやアセンブリコードからNetBSD固有の構造体や定数にアクセスするために使用されます。
-
ランタイムのエントリポイント (
rt0.s
):src/pkg/runtime/netbsd/386/rt0.s
とsrc/pkg/runtime/netbsd/amd64/rt0.s
は、GoプログラムがNetBSD上で起動する際のエントリポイントとなるアセンブリコードです。これらは、Goランタイムの初期化ルーチンである_rt0_386
または_rt0_amd64
にジャンプする役割を担います。OSがプログラムをロードした後、最初に実行されるコードであり、Goランタイムの起動処理を開始します。
-
システムコールインターフェース (
sys.s
):src/pkg/runtime/netbsd/386/sys.s
とsrc/pkg/runtime/netbsd/amd64/sys.s
は、GoランタイムがNetBSDのシステムコールを呼び出すためのアセンブリラッパーを提供します。- 例えば、
runtime·exit
(プログラム終了),runtime·write
(標準出力への書き込み),runtime·mmap
(メモリマップ),runtime·munmap
(メモリマップ解除),runtime·sigaction
(シグナルハンドラ設定),runtime·rfork_thread
(スレッド作成),runtime·sysctl
(カーネルパラメータ取得) など、GoランタイムがOSと対話するために必要な基本的なシステムコールが実装されています。 - これらのラッパーは、適切なシステムコール番号をレジスタに設定し、
INT $0x80
(386) またはSYSCALL
(amd64) 命令を使用してカーネルに制御を渡します。
-
シグナルハンドリング (
signal.c
,signals.h
):src/pkg/runtime/netbsd/signal.c
は、NetBSD上でのシグナル処理の核心部分です。runtime·sighandler
は、OSからシグナルが配送された際に呼び出される主要なハンドラです。この関数は、シグナルの種類に応じて、Goのパニック処理 (runtime·sigpanic
) をトリガーしたり、プロファイリング情報を収集したり、特定のシグナルを無視したりします。runtime·initsig
は、Goランタイムが起動する際に、Goが処理すべきシグナル(例えば、SIGSEGV
、SIGBUS
、SIGFPE
などのパニックを引き起こすシグナルや、SIGPROF
などのプロファイリング用シグナル)に対してruntime·sighandler
を登録します。src/pkg/runtime/netbsd/signals.h
は、各シグナルの名前と、Goランタイムがそのシグナルをどのように扱うべきかを示すフラグ(SigCatch
,SigIgnore
,SigPanic
,SigQueue
,SigRestart
)を定義したSigTab
構造体を定義しています。
-
メモリ管理 (
mem.c
):src/pkg/runtime/netbsd/mem.c
は、Goランタイムのメモリ管理サブシステムがNetBSDのメモリ管理機能と連携するためのインターフェースを提供します。runtime·SysAlloc
は、OSから実行可能なメモリ領域を割り当てるためにmmap
を使用します。runtime·SysFree
は、割り当てられたメモリを解放するためにmunmap
を使用します。runtime·SysReserve
とruntime·SysMap
は、Goのヒープ管理におけるメモリの予約とマッピングのフェーズをNetBSDのmmap
と連携させます。特に64ビットシステムでは、アドレス空間の予約と実際のマッピングを分離して、より効率的なメモリ利用を目指しています。
-
スレッド管理 (
thread.c
):src/pkg/runtime/netbsd/thread.c
は、Goランタイムが新しいOSスレッドを作成し、管理するためのNetBSD固有のロジックを含んでいます。runtime·newosproc
は、GoのM (Machine) を表現する新しいOSスレッドを作成するために、NetBSD固有のrfork_thread
システムコールを使用します。RFPROC | RFTHREAD | RFMEM | RFNOWAIT
といったフラグを設定することで、新しいスレッドが親プロセスとメモリ空間を共有し、かつ親が子スレッドの終了を待つ必要がないように設定されます。runtime·semasleep
とruntime·semawakeup
は、Goのスケジューラがゴルーチンをブロックしたり、起こしたりするために使用するセマフォ操作を、NetBSDのthrsleep
とthrwakeup
システムコールを介して実装しています。getncpu
関数は、sysctl
システムコールを使用してシステムのCPU数を取得し、Goランタイムのスケジューラが利用可能なCPUリソースを把握できるようにします。
このコミットは、Go言語の移植性を高め、NetBSDという特定のOS環境でGoプログラムが安定して動作するための基盤を構築する上で不可欠なものです。
コアとなるコードの変更箇所
このコミットは、既存のファイルを変更するのではなく、NetBSDサポートのために新しいファイルを大量に追加しています。そのため、特定の「変更箇所」というよりは、追加されたファイル群全体がコアとなります。
特に重要なファイルは以下の通りです。
src/pkg/runtime/netbsd/386/defs.h
およびsrc/pkg/runtime/netbsd/amd64/defs.h
: NetBSD固有の定数とデータ構造の定義。src/pkg/runtime/netbsd/386/rt0.s
およびsrc/pkg/runtime/netbsd/amd64/rt0.s
: GoプログラムのNetBSD上でのエントリポイント。src/pkg/runtime/netbsd/386/signal.c
およびsrc/pkg/runtime/netbsd/amd64/signal.c
: NetBSD固有のシグナルハンドリングロジック。src/pkg/runtime/netbsd/386/sys.s
およびsrc/pkg/runtime/netbsd/amd64/sys.s
: NetBSD固有のシステムコールラッパー。src/pkg/runtime/netbsd/defs.go
:defs.h
を生成するためのGoの定義。src/pkg/runtime/netbsd/mem.c
: NetBSD固有のメモリ管理の実装。src/pkg/runtime/netbsd/os.h
: NetBSD固有のOS関連関数の宣言。src/pkg/runtime/netbsd/signals.h
: シグナル定義とGoランタイムでの処理フラグ。src/pkg/runtime/netbsd/thread.c
: NetBSD固有のスレッドおよびセマフォ管理の実装。
これらのファイルは、GoランタイムがNetBSDのカーネルと直接対話するための低レベルなインターフェースを提供し、Goの並行処理モデルやメモリモデルをNetBSD上で実現するために不可欠です。
コアとなるコードの解説
ここでは、特に重要な機能を提供するファイルから、代表的なコードスニペットとその解説を行います。
src/pkg/runtime/netbsd/386/sys.s
(システムコールラッパーの例)
TEXT runtime·mmap(SB),7,$36
LEAL arg0+0(FP), SI
LEAL 4(SP), DI
CLD
MOVSL // arg 1 - addr
MOVSL // arg 2 - len
MOVSL // arg 3 - prot
MOVSL // arg 4 - flags
MOVSL // arg 5 - fd
MOVL $0, AX
STOSL // arg 6 - pad
MOVSL // arg 7 - offset
MOVL $0, AX // top 64 bits of file offset
STOSL
MOVL $197, AX // sys_mmap
INT $0x80
JCC 2(PC)
NEGL AX
RET
このアセンブリコードは、mmap
システムコールを呼び出すためのGoランタイムのラッパーです。
TEXT runtime·mmap(SB),7,$36
:runtime·mmap
という関数を定義しています。$36
はスタックフレームのサイズを示します。LEAL arg0+0(FP), SI
など: 関数引数をスタックフレームからレジスタにロードしています。mmap
はaddr
,len
,prot
,flags
,fd
,offset
などの引数を取ります。MOVL $197, AX
:AX
レジスタにmmap
システムコールの番号(NetBSDの386アーキテクチャでは197)を設定します。INT $0x80
: ソフトウェア割り込み0x80
を発生させ、カーネルにシステムコールを実行するよう要求します。JCC 2(PC)
: システムコールが成功したかどうかをチェックします。成功した場合(キャリーフラグがクリア)、次の命令にジャンプします。NEGL AX
: システムコールが失敗した場合、AX
レジスタにエラーコードが設定されているため、それを負の値に変換して返します(Unix系システムコールの慣習)。
src/pkg/runtime/netbsd/thread.c
(スレッド作成の例)
void
runtime·newosproc(M *m, G *g, void *stk, void (*fn)(void))
{
int32 flags;
int32 ret;
flags = RFPROC | RFTHREAD | RFMEM | RFNOWAIT;
// ... (デバッグ出力など)
m->tls[0] = m->id; // so 386 asm can find it
if((ret = runtime·rfork_thread(flags, stk, m, g, fn)) < 0) {
runtime·printf("runtime: failed to create new OS thread (have %d already; errno=%d)\\n", runtime·mcount() - 1, -ret);
if (ret == -ENOTSUP)
runtime·printf("runtime: is kern.rthreads disabled?\\n");
runtime·throw("runtime.newosproc");
}
}
このCコードは、Goランタイムが新しいOSスレッドを作成する際に呼び出されるruntime·newosproc
関数です。
flags = RFPROC | RFTHREAD | RFMEM | RFNOWAIT;
:rfork_thread
システムコールに渡すフラグを設定しています。RFPROC
: 新しいプロセスを作成する(この場合はスレッドだが、rfork
の基本フラグ)。RFTHREAD
: 新しいスレッドを作成する。RFMEM
: 親とメモリ空間を共有する。RFNOWAIT
: 親プロセスは子スレッドの終了を待つ必要がない。
runtime·rfork_thread(flags, stk, m, g, fn)
: NetBSD固有のrfork_thread
システムコールを呼び出し、新しいOSスレッドを作成します。このスレッドは、stk
で指定されたスタックを使用し、fn
で指定された関数(通常はGoランタイムのスケジューラ関連の関数)を実行します。m
とg
は、新しいスレッドに渡されるGoのMとG(ゴルーチン)のポインタです。- エラーハンドリング:
rfork_thread
が失敗した場合、エラーメッセージを出力し、runtime·throw
を呼び出してパニックを発生させます。特に-ENOTSUP
エラーの場合、kern.rthreads
が有効になっているか確認するよう促しています。
src/pkg/runtime/netbsd/signal.c
(シグナルハンドラの例)
void
runtime·sighandler(int32 sig, Siginfo *info, void *context, G *gp)
{
Sigcontext *r = context;
uintptr *sp;
if(sig == SIGPROF) {
runtime·sigprof((uint8*)r->sc_eip, (uint8*)r->sc_esp, nil, gp);
return;
}
if(gp != nil && (runtime·sigtab[sig].flags & SigPanic)) {
// Make it look like a call to the signal func.
// ...
if(r->sc_eip != 0) {
sp = (uintptr*)r->sc_esp;
*--sp = r->sc_eip;
r->sc_esp = (uintptr)sp;
}
r->sc_eip = (uintptr)runtime·sigpanic;
return;
}
// ... (その他のシグナル処理、パニック時のトレースバックなど)
}
このCコードは、Goランタイムの主要なシグナルハンドラであるruntime·sighandler
関数の一部です。
Sigcontext *r = context;
:context
引数からシグナル発生時のCPUレジスタの状態(Sigcontext
構造体)を取得します。これにより、シグナル発生時のプログラムカウンタ(sc_eip
またはsc_rip
)やスタックポインタ(sc_esp
またはsc_rsp
)にアクセスできます。if(sig == SIGPROF)
:SIGPROF
(プロファイリングタイマー)シグナルの処理です。CPUプロファイリングのためにruntime·sigprof
を呼び出します。if(gp != nil && (runtime·sigtab[sig].flags & SigPanic))
:SigPanic
フラグが設定されているシグナル(例:SIGSEGV
,SIGBUS
,SIGFPE
)がGoルーチン内で発生した場合の処理です。- このブロックでは、シグナル発生時の実行コンテキストを操作し、
runtime·sigpanic
関数が呼び出されたかのように見せかけます。具体的には、現在のプログラムカウンタ(r->sc_eip
)をスタックにプッシュし、r->sc_eip
をruntime·sigpanic
のアドレスに書き換えます。これにより、シグナルハンドラから戻った際にruntime·sigpanic
が実行され、Goのパニック処理が開始されます。これは、Goのランタイムがシグナルを捕捉し、Goのパニックメカニズムに変換するための重要な部分です。
- このブロックでは、シグナル発生時の実行コンテキストを操作し、
これらのコードスニペットは、GoランタイムがNetBSDの低レベルなOS機能とどのように連携し、Goプログラムの実行環境を構築しているかを示しています。
関連リンク
- Go言語公式ドキュメント: https://go.dev/doc/
- NetBSD公式ウェブサイト: https://www.netbsd.org/
- Goのランタイムに関するブログ記事やドキュメント(一般的な情報源)
参考にした情報源リンク
- Goのソースコード(特に
src/pkg/runtime
ディレクトリ) - NetBSDのシステムコールに関するドキュメント(例:
man 2 syscalls
) - NetBSDのカーネルソースコード(特に
sys/kern
やsys/arch
ディレクトリ) - Goのコミット履歴と関連するコードレビュー(
https://golang.org/cl/5477052
) - Goのランタイムに関する一般的な知識と、OSの低レベルプログラミングに関する知識。
- Goの
godefs
ツールの機能に関する情報。 - Unix系OSにおけるシグナル、メモリ管理、プロセス/スレッド管理の概念。
- アセンブリ言語(x86/x64)の基本的な知識。