[インデックス 18635] ファイルの概要
このコミットは、Go言語のランタイムとネットワークパッケージにSolarisオペレーティングシステム(GOOS=solaris
)のサポートを追加するものです。これにより、GoプログラムがSolaris環境でネイティブに動作し、ネットワーク操作を効率的に実行できるようになります。
コミット
commit 50df1364838445164d29c1e16b7077437b04b537
Author: Aram Hăvărneanu <aram@mgk.ro>
Date: Mon Feb 24 22:31:01 2014 -0500
runtime, net: add support for GOOS=solaris
LGTM=dave, rsc
R=golang-codereviews, minux.ma, mikioh.mikioh, dave, iant, rsc
CC=golang-codereviews
https://golang.org/cl/36030043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/50df1364838445164d29c1e16b7077437b04b537
元コミット内容
このコミットの元のメッセージは以下の通りです。
runtime, net: add support for GOOS=solaris
これは、GoのランタイムとネットワークライブラリがSolaris OSをターゲットとしてビルドおよび実行できるようになることを簡潔に示しています。
変更の背景
Go言語は、その設計思想としてクロスプラットフォーム対応を重視しています。GOOS
環境変数によってターゲットOSを指定し、特定のOS向けにコンパイルできる仕組みが提供されています。このコミットが作成された2014年当時、GoはLinux、macOS (Darwin)、FreeBSD、Windowsなど主要なOSをサポートしていましたが、Solarisはまだ公式サポートの対象外でした。
Solarisは、特にエンタープライズ環境や高性能コンピューティング分野で利用されてきたUNIX系のオペレーティングシステムです。GoプログラムをSolaris上でネイティブに実行できるようにすることは、Goの適用範囲を広げ、これらの環境でGoを採用したい開発者や企業にとって重要な意味を持ちます。
この変更の主な動機は、GoのネットワークスタックとランタイムがSolarisのシステムコールやI/O多重化メカニズム(特にSolaris Port I/O)と適切に連携できるようにすることでした。これにより、Goの非同期I/OモデルがSolaris上でも効率的に機能するようになります。
前提知識の解説
Goのクロスコンパイルとビルドタグ (+build
)
Go言語は、GOOS
(ターゲットOS)とGOARCH
(ターゲットアーキテクチャ)環境変数を設定することで、異なるプラットフォーム向けのバイナリを簡単に生成できるクロスコンパイル機能を備えています。
Goのソースコードでは、ファイルの先頭に+build
タグを記述することで、そのファイルがどのOSやアーキテクチャでコンパイルされるかを制御できます。例えば、// +build linux darwin
と書かれたファイルはLinuxとmacOSでのみコンパイルされ、// +build !windows
と書かれたファイルはWindows以外でコンパイルされます。このコミットでは、多くのファイルに+build solaris
が追加されており、Solaris固有のコードや、Solarisでも共通して利用できるコードが有効化されています。
Goのネットワークスタックと非同期I/O (netpoll
)
Goのランタイムは、効率的な非同期I/Oを実現するために「ネットワークポーラー(netpoll)」と呼ばれるメカニズムを使用しています。これは、OSが提供するI/O多重化API(Linuxのepoll、macOS/FreeBSDのkqueue、WindowsのIOCPなど)を抽象化し、Goのゴルーチンと連携させることで、多数のネットワーク接続をノンブロッキングで処理できるようにします。
Goのネットワークポーラーは、ファイルディスクリプタ(ソケット)が読み書き可能になったときに、そのディスクリプタを待機しているゴルーチンをスケジューラに通知し、実行可能状態にする役割を担います。これにより、GoプログラムはブロッキングI/O呼び出しを行うことなく、効率的にネットワーク通信を行うことができます。
Solaris Port I/O (Event Ports)
Solarisは、高性能なI/O多重化メカニズムとして「Port I/O」(またはEvent Ports)を提供しています。これは、LinuxのepollやFreeBSDのkqueueに似ていますが、異なるAPIを持っています。
port_create()
: イベントポートを作成します。port_associate()
: ファイルディスクリプタ(ソケットなど)をイベントポートに関連付け、監視したいイベント(読み込み可能、書き込み可能など)を指定します。port_dissociate()
: ファイルディスクリプタとイベントポートの関連付けを解除します。port_getn()
: イベントポートで発生したイベントを取得します。これはブロッキング呼び出しであり、イベントが発生するまで待機できます。
GoのランタイムがSolaris上で効率的に動作するためには、このPort I/OメカニズムをGoのnetpoll
に統合する必要があります。
ソケットオプション (setsockopt
)
ネットワークプログラミングにおいて、ソケットの挙動を制御するために様々なオプションを設定できます。これは通常、setsockopt
システムコールを通じて行われます。OSによって利用可能なオプションやその値が異なるため、Solaris固有のソケットオプション設定が必要になる場合があります。
例えば、SO_REUSEADDR
は、ソケットを閉じた後すぐに同じアドレスとポートを再利用できるようにするオプションで、サーバーアプリケーションでよく使われます。IPV6_V6ONLY
は、IPv6ソケットがIPv4接続も受け入れるかどうかを制御するオプションです。
技術的詳細
このコミットは、Goのランタイムとnet
パッケージにSolarisサポートを導入するために、以下の主要な技術的変更を行っています。
-
ビルドタグの拡張:
src/pkg/net
内の多くのGoソースファイル(例:cgo_bsd.go
,cgo_unix.go
,dnsclient_unix.go
,fd_poll_runtime.go
など)の+build
タグにsolaris
が追加されています。これにより、これらのファイルがSolaris向けビルドに含まれるようになります。src/pkg/net/interface_stub.go
やsrc/pkg/net/sendfile_stub.go
など、特定のOSでスタブ実装が使われるファイルにもsolaris
が追加されています。これは、Solarisでこれらの機能がまだ完全にサポートされていないか、異なる実装が必要であることを示唆しています。
-
Solaris固有のネットワークコードの追加:
src/pkg/net/sock_solaris.go
が新規追加されています。このファイルには、Solaris固有のソケット関連のヘルパー関数(例:maxListenerBacklog()
)が定義される可能性があります。現在の実装ではsyscall.SOMAXCONN
を返しており、これはSolarisのシステム定義の最大バックログ値を利用することを示しています。src/pkg/net/sockopt_solaris.go
が新規追加されています。このファイルは、Solaris環境でのソケットオプションのデフォルト設定を扱います。setDefaultSockopts
、setDefaultListenerSockopts
、setDefaultMulticastSockopts
といった関数が定義されており、IPV6_V6ONLY
、SO_BROADCAST
、SO_REUSEADDR
などのオプションを設定しています。これは、Goのネットワーク接続がSolaris上で期待通りに動作するための重要な設定です。src/pkg/net/tcpsockopt_solaris.go
が新規追加されています。このファイルは、Solaris固有のTCPソケットオプション、特にSO_KEEPALIVE
の設定を扱います。setKeepAlivePeriod
関数が定義されており、Goのtime.Duration
をSolarisカーネルが期待する秒数に変換してsetsockopt
を呼び出しています。
-
Solaris向けネットワークポーラーの実装 (
netpoll_solaris.c
):- 最も重要な変更点の一つは、
src/pkg/runtime/netpoll_solaris.c
の新規追加です。このCファイルは、GoランタイムのネットワークポーラーのSolaris固有の実装を提供します。 - Port I/Oの利用: このファイルは、SolarisのPort I/O API(
port_create
,port_associate
,port_dissociate
,port_getn
)をGoランタイムから呼び出すためのラッパー関数を定義しています。#pragma dynimport
ディレクティブは、これらの関数が動的にリンクされることを示しています。 runtime·netpollinit()
: イベントポートを作成し、portfd
に格納します。runtime·netpollopen()
: ファイルディスクリプタ(ソケット)をイベントポートに関連付け、POLLIN
(読み込み)とPOLLOUT
(書き込み)イベントを監視対象として設定します。PollDesc
構造体のuser
フィールドにイベントマスクを保存しています。runtime·netpollclose()
: ファイルディスクリプタとイベントポートの関連付けを解除します。runtime·netpollupdate()
: 監視するイベントを更新します。これは、Goのnetpoll
がレベルトリガー型I/O(Solaris Port I/Oはレベルトリガー型)をエッジトリガー型のように扱うために重要です。イベントが発生した後、そのイベントを一時的に監視対象から外し、処理が完了したら再度監視対象に戻すことで、イベントの重複処理を防ぎます。runtime·netpollarm()
:PollDesc
のイベントマスクを更新し、port_associate
を呼び出してイベントポートに再関連付けします。runtime·netpoll()
: イベントポートからイベントを取得し、準備ができたゴルーチンを返します。port_getn
を呼び出し、発生したイベントに基づいてPollDesc
を更新し、関連するゴルーチンをruntime·netpollready
を通じて実行可能状態にします。
- 最も重要な変更点の一つは、
-
Goランタイムのポーラーインターフェースの変更:
src/pkg/runtime/netpoll.goc
が変更され、PollDesc
構造体にvoid* user
フィールドが追加されました。これは、OS固有のポーラー実装が追加のデータをPollDesc
に関連付けるために使用できます。Solarisの実装では、監視するイベントマスクをこのuser
フィールドに保存しています。runtime·netpollarm
関数のシグネチャがuintptr fd, int32 mode
からPollDesc* pd, int32 mode
に変更されました。これにより、ポーラー実装がファイルディスクリプタだけでなく、PollDesc
全体にアクセスできるようになり、より柔軟な処理が可能になります。これは、SolarisのPort I/Oがファイルディスクリプタだけでなく、関連付けられたユーザーデータ(PollDesc
へのポインタ)もイベントと共に返すため、その情報を活用するためです。runtime·netpolluser
関数が追加され、PollDesc
のuser
フィールドへのポインタを返します。
-
テストのスキップ:
src/pkg/net/multicast_test.go
とsrc/pkg/net/tcp_test.go
では、Solaris上で特定のテストをスキップするロジックが追加されています。これは、マルチキャストやTCPの同時Acceptに関する機能がSolaris上でまだ完全に動作しないか、既知の問題があることを示しています(例: "see issue 7399", "see issue 7400")。これは、新しいOSサポートを追加する際の一般的なアプローチであり、段階的に機能が安定していく過程を示しています。
-
その他:
src/pkg/net/dial_test.go
のTestSelfConnect
で、Solarisが他の非Linuxシステムと同様にローカルホストへの接続確立に時間がかかるケースとして追加されています。src/pkg/net/port_unix.go
で、Solarisの一部のディストリビューションで/etc/services
が完全でない場合のために、http
ポートの最小限のマッピングがservices
マップに初期値として追加されています。src/pkg/runtime/defs_solaris_amd64.go
など、Solaris関連のファイルで著作権年が2013年から2014年に更新されています。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、Solaris固有のネットワークポーラーの実装と、それに伴うGoランタイムのポーラーインターフェースの調整です。
-
src/pkg/runtime/netpoll_solaris.c
の新規追加:- このファイルは、SolarisのPort I/O API (
port_create
,port_associate
,port_dissociate
,port_getn
) を利用して、Goの非同期I/OをSolaris上で実現します。 - 特に
runtime·netpoll()
関数は、port_getn
を呼び出してイベントを待ち受け、発生したイベントに基づいてPollDesc
を更新し、関連するゴルーチンをスケジューリングします。 runtime·netpollupdate()
関数は、レベルトリガー型のPort I/OをGoのポーラーが期待するエッジトリガー型のように扱うためのロジックを含んでいます。
- このファイルは、SolarisのPort I/O API (
-
src/pkg/runtime/netpoll.goc
の変更:PollDesc
構造体にvoid* user;
フィールドが追加されました。runtime·netpollarm
関数のシグネチャが変更され、PollDesc* pd
を受け取るようになりました。runtime·netpolluser
関数が追加され、PollDesc
のuser
フィールドへのポインタを返します。
-
src/pkg/net/sockopt_solaris.go
およびsrc/pkg/net/tcpsockopt_solaris.go
の新規追加:- これらのファイルは、Solaris環境でのソケットおよびTCPソケットのデフォルトオプション設定を定義し、Goのネットワーク接続がSolaris上で適切に動作するようにします。
コアとなるコードの解説
src/pkg/runtime/netpoll_solaris.c
このファイルは、SolarisのPort I/OをGoのnetpoll
に統合する中心的な役割を担います。
-
#pragma dynimport
:#pragma dynimport libc·fcntl fcntl "libc.so" #pragma dynimport libc·port_create port_create "libc.so" // ...
これらの行は、
libc.so
ライブラリからSolarisのシステムコール(fcntl
,port_create
など)を動的にインポートすることをGoのツールチェインに指示します。これにより、GoランタイムはこれらのC関数を直接呼び出すことができます。 -
runtime·netpollinit()
:void runtime·netpollinit(void) { if((portfd = runtime·port_create()) >= 0) { runtime·fcntl(portfd, F_SETFD, FD_CLOEXEC); return; } // ... エラーハンドリング }
Goランタイムの初期化時に一度だけ呼び出され、
port_create()
システムコールを使って新しいイベントポートを作成します。作成されたポートのファイルディスクリプタはportfd
に保存されます。F_SETFD
とFD_CLOEXEC
は、このファイルディスクリプタがexec
システムコールで子プロセスに引き継がれないように設定します。 -
runtime·netpollopen(uintptr fd, PollDesc *pd)
:int32 runtime·netpollopen(uintptr fd, PollDesc *pd) { uint32 events = POLLIN | POLLOUT; *runtime·netpolluser(pd) = (void*)events; // PollDescのuserフィールドにイベントマスクを保存 return runtime·port_associate(portfd, PORT_SOURCE_FD, fd, events, (uintptr)pd); }
新しいファイルディスクリプタ(ソケット)がオープンされたときに呼び出されます。
port_associate()
を使って、指定されたファイルディスクリプタfd
をportfd
(イベントポート)に関連付けます。監視対象のイベントとしてPOLLIN
(読み込み可能)とPOLLOUT
(書き込み可能)を設定し、PollDesc
構造体へのポインタpd
をユーザーデータとして渡します。このpd
は、イベント発生時にport_getn
によって返され、どのGoのPollDesc
がイベントに関連しているかを特定するために使われます。 -
runtime·netpollupdate(PollDesc* pd, uint32 set, uint32 clear)
:void runtime·netpollupdate(PollDesc* pd, uint32 set, uint32 clear) { uint32 *ep, old, events; uintptr fd = runtime·netpollfd(pd); ep = (uint32*)runtime·netpolluser(pd); // PollDescのuserフィールド(イベントマスク)へのポインタ do { old = *ep; events = (old & ~clear) | set; // 新しいイベントマスクを計算 if(old == events) return; if(events && runtime·port_associate(portfd, PORT_SOURCE_FD, fd, events, (uintptr)pd) != 0) { // ... エラーハンドリング } } while(runtime·cas(ep, old, events) != events); // CASでアトミックに更新 }
PollDesc
に関連付けられたイベントの監視状態を更新します。set
で指定されたイベントを追加し、clear
で指定されたイベントを削除します。Solaris Port I/Oはレベルトリガー型であるため、イベントが発生すると、そのイベントは明示的に監視対象から外されるまで繰り返し報告されます。Goのnetpoll
はエッジトリガー型のように動作することを期待するため、イベント処理後にnetpollupdate
を呼び出して、処理済みのイベントを一時的に監視対象から外す(または監視対象のイベントマスクを調整する)ことで、イベントの重複報告を防ぎます。runtime·cas
(Compare-And-Swap)は、複数のゴルーチンが同時にPollDesc
を更新しようとした場合でも、アトミックに更新を保証します。 -
runtime·netpoll(bool block)
:G* runtime·netpoll(bool block) { // ... if(!block) { zero.tv_sec = 0; zero.tv_nsec = 0; wait = &zero; // ノンブロッキングモードの場合、タイムアウトを0に設定 } // ... if(runtime·port_getn(portfd, events, nelem(events), &n, wait) < 0) { // ... エラーハンドリング } // ... for(i = 0; i < n; i++) { ev = &events[i]; // ... if((pd = (PollDesc *)ev->portev_user) == nil) // イベントからPollDescを取得 continue; // ... if(ev->portev_events & (POLLIN|POLLHUP|POLLERR)) mode += 'r'; // 読み込みイベント if(ev->portev_events & (POLLOUT|POLLHUP|POLLERR)) mode += 'w'; // 書き込みイベント // ... runtime·netpollupdate(pd, 0, ev->portev_events & (POLLIN|POLLOUT)); // 処理済みのイベントを監視対象から一時的に外す if(mode) runtime·netpollready(&gp, pd, mode); // 準備ができたゴルーチンをキューに入れる } // ... return gp; // 準備ができたゴルーチンのリストを返す }
Goスケジューラから呼び出され、ネットワークイベントをポーリングします。
block
がtrue
の場合、イベントが発生するまでブロックします。port_getn()
システムコールを呼び出して、イベントポートから発生したイベントを取得します。取得したイベント(PortEvent
)には、関連付けられたユーザーデータ(PollDesc
へのポインタ)が含まれており、これを使ってどのPollDesc
が準備できたかを特定します。イベントの種類(読み込み、書き込みなど)に応じてmode
を設定し、runtime·netpollupdate
を呼び出して、処理済みのイベントを監視対象から一時的に外します。最後に、runtime·netpollready
を呼び出して、準備ができたゴルーチンをGoスケジューラに通知し、実行可能状態にします。
src/pkg/runtime/netpoll.goc
-
PollDesc
構造体の変更:struct PollDesc { // ... 既存のフィールド void* user; // user settable cookie };
user
フィールドの追加は、OS固有のポーラー実装がPollDesc
に関連する追加の情報を格納できるようにするための汎用的な拡張です。Solarisのnetpoll_solaris.c
では、このフィールドに監視対象のイベントマスクを保存しています。 -
runtime·netpollarm
のシグネチャ変更:void runtime·netpollarm(PollDesc* pd, int32 mode)
以前はファイルディスクリプタ
fd
を直接受け取っていましたが、PollDesc* pd
を受け取るように変更されました。これにより、ポーラー実装はPollDesc
内のすべての情報(fd
を含む)にアクセスできるようになり、特にSolarisのようにport_associate
がユーザーデータを受け取る場合に、そのデータをPollDesc
から直接取得して渡すことが可能になります。
src/pkg/net/sockopt_solaris.go
package net
import (
"os"
"syscall"
)
func setDefaultSockopts(s, family, sotype int, ipv6only bool) error {
if family == syscall.AF_INET6 && sotype != syscall.SOCK_RAW {
// Allow both IP versions even if the OS default
// is otherwise. Note that some operating systems
// never admit this option.
syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, boolint(ipv6only))
}
// Allow broadcast.
return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1))
}
func setDefaultListenerSockopts(s int) error {
// Allow reuse of recently-used addresses.
return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1))
}
func setDefaultMulticastSockopts(s int) error {
// Allow multicast UDP and raw IP datagram sockets to listen
// concurrently across multiple listeners.
return os.NewSyscallError("setsockopt", syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1))
}
このファイルは、Solaris環境でソケットが作成された際に適用されるデフォルトのソケットオプションを定義しています。
setDefaultSockopts
: IPv6ソケットでIPV6_V6ONLY
オプションを設定し、IPv4とIPv6の両方の接続を受け入れるようにします。また、ブロードキャストを許可するためにSO_BROADCAST
オプションを設定します。setDefaultListenerSockopts
: リスナーソケットでSO_REUSEADDR
オプションを設定し、ソケットを閉じた後すぐに同じアドレスとポートを再利用できるようにします。これは、サーバーの再起動時などにポートがTIME_WAIT状態になるのを防ぎ、迅速な再起動を可能にします。setDefaultMulticastSockopts
: マルチキャストソケットでSO_REUSEADDR
オプションを設定し、複数のリスナーが同時にマルチキャストグループをリッスンできるようにします。
これらの関数は、Goのnet
パッケージがソケットを作成する際に呼び出され、Solaris環境でのネットワーク動作の互換性と効率性を確保します。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
- Goの
net
パッケージ: https://pkg.go.dev/net - Goの
syscall
パッケージ: https://pkg.go.dev/syscall - Solaris Port I/O (Event Ports) のドキュメント (例: Oracle Solaris 11.4 man pages):
port_create(3C)
,port_associate(3C)
,port_getn(3C)
参考にした情報源リンク
- Goのコミット履歴: https://github.com/golang/go/commits/master
- Goのコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージに記載されている
https://golang.org/cl/36030043
は、このGerritの変更リストへのリンクです。) - Solaris Port I/Oに関する情報 (例: Oracle Solaris Documentation Library)
- Goのネットワークポーラーに関する技術記事やGoのソースコード解析。
- Goのビルドタグに関する公式ドキュメント: https://pkg.go.dev/cmd/go#hdr-Build_constraints
- Goの
netpoll
に関する議論や実装の詳細(Goのソースコード自体が最も正確な情報源です)。 - UNIXネットワークプログラミングに関する一般的な知識(ソケット、
setsockopt
など)。 - GoのIssueトラッカー (例: issue 7399, issue 7400) - これらのIssueは、Solarisサポートの初期段階で発見された特定のバグや未実装の機能に関する情報を提供します。
[インデックス 18635] ファイルの概要
このコミットは、Go言語のランタイムとネットワークパッケージにSolarisオペレーティングシステム(GOOS=solaris
)のサポートを追加するものです。これにより、GoプログラムがSolaris環境でネイティブに動作し、ネットワーク操作を効率的に実行できるようになります。
コミット
commit 50df1364838445164d29c1e16b7077437b04b537
Author: Aram Hăvărneanu <aram@mgk.ro>
Date: Mon Feb 24 22:31:01 2014 -0500
runtime, net: add support for GOOS=solaris
LGTM=dave, rsc
R=golang-codereviews, minux.ma, mikioh.mikioh, dave, iant, rsc
CC=golang-codereviews
https://golang.org/cl/36030043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/50df1364838445164d29c1e16b7077437b04b537
元コミット内容
このコミットの元のメッセージは以下の通りです。
runtime, net: add support for GOOS=solaris
これは、GoのランタイムとネットワークライブラリがSolaris OSをターゲットとしてビルドおよび実行できるようになることを簡潔に示しています。
変更の背景
Go言語は、その設計思想としてクロスプラットフォーム対応を重視しています。GOOS
環境変数によってターゲットOSを指定し、特定のOS向けにコンパイルできる仕組みが提供されています。このコミットが作成された2014年当時、GoはLinux、macOS (Darwin)、FreeBSD、Windowsなど主要なOSをサポートしていましたが、Solarisはまだ公式サポートの対象外でした。
Solarisは、特にエンタープライズ環境や高性能コンピューティング分野で利用されてきたUNIX系のオペレーティングシステムです。GoプログラムをSolaris上でネイティブに実行できるようにすることは、Goの適用範囲を広げ、これらの環境でGoを採用したい開発者や企業にとって重要な意味を持ちます。
この変更の主な動機は、GoのネットワークスタックとランタイムがSolarisのシステムコールやI/O多重化メカニズム(特にSolaris Port I/O)と適切に連携できるようにすることでした。これにより、Goの非同期I/OモデルがSolaris上でも効率的に機能するようになります。
前提知識の解説
Goのクロスコンパイルとビルドタグ (+build
)
Go言語は、GOOS
(ターゲットOS)とGOARCH
(ターゲットアーキテクチャ)環境変数を設定することで、異なるプラットフォーム向けのバイナリを簡単に生成できるクロスコンパイル機能を備えています。
Goのソースコードでは、ファイルの先頭に+build
タグを記述することで、そのファイルがどのOSやアーキテクチャでコンパイルされるかを制御できます。例えば、// +build linux darwin
と書かれたファイルはLinuxとmacOSでのみコンパイルされ、// +build !windows
と書かれたファイルはWindows以外でコンパイルされます。このコミットでは、多くのファイルに+build solaris
が追加されており、Solaris固有のコードや、Solarisでも共通して利用できるコードが有効化されています。
Goのネットワークスタックと非同期I/O (netpoll
)
Goのランタイムは、効率的な非同期I/Oを実現するために「ネットワークポーラー(netpoll)」と呼ばれるメカニズムを使用しています。これは、OSが提供するI/O多重化API(Linuxのepoll、macOS/FreeBSDのkqueue、WindowsのIOCPなど)を抽象化し、多数のネットワーク接続をノンブロッキングで処理できるようにします。
Goのネットワークポーラーは、ファイルディスクリプタ(ソケット)が読み書き可能になったときに、そのディスクリプタを待機しているゴルーチンをスケジューラに通知し、実行可能状態にする役割を担います。これにより、GoプログラムはブロッキングI/O呼び出しを行うことなく、効率的にネットワーク通信を行うことができます。
Solaris Port I/O (Event Ports)
Solarisは、高性能なI/O多重化メカニズムとして「Port I/O」(またはEvent Ports)を提供しています。これは、LinuxのepollやFreeBSDのkqueueに似ていますが、異なるAPIを持っています。
port_create()
: イベントポートを作成します。port_associate()
: ファイルディスクリプタ(ソケットなど)をイベントポートに関連付け、監視したいイベント(読み込み可能、書き込み可能など)を指定します。port_dissociate()
: ファイルディスクリプタとイベントポートの関連付けを解除します。port_getn()
: イベントポートで発生したイベントを取得します。これはブロッキング呼び出しであり、イベントが発生するまで待機できます。
GoのランタイムがSolaris上で効率的に動作するためには、このPort I/OメカニズムをGoのnetpoll
に統合する必要があります。
ソケットオプション (setsockopt
)
ネットワークプログラミングにおいて、ソケットの挙動を制御するために様々なオプションを設定できます。これは通常、setsockopt
システムコールを通じて行われます。OSによって利用可能なオプションやその値が異なるため、Solaris固有のソケットオプション設定が必要になる場合があります。
例えば、SO_REUSEADDR
は、ソケットを閉じた後すぐに同じアドレスとポートを再利用できるようにするオプションで、サーバーアプリケーションでよく使われます。IPV6_V6ONLY
は、IPv6ソケットがIPv4接続も受け入れるかどうかを制御するオプションです。
技術的詳細
このコミットは、Goのランタイムとnet
パッケージにSolarisサポートを導入するために、以下の主要な技術的変更を行っています。
-
ビルドタグの拡張:
src/pkg/net
内の多くのGoソースファイル(例:cgo_bsd.go
,cgo_unix.go
,dnsclient_unix.go
,fd_poll_runtime.go
など)の+build
タグにsolaris
が追加されています。これにより、これらのファイルがSolaris向けビルドに含まれるようになります。src/pkg/net/interface_stub.go
やsrc/pkg/net/sendfile_stub.go
など、特定のOSでスタブ実装が使われるファイルにもsolaris
が追加されています。これは、Solarisでこれらの機能がまだ完全にサポートされていないか、異なる実装が必要であることを示唆しています。
-
Solaris固有のネットワークコードの追加:
src/pkg/net/sock_solaris.go
が新規追加されています。このファイルには、Solaris固有のソケット関連のヘルパー関数(例:maxListenerBacklog()
)が定義される可能性があります。現在の実装ではsyscall.SOMAXCONN
を返しており、これはSolarisのシステム定義の最大バックログ値を利用することを示しています。src/pkg/net/sockopt_solaris.go
が新規追加されています。このファイルは、Solaris環境でのソケットオプションのデフォルト設定を扱います。setDefaultSockopts
、setDefaultListenerSockopts
、setDefaultMulticastSockopts
といった関数が定義されており、IPV6_V6ONLY
、SO_BROADCAST
、SO_REUSEADDR
などのオプションを設定しています。これは、Goのネットワーク接続がSolaris上で期待通りに動作するための重要な設定です。src/pkg/net/tcpsockopt_solaris.go
が新規追加されています。このファイルは、Solaris固有のTCPソケットオプション、特にSO_KEEPALIVE
の設定を扱います。setKeepAlivePeriod
関数が定義されており、Goのtime.Duration
をSolarisカーネルが期待する秒数に変換してsetsockopt
を呼び出しています。
-
Solaris向けネットワークポーラーの実装 (
netpoll_solaris.c
):- 最も重要な変更点の一つは、
src/pkg/runtime/netpoll_solaris.c
の新規追加です。このCファイルは、GoランタイムのネットワークポーラーのSolaris固有の実装を提供します。 - Port I/Oの利用: このファイルは、SolarisのPort I/O API(
port_create
,port_associate
,port_dissociate
,port_getn
)をGoランタイムから呼び出すためのラッパー関数を定義しています。#pragma dynimport
ディレクティブは、これらの関数が動的にリンクされることを示しています。 runtime·netpollinit()
: イベントポートを作成し、portfd
に格納します。runtime·netpollopen()
: ファイルディスクリプタ(ソケット)をイベントポートに関連付け、POLLIN
(読み込み)とPOLLOUT
(書き込み)イベントを監視対象として設定します。PollDesc
構造体のuser
フィールドにイベントマスクを保存しています。runtime·netpollclose()
: ファイルディスクリプタとイベントポートの関連付けを解除します。runtime·netpollupdate()
: 監視するイベントを更新します。これは、Goのnetpoll
がレベルトリガー型I/O(Solaris Port I/Oはレベルトリガー型)をエッジトリガー型のように扱うために重要です。イベントが発生した後、そのイベントを一時的に監視対象から外し、処理が完了したら再度監視対象に戻すことで、イベントの重複処理を防ぎます。runtime·netpollarm()
:PollDesc
のイベントマスクを更新し、port_associate
を呼び出してイベントポートに再関連付けします。runtime·netpoll()
: イベントポートからイベントを取得し、準備ができたゴルーチンを返します。port_getn
を呼び出し、発生したイベントに基づいてPollDesc
を更新し、関連するゴルーチンをruntime·netpollready
を通じて実行可能状態にします。
- 最も重要な変更点の一つは、
-
Goランタイムのポーラーインターフェースの変更:
src/pkg/runtime/netpoll.goc
が変更され、PollDesc
構造体にvoid* user
フィールドが追加されました。これは、OS固有のポーラー実装が追加のデータをPollDesc
に関連付けるために使用できます。Solarisの実装では、監視するイベントマスクをこのuser
フィールドに保存しています。runtime·netpollarm
関数のシグネチャがuintptr fd, int32 mode
からPollDesc* pd, int32 mode
に変更されました。これにより、ポーラー実装がファイルディスクリプタだけでなく、PollDesc
全体にアクセスできるようになり、より柔軟な処理が可能になります。これは、SolarisのPort I/Oがファイルディスクリプタだけでなく、関連付けられたユーザーデータ(PollDesc
へのポインタ)もイベントと共に返すため、その情報を活用するためです。runtime·netpolluser
関数が追加され、PollDesc
のuser
フィールドへのポインタを返します。
-
テストのスキップ:
src/pkg/net/multicast_test.go
とsrc/pkg/net/tcp_test.go
では、Solaris上で特定のテストをスキップするロジックが追加されています。これは、マルチキャストやTCPの同時Acceptに関する機能がSolaris上でまだ完全に動作しないか、既知の問題があることを示しています(例: "see issue 7399", "see issue 7400")。これは、新しいOSサポートを追加する際の一般的なアプローチであり、段階的に機能が安定していく過程を示しています。
-
その他:
src/pkg/net/dial_test.go
のTestSelfConnect
で、Solarisが他の非Linuxシステムと同様にローカルホストへの接続確立に時間がかかるケースとして追加されています。src/pkg/net/port_unix.go
で、Solarisの一部のディストリビューションで/etc/services
が完全でない場合のために、http
ポートの最小限のマッピングがservices
マップに初期値として追加されています。src/pkg/runtime/defs_solaris_amd64.go
など、Solaris関連のファイルで著作権年が2013年から2014年に更新されています。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、Solaris固有のネットワークポーラーの実装と、それに伴うGoランタイムのポーラーインターフェースの調整です。
-
src/pkg/runtime/netpoll_solaris.c
の新規追加:- このファイルは、SolarisのPort I/O API (
port_create
,port_associate
,port_dissociate
,port_getn
) を利用して、Goの非同期I/OをSolaris上で実現します。 - 特に
runtime·netpoll()
関数は、port_getn
を呼び出してイベントを待ち受け、発生したイベントに基づいてPollDesc
を更新し、関連するゴルーチンをスケジューリングします。 runtime·netpollupdate()
関数は、レベルトリガー型のPort I/OをGoのポーラーが期待するエッジトリガー型のように扱うためのロジックを含んでいます。
- このファイルは、SolarisのPort I/O API (
-
src/pkg/runtime/netpoll.goc
の変更:PollDesc
構造体にvoid* user;
フィールドが追加されました。runtime·netpollarm
関数のシグネチャが変更され、PollDesc* pd
を受け取るようになりました。runtime·netpolluser
関数が追加され、PollDesc
のuser
フィールドへのポインタを返します。
-
src/pkg/net/sockopt_solaris.go
およびsrc/pkg/net/tcpsockopt_solaris.go
の新規追加:- これらのファイルは、Solaris環境でのソケットおよびTCPソケットのデフォルトオプション設定を定義し、Goのネットワーク接続がSolaris上で適切に動作するようにします。
コアとなるコードの解説
src/pkg/runtime/netpoll_solaris.c
このファイルは、SolarisのPort I/OをGoのnetpoll
に統合する中心的な役割を担います。
-
#pragma dynimport
:#pragma dynimport libc·fcntl fcntl "libc.so" #pragma dynimport libc·port_create port_create "libc.so" // ...
これらの行は、
libc.so
ライブラリからSolarisのシステムコール(fcntl
,port_create
など)を動的にインポートすることをGoのツールチェインに指示します。これにより、GoランタイムはこれらのC関数を直接呼び出すことができます。 -
runtime·netpollinit()
:void runtime·netpollinit(void) { if((portfd = runtime·port_create()) >= 0) { runtime·fcntl(portfd, F_SETFD, FD_CLOEXEC); return; } // ... エラーハンドリング }
Goランタイムの初期化時に一度だけ呼び出され、
port_create()
システムコールを使って新しいイベントポートを作成します。作成されたポートのファイルディスクリプタはportfd
に保存されます。F_SETFD
とFD_CLOEXEC
は、このファイルディスクリプタがexec
システムコールで子プロセスに引き継がれないように設定します。 -
runtime·netpollopen(uintptr fd, PollDesc *pd)
:int32 runtime·netpollopen(uintptr fd, PollDesc *pd) { uint32 events = POLLIN | POLLOUT; *runtime·netpolluser(pd) = (void*)events; // PollDescのuserフィールドにイベントマスクを保存 return runtime·port_associate(portfd, PORT_SOURCE_FD, fd, events, (uintptr)pd); }
新しいファイルディスクリプタ(ソケット)がオープンされたときに呼び出されます。
port_associate()
を使って、指定されたファイルディスクリプタfd
をportfd
(イベントポート)に関連付けます。監視対象のイベントとしてPOLLIN
(読み込み可能)とPOLLOUT
(書き込み可能)を設定し、PollDesc
構造体へのポインタpd
をユーザーデータとして渡します。このpd
は、イベント発生時にport_getn
によって返され、どのGoのPollDesc
がイベントに関連しているかを特定するために使われます。 -
runtime·netpollupdate(PollDesc* pd, uint32 set, uint32 clear)
:void runtime·netpollupdate(PollDesc* pd, uint32 set, uint32 clear) { uint32 *ep, old, events; uintptr fd = runtime·netpollfd(pd); ep = (uint32*)runtime·netpolluser(pd); // PollDescのuserフィールド(イベントマスク)へのポインタ do { old = *ep; events = (old & ~clear) | set; // 新しいイベントマスクを計算 if(old == events) return; if(events && runtime·port_associate(portfd, PORT_SOURCE_FD, fd, events, (uintptr)pd) != 0) { // ... エラーハンドリング } } while(runtime·cas(ep, old, events) != events); // CASでアトミックに更新 }
PollDesc
に関連付けられたイベントの監視状態を更新します。set
で指定されたイベントを追加し、clear
で指定されたイベントを削除します。Solaris Port I/Oはレベルトリガー型であるため、イベントが発生すると、そのイベントは明示的に監視対象から外されるまで繰り返し報告されます。Goのnetpoll
はエッジトリガー型のように動作することを期待するため、イベント処理後にnetpollupdate
を呼び出して、処理済みのイベントを一時的に監視対象から外す(または監視対象のイベントマスクを調整する)ことで、イベントの重複報告を防ぎます。runtime·cas
(Compare-And-Swap)は、複数のゴルーチンが同時にPollDesc
を更新しようとした場合でも、アトミックに更新を保証します。 -
runtime·netpoll(bool block)
:G* runtime·netpoll(bool block) { // ... if(!block) { zero.tv_sec = 0; zero.tv_nsec = 0; wait = &zero; // ノンブロッキングモードの場合、タイムアウトを0に設定 } // ... if(runtime·port_getn(portfd, events, nelem(events), &n, wait) < 0) { // ... エラーハンドリング } // ... for(i = 0; i < n; i++) { ev = &events[i]; // ... if((pd = (PollDesc *)ev->portev_user) == nil) // イベントからPollDescを取得 continue; // ... if(ev->portev_events & (POLLIN|POLLHUP|POLLERR)) mode += 'r'; // 読み込みイベント if(ev->portev_events & (POLLOUT|POLLHUP|POLLERR)) mode += 'w'; // 書き込みイベント // ... runtime·netpollupdate(pd, 0, ev->portev_events & (POLLIN|POLLOUT)); // 処理済みのイベントを監視対象から一時的に外す if(mode) runtime·netpollready(&gp, pd, mode); // 準備ができたゴルーチンをキューに入れる } // ... return gp; // 準備ができたゴルーチンのリストを返す }
Goスケジューラから呼び出され、ネットワークイベントをポーリングします。
block
がtrue
の場合、イベントが発生するまでブロックします。port_getn()
システムコールを呼び出して、イベントポートから発生したイベントを取得します。取得したイベント(PortEvent
)には、関連付けられたユーザーデータ(PollDesc
へのポインタ)が含まれており、これを使ってどのPollDesc
が準備できたかを特定します。イベントの種類(読み込み、書き込みなど)に応じてmode
を設定し、runtime·netpollupdate
を呼び出して、処理済みのイベントを監視対象から一時的に外します。最後に、runtime·netpollready
を呼び出して、準備ができたゴルーチンをGoスケジューラに通知し、実行可能状態にします。
src/pkg/runtime/netpoll.goc
-
PollDesc
構造体の変更:struct PollDesc { // ... 既存のフィールド void* user; // user settable cookie };
user
フィールドの追加は、OS固有のポーラー実装がPollDesc
に関連する追加の情報を格納できるようにするための汎用的な拡張です。Solarisのnetpoll_solaris.c
では、このフィールドに監視対象のイベントマスクを保存しています。 -
runtime·netpollarm
のシグネチャ変更:void runtime·netpollarm(PollDesc* pd, int32 mode)
以前はファイルディスクリプタ
fd
を直接受け取っていましたが、PollDesc* pd
を受け取るように変更されました。これにより、ポーラー実装はPollDesc
内のすべての情報(fd
を含む)にアクセスできるようになり、特にSolarisのようにport_associate
がユーザーデータを受け取る場合に、そのデータをPollDesc
から直接取得して渡すことが可能になります。
src/pkg/net/sockopt_solaris.go
package net
import (
"os"
"syscall"
)
func setDefaultSockopts(s, family, sotype int, ipv6only bool) error {
if family == syscall.AF_INET6 && sotype != syscall.SOCK_RAW {
// Allow both IP versions even if the OS default
// is otherwise. Note that some operating systems
// never admit this option.
syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, boolint(ipv6only))
}
// Allow broadcast.
return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1))
}
func setDefaultListenerSockopts(s int) error {
// Allow reuse of recently-used addresses.
return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1))
}
func setDefaultMulticastSockopts(s int) error {
// Allow multicast UDP and raw IP datagram sockets to listen
// concurrently across multiple listeners.
return os.NewSyscallError("setsockopt", syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1))
}
このファイルは、Solaris環境でソケットが作成された際に適用されるデフォルトのソケットオプションを定義しています。
setDefaultSockopts
: IPv6ソケットでIPV6_V6ONLY
オプションを設定し、IPv4とIPv6の両方の接続を受け入れるようにします。また、ブロードキャストを許可するためにSO_BROADCAST
オプションを設定します。setDefaultListenerSockopts
: リスナーソケットでSO_REUSEADDR
オプションを設定し、ソケットを閉じた後すぐに同じアドレスとポートを再利用できるようにします。これは、サーバーの再起動時などにポートがTIME_WAIT状態になるのを防ぎ、迅速な再起動を可能にします。setDefaultMulticastSockopts
: マルチキャストソケットでSO_REUSEADDR
オプションを設定し、複数のリスナーが同時にマルチキャストグループをリッスンできるようにします。
これらの関数は、Goのnet
パッケージがソケットを作成する際に呼び出され、Solaris環境でのネットワーク動作の互換性と効率性を確保します。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
- Goの
net
パッケージ: https://pkg.go.dev/net - Goの
syscall
パッケージ: https://pkg.go.dev/syscall - Solaris Port I/O (Event Ports) のドキュメント (例: Oracle Solaris 11.4 man pages):
port_create(3C)
,port_associate(3C)
,port_getn(3C)
参考にした情報源リンク
- Goのコミット履歴: https://github.com/golang/go/commits/master
- Goのコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージに記載されている
https://golang.org/cl/36030043
は、このGerritの変更リストへのリンクです。) - Solaris Port I/Oに関する情報 (例: Oracle Solaris Documentation Library)
- Goのネットワークポーラーに関する技術記事やGoのソースコード解析。
- Goのビルドタグに関する公式ドキュメント: https://pkg.go.dev/cmd/go#hdr-Build_constraints
- Goの
netpoll
に関する議論や実装の詳細(Goのソースコード自体が最も正確な情報源です)。 - UNIXネットワークプログラミングに関する一般的な知識(ソケット、
setsockopt
など)。 - GoのIssueトラッカー (例: issue 7399, issue 7400) - これらのIssueは、Solarisサポートの初期段階で発見された特定のバグや未実装の機能に関する情報を提供します。