[インデックス 15210] ファイルの概要
このコミットは、Go言語のsyscallパッケージにNetBSDオペレーティングシステムとARMアーキテクチャの組み合わせに対するサポートを追加するものです。具体的には、NetBSD/ARM環境でGoプログラムがシステムコールを直接呼び出せるようにするための低レベルなアセンブリコード、Go言語のラッパー関数、およびシステム固有の定数や型定義が追加されています。
コミット
commit 8311697792122ba853ff1d52b1731f7fdd1e577b
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Wed Feb 13 01:03:25 2013 +0800
syscall: NetBSD/ARM support
R=golang-dev, rsc, dave
CC=golang-dev
https://golang.org/cl/7288050
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8311697792122ba853ff1d52b1731f7fdd1e577b
元コミット内容
syscall: NetBSD/ARM support
変更の背景
Go言語は、その設計思想として、様々なプラットフォーム(OSとアーキテクチャの組み合わせ)での動作をサポートすることを目指しています。syscallパッケージは、Goプログラムが基盤となるオペレーティングシステムの機能(ファイル操作、ネットワーク通信、プロセス管理など)に直接アクセスするためのインターフェースを提供します。
このコミットが作成された2013年2月時点では、Go言語は既に複数の主要なOSとアーキテクチャの組み合わせをサポートしていましたが、NetBSD/ARMはその対象外でした。NetBSDは、移植性の高さで知られるUNIX系OSであり、ARMはモバイルデバイスや組み込みシステムで広く利用されるプロセッサアーキテクチャです。この組み合わせへのサポートを追加することは、Go言語の適用範囲を広げ、より多様な環境での利用を可能にする上で重要なステップでした。
具体的には、GoプログラムがNetBSD/ARM上で動作し、そのOSの提供するシステムコールを効率的かつ安全に利用できるようにするために、低レベルな実装が必要とされていました。これには、システムコールを呼び出すためのアセンブリコード、Go言語からシステムコールを呼び出すためのラッパー、そしてNetBSD/ARM固有のシステムコール番号、エラーコード、データ構造などの定義が含まれます。
前提知識の解説
1. システムコール (System Call)
システムコールは、オペレーティングシステム(OS)のカーネルが提供するサービスを、ユーザー空間のプログラムが利用するためのインターフェースです。ファイルI/O、メモリ管理、プロセス制御、ネットワーク通信など、OSの根幹をなす機能は、直接ハードウェアにアクセスするため、ユーザープログラムが勝手に操作することはできません。システムコールは、これらの特権的な操作を安全に実行するための唯一の手段を提供します。
Go言語のsyscallパッケージは、OSのシステムコールをGoプログラムから直接呼び出すための機能を提供します。これにより、Goプログラムは低レベルなOS機能にアクセスし、OS固有の動作を制御することができます。
2. NetBSD
NetBSDは、BSD系UNIXオペレーティングシステムの一つで、その最大の特徴は「移植性の高さ」です。非常に多くのハードウェアアーキテクチャに対応しており、デスクトップPCから組み込みシステム、古いハードウェアまで、幅広い環境で動作します。この移植性の高さは、クリーンな設計と厳格なコード管理によって実現されています。
3. ARMアーキテクチャ
ARM(Advanced RISC Machine)は、主にモバイルデバイス、組み込みシステム、IoTデバイスなどで広く利用されているRISC(Reduced Instruction Set Computer)ベースのプロセッサアーキテクチャです。低消費電力と高い性能効率が特徴で、スマートフォン、タブレット、スマートテレビ、ルーターなど、私たちの身の回りの多くの電子機器に搭載されています。
4. Go言語のsyscallパッケージの構造
Go言語のsyscallパッケージは、OSとアーキテクチャの組み合わせごとに異なる実装を持っています。これは、システムコールの呼び出し規約、システムコール番号、エラーコード、データ構造などがOSやアーキテクチャによって異なるためです。
asm_*.sファイル: アセンブリ言語で書かれたファイルで、GoのランタイムとOSのシステムコールの間の橋渡しをします。Goの関数呼び出し規約からOSのシステムコール呼び出し規約への変換(レジスタへの引数配置、システムコール命令の実行など)を行います。syscall_*.goファイル: Go言語で書かれたファイルで、OS固有のユーティリティ関数や、アセンブリで定義された低レベルなシステムコールラッパーをGoの関数として公開します。zerrors_*.go、zsyscall_*.go、zsysnum_*.go、ztypes_*.goファイル: これらのファイルは、通常、C言語のヘッダーファイルから自動生成されます。zerrors_*.go: OS固有のエラーコード(例:EACCES,ENOENT)をGoの定数として定義します。zsyscall_*.go: OSのシステムコールをGoの関数として呼び出すためのラッパー関数を定義します。これらの関数は、asm_*.sで定義された低レベルなシステムコールエントリポイントを呼び出します。zsysnum_*.go: OS固有のシステムコール番号(例:SYS_READ,SYS_WRITE)をGoの定数として定義します。ztypes_*.go: OS固有のデータ構造(例:Timespec,Stat_t)をGoの構造体として定義します。
5. mkerrors.sh, mksyscall.pl, cgo -godefs
これらのツールは、Go言語のsyscallパッケージでOS固有の定数や型、システムコールラッパーを自動生成するために使用されます。
cgo -godefs: C言語のヘッダーファイルからGoの型定義を生成するツールです。これにより、Cの構造体や定数をGoのコードで利用できるようになります。mkerrors.sh: OSのエラーコードをGoの定数として生成するスクリプトです。mksyscall.pl: システムコール定義からGoのシステムコールラッパー関数を生成するPerlスクリプトです。
これらの自動生成プロセスにより、手動でのエラーやOSの変更への追従が容易になります。
技術的詳細
このコミットは、Go言語のsyscallパッケージにNetBSD/ARMプラットフォームのサポートを統合するための包括的な変更です。これは、GoプログラムがNetBSD/ARM上でネイティブに動作し、OSの低レベルな機能にアクセスできるようにするために不可欠なものです。
変更は主に以下の6つのファイルにわたります。
-
src/pkg/syscall/asm_netbsd_arm.s:- このファイルは、ARMアセンブリ言語で書かれており、GoのランタイムとNetBSDカーネル間のシステムコール呼び出しの低レベルなインターフェースを定義します。
Syscall,Syscall6,Syscall9といったGoのsyscallパッケージの主要な関数に対応するアセンブリルーチンが含まれています。これらのルーチンは、Goの関数呼び出し規約(引数がスタックに積まれる)から、NetBSD/ARMのシステムコール呼び出し規約(引数がレジスタに渡される)への変換を行います。- 具体的には、
SWI $0(Software Interrupt 0) 命令を使用してシステムコールをトリガーします。これは、ARMアーキテクチャにおける一般的なシステムコール呼び出しメカニズムです。 - システムコール実行前には
runtime·entersyscallを呼び出してGoランタイムにシステムコールへの移行を通知し、実行後にはruntime·exitsyscallを呼び出してGoランタイムに制御を戻します。これにより、Goのスケジューラがシステムコール中のゴルーチンを適切に管理できます。 - エラー処理もアセンブリレベルで行われ、システムコールが失敗した場合(通常、キャリーフラグがセットされる)には、エラーコードをGoの
error型に変換して返します。
-
src/pkg/syscall/syscall_netbsd_arm.go:- このGoファイルは、NetBSD/ARM固有のユーティリティ関数や、
syscallパッケージの一般的なインターフェースを実装します。 Getpagesize(): システムのページサイズ(通常4096バイト)を返します。TimespecToNsec(),NsecToTimespec():Timespec構造体(秒とナノ秒で時間を表現)とナノ秒単位のint64値との間で変換を行います。TimevalToNsec(),NsecToTimeval():Timeval構造体(秒とマイクロ秒で時間を表現)とナノ秒単位のint64値との間で変換を行います。SetKevent(),SetLen(),SetControllen():kqueueイベント、Iovec(I/Oベクタ)、Msghdr(メッセージヘッダー)、Cmsghdr(補助データヘッダー)といったOS固有のデータ構造を操作するためのヘルパー関数です。これらは、Goの型とCの型の間でデータを安全にやり取りするために必要です。
- このGoファイルは、NetBSD/ARM固有のユーティリティ関数や、
-
src/pkg/syscall/zerrors_netbsd_arm.go:- このファイルは、
mkerrors.shスクリプトによって自動生成されたもので、NetBSD/ARM固有のシステムエラーコード(例:EACCES,ENOENT,ETIMEDOUTなど)をGoの定数として定義します。 - また、
AF_*(Address Family),DLT_*(Datalink Type),ETHERTYPE_*(Ethernet Type),IFF_*(Interface Flags),IFT_*(Interface Type),IPPROTO_*(IP Protocol),MSG_*(Message Flags),O_*(Open Flags),SOCK_*(Socket Type),SO_*(Socket Options),SIG*(Signals) など、NetBSDシステムプログラミングで利用される多数の定数が含まれています。 - これらの定数は、GoプログラムがNetBSDのAPIと互換性のある方法で動作するために不可欠です。
- このファイルは、
-
src/pkg/syscall/zsyscall_netbsd_arm.go:- このファイルは、
mksyscall.plスクリプトによって自動生成されたもので、NetBSD/ARMのシステムコールをGo言語から呼び出すためのラッパー関数を定義します。 - 各システムコール(例:
getgroups,setgroups,wait4,accept,bind,connect,socket,getsockopt,setsockopt,getpeername,getsockname,Shutdown,socketpair,recvfromなど)に対して、対応するGo関数が生成されています。 - これらの関数は、Goの引数を
uintptrに変換し、asm_netbsd_arm.sで定義されたRawSyscallまたはSyscall関数を呼び出します。 - システムコールからの戻り値(成功時の値とエラーコード)をGoの
intとerror型に適切に変換します。
- このファイルは、
-
src/pkg/syscall/zsysnum_netbsd_arm.go:- このファイルは、
cgo -godefsツールによって自動生成されたもので、NetBSD/ARM固有のシステムコール番号をGoの定数として定義します。 - 例えば、
SYS_READ,SYS_WRITE,SYS_OPEN,SYS_CLOSE,SYS_EXITなど、各システムコールには一意の番号が割り当てられています。 - これらの定数は、
zsyscall_netbsd_arm.goでシステムコールを呼び出す際に使用されます。
- このファイルは、
-
src/pkg/syscall/ztypes_netbsd_arm.go:- このファイルは、
cgo -godefsツールによって自動生成されたもので、NetBSD/ARMのシステムプログラミングで利用されるC言語のデータ構造(例:Stat_t,Timeval,Timespec,Rusage,SockaddrInet4,SockaddrInet6,Msghdr,Cmsghdr,Iovec,Kevent_tなど)に対応するGoの構造体を定義します。 - これらのGoの構造体は、C言語の構造体とメモリレイアウトが一致するように設計されており、GoとCの間でデータを安全にやり取りできるようにします。
- このファイルは、
これらのファイルが追加されることで、GoのコンパイラとランタイムはNetBSD/ARM環境でシステムコールを正しく実行し、OSの機能を利用できるようになります。これは、Go言語がNetBSD/ARM上で動作するアプリケーションを開発するための基盤を確立するものです。
コアとなるコードの変更箇所
このコミットは、既存のファイルを変更するのではなく、NetBSD/ARMサポートのために新しいファイルを一括して追加しています。
追加されたファイルは以下の通りです。
src/pkg/syscall/asm_netbsd_arm.s(124行追加)src/pkg/syscall/syscall_netbsd_arm.go(42行追加)src/pkg/syscall/zerrors_netbsd_arm.go(1642行追加)src/pkg/syscall/zsyscall_netbsd_arm.go(1228行追加)src/pkg/syscall/zsysnum_netbsd_arm.go(271行追加)src/pkg/syscall/ztypes_netbsd_arm.go(363行追加)
これらのファイルは、NetBSD/ARMにおけるシステムコールインターフェースの全体像を構成しています。
コアとなるコードの解説
src/pkg/syscall/asm_netbsd_arm.s
このファイルは、Goのシステムコールラッパーが最終的にOSのカーネルにシステムコールを要求するためのアセンブリコードです。
TEXT ·Syscall(SB),7,$0
BL runtime·entersyscall(SB)
MOVW 0(FP), R0 // sigcall num
MOVW 4(FP), R1 // a1
MOVW 8(FP), R2 // a2
MOVW 12(FP), R3 // a3
SWI $0 // syscall
MOVW $0, R2
BCS error
MOVW R0, 16(FP) // r1
MOVW R1, 20(FP) // r2
MOVW R2, 24(FP) // err
BL runtime·exitsyscall(SB)
RET
error:
MOVW $-1, R3
MOVW R3, 16(FP) // r1
MOVW R2, 20(FP) // r2
MOVW R0, 24(FP) // err
BL runtime·exitsyscall(SB)
RET
TEXT ·Syscall(SB),7,$0:SyscallというGo関数に対応するアセンブリルーチンの開始を宣言しています。SBはスタックベースポインタ、7はフレームサイズ、$0は引数のサイズを示します。BL runtime·entersyscall(SB): システムコールに入る前にGoランタイムのentersyscall関数を呼び出します。これは、Goのスケジューラがこのゴルーチンがシステムコールを実行中であることを認識し、必要に応じて他のゴルーチンをスケジュールできるようにするためです。MOVW 0(FP), R0 // sigcall num: Goの関数呼び出し規約では、引数はフレームポインタ(FP)からのオフセットでアクセスされます。ここでは、最初の引数(システムコール番号)をR0レジスタに移動しています。NetBSD/ARMのシステムコール規約では、システムコール番号は通常R0に配置されます。MOVW 4(FP), R1 // a1、MOVW 8(FP), R2 // a2、MOVW 12(FP), R3 // a3: 続く3つの引数a1,a2,a3をそれぞれR1,R2,R3レジスタに移動します。これらはシステムコールへの引数です。SWI $0 // syscall: ARMアーキテクチャにおけるソフトウェア割り込み命令です。$0は、NetBSDにおけるシステムコールをトリガーするための特定の割り込み番号です。この命令が実行されると、CPUは特権モードに切り替わり、カーネルのシステムコールハンドラが呼び出されます。MOVW $0, R2: システムコールが成功した場合、R2レジスタに0をセットします。これは、Goのsyscall関数が返すerr値(エラーがない場合は0)に対応します。BCS error:BCSは"Branch if Carry Set"の略です。ARMのシステムコール規約では、システムコールがエラーを返した場合、キャリーフラグ(CPSRレジスタのCビット)がセットされます。この命令は、キャリーフラグがセットされていればerrorラベルにジャンプし、エラー処理を行います。MOVW R0, 16(FP) // r1、MOVW R1, 20(FP) // r2、MOVW R2, 24(FP) // err: システムコールが成功した場合、R0とR1レジスタにはシステムコールの戻り値が格納されます。これらをGoの戻り値r1,r2、そしてerr(成功時は0)としてスタックに書き戻します。BL runtime·exitsyscall(SB): システムコールから戻る前にGoランタイムのexitsyscall関数を呼び出します。これにより、Goのスケジューラはゴルーチンがシステムコールを完了したことを認識します。RET: Goの呼び出し元関数に戻ります。error:: エラー処理のセクションです。MOVW $-1, R3: Goのsyscall関数は、エラー時にr1に-1を返す慣例があります。MOVW R0, 24(FP) // err: システムコールがエラーを返した場合、R0レジスタにはエラーコードが格納されます。これをGoのerr戻り値としてスタックに書き戻します。
Syscall6とSyscall9も同様のロジックですが、引数の数が増えるため、一部の引数はスタック経由で渡されます。RawSyscallとRawSyscall6は、runtime·entersyscallとruntime·exitsyscallを呼び出さない、より低レベルなシステムコールラッパーです。これらは、Goランタイムの介入なしにシステムコールを実行する必要がある場合に利用されます。
src/pkg/syscall/zsyscall_netbsd_arm.go
このファイルは、Go言語で書かれたシステムコールラッパーの例です。
func getgroups(ngid int, gid *_Gid_t) (n int, err error) {
r0, _, e1 := RawSyscall(SYS_GETGROUPS, uintptr(ngid), uintptr(unsafe.Pointer(gid)), 0)
n = int(r0)
if e1 != 0 {
err = e1
}
return
}
func getgroups(ngid int, gid *_Gid_t) (n int, err error):getgroupsシステムコールに対応するGo関数です。引数と戻り値の型が定義されています。r0, _, e1 := RawSyscall(SYS_GETGROUPS, uintptr(ngid), uintptr(unsafe.Pointer(gid)), 0):RawSyscallは、asm_netbsd_arm.sで定義されたアセンブリルーチンを呼び出すGoの関数です。SYS_GETGROUPS:zsysnum_netbsd_arm.goで定義されたgetgroupsシステムコールの番号です。uintptr(ngid),uintptr(unsafe.Pointer(gid)): Goの引数をuintptr型にキャストしています。unsafe.Pointerは、Goのポインタを任意の型に変換するために使用され、Cのポインタとしてシステムコールに渡せるようにします。r0, _, e1:RawSyscallからの戻り値です。r0はシステムコールの主要な戻り値、_は無視される戻り値(通常はr1)、e1はエラーコードです。
n = int(r0):getgroupsシステムコールの成功時の戻り値(取得したグループの数)をGoのint型に変換してnに代入します。if e1 != 0 { err = e1 }:RawSyscallがエラーを返した場合(e1が0でない場合)、そのエラーコードをGoのerror型に変換してerrに代入します。
これらの自動生成されたラッパー関数は、GoプログラムがNetBSDのシステムコールをGoの慣用的な方法で呼び出せるようにし、プラットフォーム固有の詳細を抽象化します。
関連リンク
- Go言語の
syscallパッケージのドキュメント: https://pkg.go.dev/syscall - NetBSDプロジェクトの公式サイト: https://www.netbsd.org/
- ARMアーキテクチャについて (Wikipedia): https://ja.wikipedia.org/wiki/ARM%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3
参考にした情報源リンク
- Go言語のソースコード(特に
src/pkg/syscallディレクトリ内の他のOS/アーキテクチャの実装) - NetBSDのシステムコールに関するドキュメント(
man 2 introなど) - ARMアーキテクチャのシステムコール呼び出し規約に関する情報
- Go言語のコンパイラとランタイムに関するドキュメントやブログ記事
cgoツールのドキュメント