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

[インデックス 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サポートを導入するために、以下の主要な技術的変更を行っています。

  1. ビルドタグの拡張:

    • 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.gosrc/pkg/net/sendfile_stub.goなど、特定のOSでスタブ実装が使われるファイルにもsolarisが追加されています。これは、Solarisでこれらの機能がまだ完全にサポートされていないか、異なる実装が必要であることを示唆しています。
  2. Solaris固有のネットワークコードの追加:

    • src/pkg/net/sock_solaris.goが新規追加されています。このファイルには、Solaris固有のソケット関連のヘルパー関数(例: maxListenerBacklog())が定義される可能性があります。現在の実装ではsyscall.SOMAXCONNを返しており、これはSolarisのシステム定義の最大バックログ値を利用することを示しています。
    • src/pkg/net/sockopt_solaris.goが新規追加されています。このファイルは、Solaris環境でのソケットオプションのデフォルト設定を扱います。setDefaultSockoptssetDefaultListenerSockoptssetDefaultMulticastSockoptsといった関数が定義されており、IPV6_V6ONLYSO_BROADCASTSO_REUSEADDRなどのオプションを設定しています。これは、Goのネットワーク接続がSolaris上で期待通りに動作するための重要な設定です。
    • src/pkg/net/tcpsockopt_solaris.goが新規追加されています。このファイルは、Solaris固有のTCPソケットオプション、特にSO_KEEPALIVEの設定を扱います。setKeepAlivePeriod関数が定義されており、Goのtime.DurationをSolarisカーネルが期待する秒数に変換してsetsockoptを呼び出しています。
  3. 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を通じて実行可能状態にします。
  4. 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関数が追加され、PollDescuserフィールドへのポインタを返します。
  5. テストのスキップ:

    • src/pkg/net/multicast_test.gosrc/pkg/net/tcp_test.goでは、Solaris上で特定のテストをスキップするロジックが追加されています。これは、マルチキャストやTCPの同時Acceptに関する機能がSolaris上でまだ完全に動作しないか、既知の問題があることを示しています(例: "see issue 7399", "see issue 7400")。これは、新しいOSサポートを追加する際の一般的なアプローチであり、段階的に機能が安定していく過程を示しています。
  6. その他:

    • src/pkg/net/dial_test.goTestSelfConnectで、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ランタイムのポーラーインターフェースの調整です。

  1. 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のポーラーが期待するエッジトリガー型のように扱うためのロジックを含んでいます。
  2. src/pkg/runtime/netpoll.goc の変更:

    • PollDesc構造体にvoid* user;フィールドが追加されました。
    • runtime·netpollarm関数のシグネチャが変更され、PollDesc* pdを受け取るようになりました。
    • runtime·netpolluser関数が追加され、PollDescuserフィールドへのポインタを返します。
  3. 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_SETFDFD_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()を使って、指定されたファイルディスクリプタfdportfd(イベントポート)に関連付けます。監視対象のイベントとして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スケジューラから呼び出され、ネットワークイベントをポーリングします。blocktrueの場合、イベントが発生するまでブロックします。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://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サポートを導入するために、以下の主要な技術的変更を行っています。

  1. ビルドタグの拡張:

    • 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.gosrc/pkg/net/sendfile_stub.goなど、特定のOSでスタブ実装が使われるファイルにもsolarisが追加されています。これは、Solarisでこれらの機能がまだ完全にサポートされていないか、異なる実装が必要であることを示唆しています。
  2. Solaris固有のネットワークコードの追加:

    • src/pkg/net/sock_solaris.goが新規追加されています。このファイルには、Solaris固有のソケット関連のヘルパー関数(例: maxListenerBacklog())が定義される可能性があります。現在の実装ではsyscall.SOMAXCONNを返しており、これはSolarisのシステム定義の最大バックログ値を利用することを示しています。
    • src/pkg/net/sockopt_solaris.goが新規追加されています。このファイルは、Solaris環境でのソケットオプションのデフォルト設定を扱います。setDefaultSockoptssetDefaultListenerSockoptssetDefaultMulticastSockoptsといった関数が定義されており、IPV6_V6ONLYSO_BROADCASTSO_REUSEADDRなどのオプションを設定しています。これは、Goのネットワーク接続がSolaris上で期待通りに動作するための重要な設定です。
    • src/pkg/net/tcpsockopt_solaris.goが新規追加されています。このファイルは、Solaris固有のTCPソケットオプション、特にSO_KEEPALIVEの設定を扱います。setKeepAlivePeriod関数が定義されており、Goのtime.DurationをSolarisカーネルが期待する秒数に変換してsetsockoptを呼び出しています。
  3. 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を通じて実行可能状態にします。
  4. 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関数が追加され、PollDescuserフィールドへのポインタを返します。
  5. テストのスキップ:

    • src/pkg/net/multicast_test.gosrc/pkg/net/tcp_test.goでは、Solaris上で特定のテストをスキップするロジックが追加されています。これは、マルチキャストやTCPの同時Acceptに関する機能がSolaris上でまだ完全に動作しないか、既知の問題があることを示しています(例: "see issue 7399", "see issue 7400")。これは、新しいOSサポートを追加する際の一般的なアプローチであり、段階的に機能が安定していく過程を示しています。
  6. その他:

    • src/pkg/net/dial_test.goTestSelfConnectで、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ランタイムのポーラーインターフェースの調整です。

  1. 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のポーラーが期待するエッジトリガー型のように扱うためのロジックを含んでいます。
  2. src/pkg/runtime/netpoll.goc の変更:

    • PollDesc構造体にvoid* user;フィールドが追加されました。
    • runtime·netpollarm関数のシグネチャが変更され、PollDesc* pdを受け取るようになりました。
    • runtime·netpolluser関数が追加され、PollDescuserフィールドへのポインタを返します。
  3. 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_SETFDFD_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()を使って、指定されたファイルディスクリプタfdportfd(イベントポート)に関連付けます。監視対象のイベントとして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スケジューラから呼び出され、ネットワークイベントをポーリングします。blocktrueの場合、イベントが発生するまでブロックします。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://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サポートの初期段階で発見された特定のバグや未実装の機能に関する情報を提供します。