[インデックス 16710] ファイルの概要
このコミットは、Go言語の標準ライブラリにおけるsyscall
パッケージの変更に関するものです。syscall
パッケージは、オペレーティングシステム(OS)の低レベルな機能、特にシステムコールへのインターフェースを提供します。これにより、Goプログラムからファイル操作、ネットワーク通信、プロセス管理など、OSが提供するプリミティブな機能に直接アクセスできるようになります。
具体的には、このコミットで変更されたファイルは以下の通りです。
src/pkg/syscall/syscall_bsd.go
: BSD系のOS(FreeBSD, OpenBSDなど)向けのシステムコール定義が含まれています。src/pkg/syscall/syscall_linux.go
: Linux向けのシステムコール定義が含まれています。src/pkg/syscall/syscall_unix.go
: Unix系のOS(Linux, BSDなど)で共通して使用されるシステムコール定義が含まれています。
この変更の目的は、これらのファイル間で重複しているソケットオプション設定関数(Setsockopt
関連関数)のコードを削減し、共通のsyscall_unix.go
に集約することにあります。
コミット
commit 2a730f8b16f1345ec8c077cb9453c5b1dcbb2c33
Author: Dave Cheney <dave@cheney.net>
Date: Fri Jul 5 13:25:23 2013 +1000
syscall: reduce duplication between *bsd and linux
Part 3 of several.
* Linux has grown a SetsockoptByte.
* SetsockoptIPMreqn is handled directly by syscall_linux.go and syscall_freebsd.go.
R=golang-dev, mikioh.mikioh, r, bradfitz
CC=golang-dev
https://golang.org/cl/10775043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2a730f8b16f1345ec8c077cb9453c5b1dcbb2c33
元コミット内容
syscall: reduce duplication between *bsd and linux
Part 3 of several.
* Linux has grown a SetsockoptByte.
* SetsockoptIPMreqn is handled directly by syscall_linux.go and syscall_freebsd.go.
R=golang-dev, mikioh.mikioh, r, bradfitz
CC=golang-dev
https://golang.org/cl/10775043
変更の背景
このコミットの主な背景は、Go言語のsyscall
パッケージにおけるコードの重複を削減し、保守性を向上させることです。Goはクロスプラットフォーム対応を重視しており、異なるOS(特にUnix系のLinuxとBSD)間で共通する機能については、可能な限り共通のコードベースで管理することが望ましいとされています。
以前のsyscall
パッケージでは、ソケットオプションを設定するための多くのSetsockopt
関連関数が、Linux固有のsyscall_linux.go
とBSD固有のsyscall_bsd.go
の両方に個別に実装されていました。これらの関数は、内部的にはほとんど同じロジック(setsockopt
システムコールを呼び出す)を持っており、引数の型が異なるだけでした。このような重複は、新しいソケットオプションの追加や既存の関数の変更があった場合に、複数のファイルで同じ修正を行う必要があり、バグの温床となったり、開発効率を低下させたりする原因となります。
コミットメッセージにある「Part 3 of several」という記述から、この変更がより大規模なリファクタリング作業の一部であることがわかります。これは、Goの標準ライブラリ全体でコードの品質と一貫性を高めるための継続的な取り組みの一環として行われたものです。
特に、SetsockoptByte
がLinuxに追加されたこと、そしてSetsockoptIPMreqn
がLinuxとFreeBSDで直接扱われることが言及されています。これは、SetsockoptByte
のような基本的な型を扱う関数がLinuxでも共通化の対象となり、一方でSetsockoptIPMreqn
のようにOS間で実装の詳細が異なる可能性のある関数は、引き続きOS固有のファイルで管理されるという設計判断を示唆しています。
前提知識の解説
このコミットを理解するためには、以下の技術的な概念について知っておく必要があります。
1. Go言語のsyscall
パッケージ
Go言語のsyscall
パッケージは、OSが提供するシステムコールへの低レベルなインターフェースを提供します。Goの標準ライブラリの多くの部分(例: os
, net
パッケージ)は、このsyscall
パッケージを介してOSの機能を利用しています。syscall
パッケージは、OS固有の差異を吸収し、Goプログラムから統一された方法でシステムコールを呼び出せるように設計されています。そのため、syscall_linux.go
、syscall_bsd.go
、syscall_windows.go
などのように、OSごとに異なる実装ファイルが存在します。
2. setsockopt
システムコール
setsockopt
は、ソケットのオプションを設定するためのシステムコールです。ソケットはネットワーク通信のエンドポイントであり、その動作は様々なオプションによって制御されます。例えば、ソケットの送受信バッファサイズ、タイムアウト設定、ブロードキャストの許可、マルチキャストグループへの参加など、多岐にわたる設定が可能です。
setsockopt
システムコールの一般的なシグネチャは以下のようになります(C言語の例):
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
sockfd
: オプションを設定するソケットのファイルディスクリプタ。level
: オプションが適用されるプロトコルレベル(例:SOL_SOCKET
はソケットレベル、IPPROTO_IP
はIPレベル)。optname
: 設定するオプションの名前(例:SO_RCVTIMEO
は受信タイムアウト、IP_ADD_MEMBERSHIP
はマルチキャストグループ参加)。optval
: 設定するオプションの値へのポインタ。このポインタが指すデータの型はoptname
によって異なります。optlen
:optval
が指すデータのサイズ。
Goのsyscall
パッケージでは、このsetsockopt
システムコールをGoの関数としてラップし、よりGoらしいインターフェースで提供しています。
3. unsafe.Pointer
とuintptr
Go言語は通常、型安全性が厳格に保たれていますが、低レベルな操作を行うsyscall
パッケージなどでは、C言語のポインタ操作に相当する機能が必要になります。そこでunsafe
パッケージが提供されます。
unsafe.Pointer
: 任意の型のポインタを保持できる特殊なポインタ型です。C言語のvoid*
に似ていますが、Goのガベージコレクタが管理するメモリを指すことができます。型安全性をバイパスするため、使用には注意が必要です。uintptr
: ポインタの値を整数として表現する型です。ポインタ演算を行う際に使用されることがありますが、uintptr
自体はポインタではないため、ガベージコレクタの管理対象外です。unsafe.Pointer
とuintptr
の間で相互変換が可能です。
setsockopt
システムコールでは、optval
引数に任意のデータ型へのポインタを渡す必要があるため、Goのsyscall
パッケージではunsafe.Pointer
とuintptr
を組み合わせて、Goの構造体や変数のメモリアドレスをシステムコールに渡しています。例えば、uintptr(unsafe.Pointer(&n))
は、変数n
のアドレスをuintptr
型に変換し、システムコールに渡せるようにします。
4. ソケットオプションの種類と構造体
setsockopt
で設定されるオプションの値は、そのオプションの種類によって様々なデータ型を取ります。
int
: 多くの単純なオプション(例:SO_REUSEADDR
)は整数値で設定されます。byte
: 単一のバイト値で設定されるオプションもあります。[4]byte
(IPv4アドレス): IPv4アドレスを設定する場合に使用されます。Timeval
: タイムアウト値(秒とマイクロ秒)を設定するための構造体です。type Timeval struct { Sec int64 Usec int64 }
Linger
: ソケットのクローズ時の動作(遅延クローズ)を設定するための構造体です。type Linger struct { Onoff int32 // 0 = off, nonzero = on Linger int32 // linger time in seconds }
IPMreq
/IPv6Mreq
: IPマルチキャストグループへの参加/脱退を設定するための構造体です。type IPMreq struct { Multiaddr [4]byte // IP multicast address of group Interface [4]byte // IP address of local interface } type IPv6Mreq struct { Multiaddr [16]byte // IPv6 multicast address of group Interface uint32 // interface index }
ICMPv6Filter
: ICMPv6メッセージのフィルタリングを設定するための構造体です。string
: 文字列をオプションとして設定する場合(例: ソケット名など)。
これらの構造体は、OSのC言語の構造体に対応しており、Goのsyscall
パッケージ内で定義されています。unsafe.Sizeof
は、これらの構造体のメモリ上のサイズをバイト単位で取得するために使用されます。
技術的詳細
このコミットの核心は、syscall_bsd.go
とsyscall_linux.go
に散在していたSetsockopt
関連の関数群を、syscall_unix.go
に集約したことです。これにより、Unix系OSで共通のソケットオプション設定ロジックが一元化され、コードの重複が大幅に削減されました。
具体的な変更内容は以下の通りです。
-
共通関数の移動:
SetsockoptByte
SetsockoptInt
SetsockoptInet4Addr
SetsockoptTimeval
SetsockoptLinger
SetsockoptIPMreq
SetsockoptIPv6Mreq
SetsockoptICMPv6Filter
SetsockoptString
これらの関数は、syscall_bsd.go
とsyscall_linux.go
から削除され、syscall_unix.go
に移動されました。
-
setsockopt
ヘルパー関数の活用: これらのSetsockopt
関数は、内部的にsetsockopt
というヘルパー関数を呼び出しています。このヘルパー関数は、実際のシステムコールをラップし、unsafe.Pointer
とuintptr
を使って、Goのデータ構造をOSが期待する形式(ポインタとサイズ)に変換して渡します。 例えば、SetsockoptInt
のGoでの実装は、int
型のvalue
をint32
にキャストし、そのアドレスとサイズをsetsockopt
ヘルパー関数に渡します。// 共通化されたSetsockoptIntの例 (syscall_unix.go に移動後) func SetsockoptInt(fd, level, opt int, value int) (err error) { var n = int32(value) return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(&n)), 4) // 4はint32のサイズ }
同様に、構造体を引数に取る関数(例:
SetsockoptTimeval
,SetsockoptLinger
)では、unsafe.Sizeof
を使用して構造体のサイズを取得し、setsockopt
ヘルパー関数に渡しています。// 共通化されたSetsockoptTimevalの例 (syscall_unix.go に移動後) func SetsockoptTimeval(fd, level, opt int, tv *Timeval) (err error) { return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(tv)), unsafe.Sizeof(*tv)) }
-
例外処理:
SetsockoptIPMreqn
: コミットメッセージには、「SetsockoptIPMreqn
is handled directly bysyscall_linux.go
andsyscall_freebsd.go
.」と明記されています。これは、SetsockoptIPMreqn
(おそらくIPマルチキャスト関連の特定のオプション)については、LinuxとFreeBSDの間で実装の詳細やセマンティクスに違いがあるため、共通化の対象外とし、引き続きOS固有のファイルで管理するという判断がなされたことを意味します。このような判断は、OS間の微妙な差異を考慮し、正しい動作を保証するために重要です。
このリファクタリングにより、Goのsyscall
パッケージはよりクリーンで、理解しやすく、そして将来的なメンテナンスが容易になりました。共通のロジックは一箇所に集約され、OS固有の差異がある部分のみがそれぞれのファイルに残されるという、良い設計原則が適用されています。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は、src/pkg/syscall/syscall_bsd.go
とsrc/pkg/syscall/syscall_linux.go
から共通のSetsockopt
関数群が削除され、それらがsrc/pkg/syscall/syscall_unix.go
に移動・追加された点です。
src/pkg/syscall/syscall_bsd.go
からの削除
--- a/src/pkg/syscall/syscall_bsd.go
+++ b/src/pkg/syscall/syscall_bsd.go
@@ -352,44 +352,6 @@ func GetsockoptICMPv6Filter(fd, level, opt int) (*ICMPv6Filter, error) {
return &value, err
}
-func SetsockoptByte(fd, level, opt int, value byte) (err error) {
- var n = byte(value)
- return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(&n)), 1)
-}
-
-func SetsockoptInt(fd, level, opt int, value int) (err error) {
- var n = int32(value)
- return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(&n)), 4)
-}
-
-func SetsockoptInet4Addr(fd, level, opt int, value [4]byte) (err error) {
- return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(&value[0])), 4)
-}
-
-func SetsockoptTimeval(fd, level, opt int, tv *Timeval) (err error) {
- return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(tv)), unsafe.Sizeof(*tv))
-}
-
-func SetsockoptLinger(fd, level, opt int, l *Linger) (err error) {
- return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(l)), unsafe.Sizeof(*l))
-}
-
-func SetsockoptIPMreq(fd, level, opt int, mreq *IPMreq) (err error) {
- return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(mreq)), unsafe.Sizeof(*mreq))
-}
-
-func SetsockoptIPv6Mreq(fd, level, opt int, mreq *IPv6Mreq) (err error) {
- return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(mreq)), unsafe.Sizeof(*mreq))
-}
-
-func SetsockoptICMPv6Filter(fd, level, opt int, filter *ICMPv6Filter) error {
- return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(filter)), SizeofICMPv6Filter)
-}
-
-func SetsockoptString(fd, level, opt int, s string) (err error) {
- return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(&[]byte(s)[0])), uintptr(len(s)))
-}
-
//sys recvfrom(fd int, p []byte, flags int, from *RawSockaddrAny, fromlen *_Socklen) (n int, err error)
//sys sendto(s int, buf []byte, flags int, to uintptr, addrlen _Socklen) (err error)
//sys recvmsg(s int, msg *Msghdr, flags int) (n int, err error)
src/pkg/syscall/syscall_linux.go
からの削除
--- a/src/pkg/syscall/syscall_linux.go
+++ b/src/pkg/syscall/syscall_linux.go
@@ -485,42 +485,10 @@ func GetsockoptUcred(fd, level, opt int) (*Ucred, error) {
return &value, err
}
-func SetsockoptInt(fd, level, opt int, value int) (err error) {
- var n = int32(value)
- return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(&n)), 4)
-}
-
-func SetsockoptInet4Addr(fd, level, opt int, value [4]byte) (err error) {
- return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(&value[0])), 4)
-}
-
-func SetsockoptTimeval(fd, level, opt int, tv *Timeval) (err error) {
- return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(tv)), unsafe.Sizeof(*tv))
-}
-
-func SetsockoptLinger(fd, level, opt int, l *Linger) (err error) {
- return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(l)), unsafe.Sizeof(*l))
-}
-
-func SetsockoptIPMreq(fd, level, opt int, mreq *IPMreq) (err error) {
- return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(mreq)), unsafe.Sizeof(*mreq))
-}
-
func SetsockoptIPMreqn(fd, level, opt int, mreq *IPMreqn) (err error) {
return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(mreq)), unsafe.Sizeof(*mreq))
}
-func SetsockoptIPv6Mreq(fd, level, opt int, mreq *IPv6Mreq) (err error) {
- return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(mreq)), unsafe.Sizeof(*mreq))
-}
-
-func SetsockoptICMPv6Filter(fd, level, opt int, filter *ICMPv6Filter) error {
- return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(filter)), SizeofICMPv6Filter)
-}
-func SetsockoptString(fd, level, opt int, s string) (err error) {
- return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(&[]byte(s)[0])), uintptr(len(s)))
-}
-
func Recvmsg(fd int, p, oob []byte, flags int) (n, oobn int, recvflags int, from Sockaddr, err error) {
var msg Msghdr
var rsa RawSockaddrAny
src/pkg/syscall/syscall_unix.go
への追加
--- a/src/pkg/syscall/syscall_unix.go
+++ b/src/pkg/syscall/syscall_unix.go
@@ -230,6 +230,43 @@ func Sendto(fd int, p []byte, flags int, to Sockaddr) (err error) {
return sendto(fd, p, flags, ptr, n)
}
+func SetsockoptByte(fd, level, opt int, value byte) (err error) {
+ return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(&value)), 1)
+}
+
+func SetsockoptInt(fd, level, opt int, value int) (err error) {
+ var n = int32(value)
+ return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(&n)), 4)
+}
+
+func SetsockoptInet4Addr(fd, level, opt int, value [4]byte) (err error) {
+ return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(&value[0])), 4)
+}
+
+func SetsockoptIPMreq(fd, level, opt int, mreq *IPMreq) (err error) {
+ return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(mreq)), SizeofIPMreq)
+}
+
+func SetsockoptIPv6Mreq(fd, level, opt int, mreq *IPv6Mreq) (err error) {
+ return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(mreq)), SizeofIPv6Mreq)
+}
+
+func SetsockoptICMPv6Filter(fd, level, opt int, filter *ICMPv6Filter) error {
+ return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(filter)), SizeofICMPv6Filter)
+}
+
+func SetsockoptLinger(fd, level, opt int, l *Linger) (err error) {
+ return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(l)), SizeofLinger)
+}
+
+func SetsockoptString(fd, level, opt int, s string) (err error) {
+ return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(&[]byte(s)[0])), uintptr(len(s)))
+}
+
+func SetsockoptTimeval(fd, level, opt int, tv *Timeval) (err error) {
+ return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(tv)), unsafe.Sizeof(*tv))
+}
+
func Socket(domain, typ, proto int) (fd int, err error) {
if domain == AF_INET6 && SocketDisableIPv6 {
return -1, EAFNOSUPPORT
コアとなるコードの解説
このコミットのコアとなる変更は、Goのsyscall
パッケージにおけるソケットオプション設定関数(Setsockopt*
)の共通化です。
Goのsyscall
パッケージは、OSのシステムコールをGoの関数としてラップし、Goプログラムから低レベルなOS機能にアクセスできるようにします。しかし、OSごとにシステムコールの詳細な動作や引数の型が異なる場合があるため、GoではOS固有のファイル(例: syscall_linux.go
, syscall_bsd.go
)と、複数のOSで共通する部分をまとめるファイル(例: syscall_unix.go
)を使い分けています。
このコミット以前は、SetsockoptByte
, SetsockoptInt
, SetsockoptInet4Addr
, SetsockoptTimeval
, SetsockoptLinger
, SetsockoptIPMreq
, SetsockoptIPv6Mreq
, SetsockoptICMPv6Filter
, SetsockoptString
といったソケットオプション設定関数が、LinuxとBSDの両方のOS固有ファイルに個別に存在していました。これらの関数は、引数の型は異なるものの、内部的にはほとんど同じロジックでsetsockopt
システムコールを呼び出していました。
例えば、SetsockoptInt
関数は、int
型のvalue
を受け取り、それをint32
に変換した後、setsockopt
という内部ヘルパー関数を呼び出していました。このsetsockopt
ヘルパー関数は、fd
(ファイルディスクリプタ)、level
(プロトコルレベル)、opt
(オプション名)、uintptr(unsafe.Pointer(&n))
(オプション値へのポインタ)、4
(オプション値のサイズ)を引数として受け取ります。
このコミットでは、これらの関数がLinuxとBSDでほぼ同一の実装であったため、コードの重複を避けるために、それらをsyscall_unix.go
という共通のファイルに移動しました。これにより、Unix系のOS(LinuxやBSDなど)であれば、これらのSetsockopt
関数はsyscall_unix.go
で定義された共通の実装を使用するようになります。
この変更のメリットは以下の通りです。
- コードの重複削減: 同じロジックが複数のファイルに存在しなくなるため、コードベースがより簡潔になります。
- 保守性の向上: 将来的にこれらの
Setsockopt
関数のロジックに変更が必要になった場合、syscall_unix.go
の1箇所を修正するだけで済み、複数のファイルを修正する手間や、修正漏れによるバグのリスクが減少します。 - 一貫性の向上: 異なるOS間で同じ機能が同じコードで提供されるため、Goの
syscall
パッケージ全体の一貫性が高まります。
ただし、コミットメッセージにもあるように、SetsockoptIPMreqn
のような一部の関数は、OS間で実装の詳細が異なる可能性があるため、引き続きsyscall_linux.go
とsyscall_freebsd.go
で個別に扱われています。これは、共通化のメリットとOS固有の正確性のバランスを考慮した設計判断です。
関連リンク
- Go Change-Id:
10775043
(Goの内部変更リストシステムにおけるID)
参考にした情報源リンク
- Go言語の
syscall
パッケージに関する公式ドキュメントやソースコード setsockopt
システムコールに関するLinux manページやBSD manページ- Go言語の
unsafe
パッケージに関する公式ドキュメント - Go言語のクロスプラットフォーム開発に関する一般的な情報