Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 18637] ファイルの概要

このコミットは、Go言語の syscall パッケージにSolarisオペレーティングシステム(GOOS=solaris)のサポートを追加するものです。これにより、GoプログラムがSolaris環境でシステムコールを直接利用できるようになり、OS固有の機能へのアクセスや、より低レベルな操作が可能になります。

コミット

commit 1c9861918b069b0f9a6140420355a5c91dbd069d
Author: Aram Hăvărneanu <aram@mgk.ro>
Date:   Tue Feb 25 21:12:10 2014 +1100

    syscall: add support for GOOS=solaris
    
    These are only the new files, autogenerated files are in a
    different CL to keep the size down.
    
    LGTM=dave, minux.ma, jsing
    R=golang-codereviews, dave, jsing, gobot, minux.ma, rsc, iant, mikioh.mikioh
    CC=golang-codereviews
    https://golang.org/cl/36000043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/1c9861918b069b0f9a6140420355a5c91dbd069d

元コミット内容

このコミットの目的は、Go言語の syscall パッケージにSolarisオペレーティングシステム向けのサポートを追加することです。具体的には、Solaris固有のシステムコール、データ構造、および関連するユーティリティ関数が導入されています。コミットメッセージには「これらのファイルは新しいファイルのみであり、自動生成されたファイルはサイズを抑えるために別の変更リスト(CL)にある」と記載されており、このコミットがSolarisサポートの基盤となる手書きのコードやスクリプトに焦点を当てていることが示唆されています。

変更の背景

Go言語は、その設計思想としてクロスプラットフォーム対応を重視しています。syscall パッケージは、Goプログラムがオペレーティングシステムの低レベルな機能(ファイル操作、プロセス管理、ネットワーク通信など)にアクセスするためのインターフェースを提供します。Goが新しいOSをサポートするためには、そのOS固有のシステムコールをGoの syscall パッケージにマッピングし、GoのランタイムがそのOS上で正しく動作するように調整する必要があります。

このコミットが行われた2014年当時、Goは既にLinux、macOS (Darwin)、FreeBSDなどの主要なUnix系OSをサポートしていましたが、Solarisはまだ公式にサポートされていませんでした。Solarisは、特にエンタープライズ環境や特定のサーバーインフラストラクチャで利用されることがあり、Goがこれらの環境で動作するためにはSolaris固有のシステムコール実装が必要でした。このコミットは、Goの対応OSを拡大し、より多くの環境でGoプログラムが動作できるようにするための重要な一歩でした。

前提知識の解説

1. システムコール (System Call)

システムコールは、ユーザー空間で動作するプログラムが、カーネル空間で提供されるOSのサービスを要求するためのインターフェースです。ファイルI/O、プロセス生成、メモリ管理、ネットワーク通信など、OSのほとんどの機能はシステムコールを通じて提供されます。Goの syscall パッケージは、これらのシステムコールをGo言語から呼び出すためのラッパーを提供します。

2. GOOSGOARCH

Goのビルドシステムでは、GOOS (Go Operating System) と GOARCH (Go Architecture) という環境変数を使用して、ターゲットとなるOSとCPUアーキテクチャを指定します。例えば、GOOS=linux GOARCH=amd64 はLinux x86-64環境向けのビルドを意味します。このコミットでは、GOOS=solaris のサポートが追加されています。

3. ビルドタグ (Build Tags)

Goのソースファイルには、// +build <tag> の形式でビルドタグを記述することができます。これにより、特定のOS、アーキテクチャ、またはその他の条件に基づいて、どのファイルをビルドに含めるかを制御できます。例えば、// +build linux はLinuxでのみビルドされるファイルを示し、// +build darwin dragonfly freebsd linux netbsd openbsd は複数のUnix系OSで共通して使用されるファイルを示します。このコミットでは、既存のUnix系ファイルに solaris タグが追加され、Solaris固有のファイルには solaris タグが付与されています。

4. Solaris OS

Solarisは、Sun Microsystems(現在はOracleが所有)によって開発されたUnix系オペレーティングシステムです。特に、その堅牢性、スケーラビリティ、およびZFSファイルシステムやDTraceなどの高度な機能で知られています。Solarisのシステムコールインターフェースは、他のBSD系やLinux系OSとは異なる部分が多く、GoがSolarisをサポートするためにはこれらの違いを吸収する専用の実装が必要でした。

5. mksyscall および関連スクリプト

Goの syscall パッケージでは、C言語のヘッダーファイルからシステムコールのGoラッパーコードを自動生成するためのスクリプトが使用されます。これには mksyscall.pl (または mksyscall_unix.pl などOS固有のもの)、mkerrors.shmkall.sh などが含まれます。これらのスクリプトは、Cの構造体定義やシステムコールプロトタイプを解析し、Goの syscall パッケージで利用可能なGoの型定義や関数スタブを生成します。これにより、手動での記述ミスを減らし、OSのAPI変更への追従を容易にします。

6. 共有ライブラリ (Shared Library) と動的リンク

Solarisを含む多くのUnix系OSでは、共有ライブラリ(.so ファイル)を使用して、複数のプログラム間で共通のコードを共有します。Goプログラムがこれらの共有ライブラリ内の関数を呼び出す場合、動的リンクのメカニズムを使用する必要があります。dlopen, dlsym, dlclose といった関数は、実行時に共有ライブラリをロードし、その中のシンボル(関数や変数)のアドレスを取得するために使用されます。

技術的詳細

このコミットは、Goの syscall パッケージにSolarisサポートを統合するために、以下の主要な技術的アプローチを採用しています。

  1. Solaris固有のシステムコールラッパーの追加:

    • src/pkg/syscall/syscall_solaris.go: Solaris固有のシステムコール(例: Pipe, Getsockname, Accept, Recvmsg, Sendmsg など)のGoラッパー関数が定義されています。これらの関数は、Goの型とCのシステムコールインターフェース間の変換を処理します。
    • src/pkg/syscall/syscall_solaris_amd64.go: AMD64アーキテクチャ向けのSolaris固有のユーティリティ関数(例: Getpagesize, TimespecToNsec, NsecToTimeval など)が定義されています。
    • src/pkg/syscall/asm_solaris_amd64.s: アセンブリ言語で記述された、Solaris AMD64向けの低レベルなシステムコール呼び出しのスタブが含まれています。Goのランタイムが直接OSのシステムコールを呼び出すためのブリッジとして機能します。
  2. プロセス実行 (exec) のSolaris対応:

    • src/pkg/syscall/exec_solaris.go: forkAndExecInChild 関数がSolaris向けに実装されています。この関数は、新しいプロセスをフォークし、指定されたプログラムを実行する際の、ファイルディスクリプタの処理、chroot、ユーザー/グループIDの設定、セッションID/プロセスグループIDの設定など、OS固有の複雑なロジックを扱います。特に、Goのランタイムが提供する runtime_BeforeForkruntime_AfterFork を使用して、フォーク前後のランタイムの状態を適切に管理しています。また、RawSyscall を使用せず、手書きのシステムコール呼び出し (forkx, execve など) を行うことで、Goランタイムのロックやメモリ割り当てを避ける工夫がされています。
  3. 型定義のSolaris対応:

    • src/pkg/syscall/types_solaris.go: Solarisのシステムコールで使用されるC言語の構造体(例: sockaddr_in, sockaddr_in6, dirent, stat, rusage, timespec, timeval など)に対応するGoの型定義が cgo -godefs を通じて生成されるように設定されています。これにより、GoプログラムがSolarisのシステムコールとやり取りする際に、正しいデータ構造を使用できるようになります。
  4. システムコール生成スクリプトの更新:

    • src/pkg/syscall/mksyscall_solaris.pl: Solaris向けのシステムコールラッパーを自動生成するためのPerlスクリプトが追加されています。このスクリプトは、//sys または //sysnb コメントでマークされたGoの関数プロトタイプを解析し、対応するシステムコール呼び出しのGoコードを生成します。これにより、SolarisのシステムコールAPIの変更に柔軟に対応できます。
    • src/pkg/syscall/mkerrors.sh: Solaris固有のエラーコードやシグナル定数をGoのコードに含めるためのスクリプトが更新され、Solarisのヘッダーファイル (sys/types.h, sys/socket.h など) がインクルードされるようになっています。
    • src/pkg/syscall/mkall.sh: 全てのシステムコール関連ファイルを生成するためのマスターシェルスクリプトが更新され、Solaris向けの mksyscall_solaris.plmkerrors.sh の実行が組み込まれています。
  5. 共有ライブラリの動的ロードサポート:

    • src/pkg/syscall/so_solaris.go: Solarisにおける共有ライブラリ(.so ファイル)の動的ロードを扱うための so (shared object) および lazySO (lazy shared object) 型が導入されています。これらは dlopen, dlsym, dlclose といったSolarisの動的リンクAPIをGoから呼び出すためのラッパーを提供します。lazySO は、共有ライブラリのロードやプロシージャのアドレス解決を、実際にそのプロシージャが呼び出されるまで遅延させることで、起動時のオーバーヘッドを削減します。
  6. 既存のUnix系ファイルの更新:

    • src/pkg/syscall/env_unix.go, src/pkg/syscall/exec_unix.go, src/pkg/syscall/sockcmsg_unix.go, src/pkg/syscall/syscall_unix.go, src/pkg/syscall/syscall_unix_test.go: これらのファイルには、ビルドタグに solaris が追加されています。これは、SolarisがこれらのUnix系OSで共通のシステムコール実装を共有できることを示しています。

これらの変更により、GoプログラムはSolaris環境でネイティブなシステムコールを効率的かつ安全に利用できるようになり、Goのクロスプラットフォーム対応がさらに強化されました。

コアとなるコードの変更箇所

このコミットは多数のファイルにわたる変更を含んでいますが、特にSolarisサポートの核となる新規ファイルは以下の通りです。

  1. src/pkg/syscall/exec_solaris.go:

    • forkAndExecInChild 関数がSolaris向けに実装されています。これは、新しいプロセスをフォークし、その中で指定されたコマンドを実行する際のOS固有のロジックを扱います。ファイルディスクリプタの再配置、chroot、ユーザー/グループIDの設定、セッション/プロセスグループの設定などが含まれます。
  2. src/pkg/syscall/mksyscall_solaris.pl:

    • Solarisのシステムコールラッパーを自動生成するためのPerlスクリプトです。Goのソースファイル内の //sys コメントを解析し、対応するシステムコール呼び出しのGoコードを生成します。
  3. src/pkg/syscall/so_solaris.go:

    • Solarisにおける共有ライブラリ(.so)の動的ロードを管理するためのコードが含まれています。loadSOFindProclazySOlazyProc などの型と関数が定義されており、dlopendlsymdlclose といったSolarisのAPIをGoから利用できるようにします。
  4. src/pkg/syscall/syscall_solaris.go:

    • Solaris固有のシステムコール(例: Pipe, Accept, Recvmsg, Sendmsg など)のGoラッパー関数が多数定義されています。また、ParseDirent のようなディレクトリエントリーを解析するユーティリティ関数も含まれます。
  5. src/pkg/syscall/types_solaris.go:

    • Solarisのシステムコールで使用されるC言語の構造体に対応するGoの型定義が記述されています。これらは cgo -godefs を通じてGoの型に変換されます。

コアとなるコードの解説

src/pkg/syscall/exec_solaris.goforkAndExecInChild

この関数は、Goプログラムが新しい外部プロセスを起動する際にSolaris上でどのように動作するかを定義します。

func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) {
	// ... (変数宣言とfdの準備) ...

	runtime_BeforeFork() // Goランタイムのフォーク前処理
	r1, err1 := forkx(0x1) // FORK_NOSIGCHLD (Solaris固有のフォークシステムコール)
	if err1 != 0 {
		runtime_AfterFork() // Goランタイムのフォーク後処理
		return 0, err1
	}

	if r1 != 0 {
		// 親プロセス: PIDを返して終了
		runtime_AfterFork()
		return int(r1), 0
	}

	// 子プロセス: ここから先は子プロセスでの処理
	// ... (Setsid, Setpgid, Chroot, User/Groups, Chdir の設定) ...

	// ファイルディスクリプタの再配置とクローズオンエグゼックフラグの設定
	// ... (複雑なfd操作ロジック) ...

	// 最終的に execve を呼び出して新しいプログラムを実行
	err1 = execve(
		uintptr(unsafe.Pointer(argv0)),
		uintptr(unsafe.Pointer(&argv[0])),
		uintptr(unsafe.Pointer(&envv[0])))

childerror:
	// エラーが発生した場合、パイプを通じて親プロセスにエラーコードを送信
	write1(uintptr(pipe), uintptr(unsafe.Pointer(&err1)), unsafe.Sizeof(err1))
	for {
		exit(253) // エラー終了
	}
}

この関数は、Goの os/exec パッケージの基盤となる部分で、Solarisの forkexec のセマンティクスに合わせて実装されています。特に注目すべきは、runtime_BeforeFork()runtime_AfterFork() の呼び出しです。これらはGoランタイムがフォークの前後で内部状態(ガベージコレクタのロックなど)を適切に処理するために必要です。また、RawSyscall を使わず、forkxexecve といった手書きのシステムコールラッパーを直接呼び出すことで、Goランタイムのロックやスケジューリングの影響を受けないようにしています。これは、フォーク後の子プロセスでは、親プロセスのメモリ状態をそのまま引き継ぐため、Goランタイムの複雑な機能(ガベージコレクション、goroutineスケジューリングなど)が安全に動作しない可能性があるためです。

src/pkg/syscall/mksyscall_solaris.pl

このPerlスクリプトは、Goの syscall パッケージのソースファイルに記述された特別なコメント (//sys//sysnb) を解析し、対応するシステムコール呼び出しのGoコードを自動生成します。

# Line must be of the form
#	func Open(path string, mode int, perm int) (fd int, err error)
# Split into name, in params, out params.
if(!/^\/\/sys(nb)? (\w+)\(([^()]*)\)\s*(?:\(([^()]+)\))?\s*(?:=\s*(?:(\w*)\.)?(\w*))?$/) {
	print STDERR "$ARGV:$.: malformed //sys declaration\n";
	$errors = 1;
	next;
}
my ($nb, $func, $in, $out, $modname, $sysname) = ($1, $2, $3, $4, $5, $6);

# ... (引数の解析とGoコードの生成ロジック) ...

# Actual call.
my $args = join(', ', @args);
my $call = "$asm($sysvarname.Addr(), $nargs, $args)";

# Assign return values.
# ... (戻り値の処理) ...

$text .= sprintf "func %s(%s)%s {\\n", $func, join(', ', @in), $out;
$text .= "\t$ret[0], $ret[1], $ret[2] := $call\\n";
# ... (エラー処理など) ...
$text .= "}\\n";

このスクリプトは、Goの syscall パッケージの自動生成の仕組みの核心をなすものです。開発者がGoの関数プロトタイプを //sys コメントで記述するだけで、対応する低レベルなシステムコール呼び出しコードが自動的に生成されるため、開発効率が向上し、OS間の差異を吸収する手間が軽減されます。

src/pkg/syscall/so_solaris.golazySOlazyProc

これらの型は、Solarisにおける共有ライブラリの動的ロードを効率的に行うためのものです。

type lazySO struct {
	mu   sync.Mutex
	so   *so // non nil once SO is loaded
	Name string
}

// Load loads single shared file d.Name into memory.
func (d *lazySO) Load() error {
	if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.so))) == nil {
		d.mu.Lock()
		defer d.mu.Unlock()
		if d.so == nil {
			so, e := loadSO(d.Name) // dlopen を呼び出す
			if e != nil {
				return e
			}
			atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.so)), unsafe.Pointer(so))
		}
	}
	return nil
}

type lazyProc struct {
	mu   sync.Mutex
	Name string
	l    *lazySO
	proc *proc
}

// Find searches the shared library for procedure named p.Name.
func (p *lazyProc) Find() error {
	if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&p.proc))) == nil {
		p.mu.Lock()
		defer p.mu.Unlock()
		if p.proc == nil {
			e := p.l.Load() // 共有ライブラリをロード
			if e != nil {
				return e
			}
			proc, e := p.l.so.FindProc(p.Name) // dlsym を呼び出す
			if e != nil {
				return e
			}
			atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&p.proc)), unsafe.Pointer(proc))
		}
	}
	return nil
}

lazySO は共有ライブラリ自体を、lazyProc はその中のプロシージャ(関数)を表します。これらの「lazy」な実装は、実際に共有ライブラリが使用されるまでロードやシンボル解決を遅延させることで、プログラムの起動時間を短縮し、不要なリソースの消費を避けます。これは、GoプログラムがSolarisのシステムライブラリ(例: libc.so, libsocket.so など)の関数を呼び出す際に利用されます。

関連リンク

参考にした情報源リンク