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

[インデックス 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.gosyscall_bsd.gosyscall_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.Pointeruintptr

Go言語は通常、型安全性が厳格に保たれていますが、低レベルな操作を行うsyscallパッケージなどでは、C言語のポインタ操作に相当する機能が必要になります。そこでunsafeパッケージが提供されます。

  • unsafe.Pointer: 任意の型のポインタを保持できる特殊なポインタ型です。C言語のvoid*に似ていますが、Goのガベージコレクタが管理するメモリを指すことができます。型安全性をバイパスするため、使用には注意が必要です。
  • uintptr: ポインタの値を整数として表現する型です。ポインタ演算を行う際に使用されることがありますが、uintptr自体はポインタではないため、ガベージコレクタの管理対象外です。unsafe.Pointeruintptrの間で相互変換が可能です。

setsockoptシステムコールでは、optval引数に任意のデータ型へのポインタを渡す必要があるため、Goのsyscallパッケージではunsafe.Pointeruintptrを組み合わせて、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.gosyscall_linux.goに散在していたSetsockopt関連の関数群を、syscall_unix.goに集約したことです。これにより、Unix系OSで共通のソケットオプション設定ロジックが一元化され、コードの重複が大幅に削減されました。

具体的な変更内容は以下の通りです。

  1. 共通関数の移動:

    • SetsockoptByte
    • SetsockoptInt
    • SetsockoptInet4Addr
    • SetsockoptTimeval
    • SetsockoptLinger
    • SetsockoptIPMreq
    • SetsockoptIPv6Mreq
    • SetsockoptICMPv6Filter
    • SetsockoptString これらの関数は、syscall_bsd.gosyscall_linux.goから削除され、syscall_unix.goに移動されました。
  2. setsockoptヘルパー関数の活用: これらのSetsockopt関数は、内部的にsetsockoptというヘルパー関数を呼び出しています。このヘルパー関数は、実際のシステムコールをラップし、unsafe.Pointeruintptrを使って、Goのデータ構造をOSが期待する形式(ポインタとサイズ)に変換して渡します。 例えば、SetsockoptIntのGoでの実装は、int型のvalueint32にキャストし、そのアドレスとサイズを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))
    }
    
  3. 例外処理: SetsockoptIPMreqn: コミットメッセージには、「SetsockoptIPMreqn is handled directly by syscall_linux.go and syscall_freebsd.go.」と明記されています。これは、SetsockoptIPMreqn(おそらくIPマルチキャスト関連の特定のオプション)については、LinuxとFreeBSDの間で実装の詳細やセマンティクスに違いがあるため、共通化の対象外とし、引き続きOS固有のファイルで管理するという判断がなされたことを意味します。このような判断は、OS間の微妙な差異を考慮し、正しい動作を保証するために重要です。

このリファクタリングにより、Goのsyscallパッケージはよりクリーンで、理解しやすく、そして将来的なメンテナンスが容易になりました。共通のロジックは一箇所に集約され、OS固有の差異がある部分のみがそれぞれのファイルに残されるという、良い設計原則が適用されています。

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

このコミットによる主要なコード変更は、src/pkg/syscall/syscall_bsd.gosrc/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で定義された共通の実装を使用するようになります。

この変更のメリットは以下の通りです。

  1. コードの重複削減: 同じロジックが複数のファイルに存在しなくなるため、コードベースがより簡潔になります。
  2. 保守性の向上: 将来的にこれらのSetsockopt関数のロジックに変更が必要になった場合、syscall_unix.goの1箇所を修正するだけで済み、複数のファイルを修正する手間や、修正漏れによるバグのリスクが減少します。
  3. 一貫性の向上: 異なるOS間で同じ機能が同じコードで提供されるため、Goのsyscallパッケージ全体の一貫性が高まります。

ただし、コミットメッセージにもあるように、SetsockoptIPMreqnのような一部の関数は、OS間で実装の詳細が異なる可能性があるため、引き続きsyscall_linux.gosyscall_freebsd.goで個別に扱われています。これは、共通化のメリットとOS固有の正確性のバランスを考慮した設計判断です。

関連リンク

参考にした情報源リンク

  • Go言語のsyscallパッケージに関する公式ドキュメントやソースコード
  • setsockoptシステムコールに関するLinux manページやBSD manページ
  • Go言語のunsafeパッケージに関する公式ドキュメント
  • Go言語のクロスプラットフォーム開発に関する一般的な情報