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

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

このコミットは、Go言語の syscall パッケージにおけるコードの重複を削減することを目的としています。具体的には、BSD系OS (syscall_bsd.go) とLinux (syscall_linux.go) の間で共通して定義されていた型や関数を、syscall_unix.go という共通ファイルに移動することで、コードベースの保守性と一貫性を向上させています。これは、複数のOSで共通のシステムコールインターフェースを持つUnix系システムにおける、Goのシステムコール実装の整理の一環です。

コミット

  • コミットハッシュ: dd3a3cfa4939e54490d8dd0c1c121869c7eddde3
  • 作者: Dave Cheney dave@cheney.net
  • コミット日時: 2013年6月22日 11:03:40 +1000

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

https://github.com/golang/go/commit/dd3a3cfa4939e54490d8dd0c1c121869c7eddde3

元コミット内容

syscall: reduce duplication between *bsd and linux

See discussion: https://groups.google.com/forum/#!topic/golang-dev/zSmH0lQxKAs

Part 1 of several.

Move identical types and functions to syscall_unix.go.

R=rsc, mikioh.mikioh, r
CC=golang-dev
https://golang.org/cl/10392048

変更の背景

Go言語の syscall パッケージは、オペレーティングシステムが提供する低レベルなシステムコールへのインターフェースを提供します。異なるOS(例えばLinuxとBSD系OS)であっても、多くのシステムコールは共通の機能とシグネチャを持っています。しかし、Goの初期の実装では、これらの共通のシステムコールに関連する型や関数が、各OS固有のファイル(例: syscall_linux.go, syscall_bsd.go)に個別に定義されていることがありました。

このような重複は、コードの保守性を低下させ、バグの温床となる可能性があります。例えば、共通の機能に修正が必要になった場合、複数のファイルで同じ変更を適用する必要があり、いずれかのファイルで変更が漏れると不整合が生じます。

このコミットは、このようなコードの重複を解消し、共通のコードを syscall_unix.go という単一のファイルに集約することで、コードベースのDRY (Don't Repeat Yourself) 原則を適用し、保守性と可読性を向上させることを目的としています。コミットメッセージに記載されているGoogle Groupsの議論リンクは、この変更の必要性とその背景にあるコミュニティの議論を示唆しています。これは、Goのシステムコールパッケージをよりクリーンで効率的なものにするための、一連の改善の「パート1」と位置づけられています。

前提知識の解説

システムコール (System Call)

システムコールは、ユーザー空間で動作するアプリケーションが、オペレーティングシステム (OS) のカーネル空間が提供するサービスを利用するためのインターフェースです。ファイルI/O、ネットワーク通信、プロセス管理、メモリ管理など、OSの基本的な機能のほとんどはシステムコールを通じてアクセスされます。

Go言語のような高レベル言語では、通常、標準ライブラリがこれらのシステムコールを抽象化し、より使いやすいAPIを提供します。しかし、syscall パッケージは、これらの低レベルなインターフェースに直接アクセスする必要がある場合(例えば、OS固有の機能を利用する場合や、パフォーマンスが重要な場合)に利用されます。

ソケットプログラミングと Sockaddr 構造体

ネットワーク通信を行う際、アプリケーションはソケットと呼ばれるエンドポイントを作成し、これを通じてデータを送受信します。ソケットは、通信相手のアドレス(IPアドレスとポート番号)を識別するために Sockaddr と呼ばれる構造体を使用します。

Sockaddr は、OSによってその具体的な定義が異なりますが、一般的には以下のような情報を保持します。

  • アドレスファミリー (Address Family): AF_INET (IPv4), AF_INET6 (IPv6), AF_UNIX (Unixドメインソケット) など、ソケットが使用するネットワークプロトコルファミリーを示します。
  • ポート番号: ネットワークサービスを識別するための番号。
  • IPアドレス: 通信相手のIPアドレス。
  • ゾーンID (ZoneId): IPv6アドレスのスコープを識別するための情報(特にリンクローカルアドレスの場合)。
  • パス名: Unixドメインソケットの場合、ファイルシステム上のパス。

Goの syscall パッケージでは、これらの異なる Sockaddr の種類を表現するために、SockaddrInet4 (IPv4), SockaddrInet6 (IPv6), SockaddrUnix (Unixドメインソケット) といった具体的な型が定義されています。また、Sockaddr インターフェースは、これらの具体的な型が共通の sockaddr() メソッドを持つことを保証し、ポリモーフィックな操作を可能にします。

コードの重複とDRY原則

ソフトウェア開発において、同じコードが複数の場所に存在することを「コードの重複 (Code Duplication)」と呼びます。コードの重複は、以下のような問題を引き起こします。

  • 保守性の低下: 変更が必要な場合、すべての重複箇所を修正する必要があり、手間がかかるだけでなく、修正漏れによるバグのリスクが高まります。
  • 可読性の低下: 同じロジックが散在しているため、コード全体の理解が難しくなります。
  • バグの伝播: ある箇所で発生したバグが、重複している他の箇所にも影響を及ぼす可能性があります。

DRY (Don't Repeat Yourself) 原則は、これらの問題を避けるために、「すべての知識は、システム内で単一の、曖昧さのない、権威ある表現を持つべきである」という考え方を提唱します。この原則に従うことで、コードの保守性、可読性、信頼性が向上します。

このコミットは、Goの syscall パッケージにおけるコードの重複を解消し、DRY原則を適用する具体的な例と言えます。

技術的詳細

このコミットの技術的な核心は、Goの syscall パッケージ内で、異なるUnix系OS(BSDとLinux)間で共通して使用されるネットワーク関連の型と関数を特定し、それらを syscall_unix.go という新しい共通ファイルに移動した点にあります。

具体的に移動されたのは以下の要素です。

  1. SocketDisableIPv6 変数:

    • テスト目的でIPv6ソケットの作成を強制的に EAFNOSUPPORT エラーにするためのフラグ。これはOSに依存しないテストユーティリティであり、共通化が適切です。
  2. Sockaddr インターフェース:

    • 異なるソケットアドレス型(IPv4, IPv6, Unixドメイン)を抽象化するためのインターフェース。sockaddr() メソッドを定義し、具体的なソケットアドレス型がこのインターフェースを実装することで、ポリモーフィックな操作を可能にします。このインターフェース自体はOS固有のシステムコールとは直接関係なく、Goの型システムにおける抽象化であるため、共通化が可能です。
  3. SockaddrInet4 構造体:

    • IPv4ソケットアドレスを表現するための構造体。ポート番号、IPv4アドレス ([4]byte)、およびOS固有の生 (raw) 構造体を保持します。sockaddr() メソッドを実装し、Goの型からOSが期待する生のアドレス構造体への変換ロジックを提供します。IPv4アドレスの表現やポート番号の概念はOS間で共通であるため、この構造体も共通化できます。
  4. SockaddrInet6 構造体:

    • IPv6ソケットアドレスを表現するための構造体。ポート番号、ゾーンID、IPv6アドレス ([16]byte)、およびOS固有の生 (raw) 構造体を保持します。sockaddr() メソッドを実装し、IPv6アドレスの変換ロジックを提供します。IPv6アドレスの表現もOS間で共通であるため、共通化が可能です。
  5. SockaddrUnix 構造体:

    • Unixドメインソケットアドレスを表現するための構造体。パス名 (string) とOS固有の生 (raw) 構造体を保持します。sockaddr() メソッドを実装し、パス名の変換ロジックを提供します。Unixドメインソケットの概念もUnix系OS間で共通であるため、共通化できます。
  6. Bind 関数:

    • ソケットを特定のアドレスにバインド(結合)するための関数。Sockaddr インターフェースを受け取り、その sockaddr() メソッドを呼び出してOS固有の生のアドレス構造体を取得し、実際の bind システムコールを呼び出します。このロジックは、Sockaddr インターフェースの抽象化のおかげでOS間で共通化できます。
  7. Connect 関数:

    • ソケットをリモートアドレスに接続するための関数。Bind と同様に Sockaddr インターフェースを利用し、実際の connect システムコールを呼び出します。ロジックはOS間で共通です。
  8. Socket 関数:

    • 新しいソケットを作成するための関数。ドメイン、タイプ、プロトコルを指定します。SocketDisableIPv6 フラグのチェックもここで行われます。ソケット作成の基本的なロジックはOS間で共通です。
  9. Socketpair 関数:

    • 相互に接続されたソケットのペアを作成するための関数。プロセス間通信などに使用されます。この機能もUnix系OSで共通に提供されています。

これらの要素は、syscall_bsd.gosyscall_linux.go の両方にほぼ同一の形で存在していました。このコミットでは、これらの重複する定義を両ファイルから削除し、src/pkg/syscall/syscall_unix.go に一元的に配置しました。これにより、コードの重複が解消され、将来的な変更やバグ修正が容易になります。

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

このコミットでは、以下の3つのファイルが変更されました。

  1. src/pkg/syscall/syscall_bsd.go: 60行削除
  2. src/pkg/syscall/syscall_linux.go: 60行削除
  3. src/pkg/syscall/syscall_unix.go: 60行追加

具体的に移動されたコードは以下の通りです。

削除されたコード(syscall_bsd.go および syscall_linux.go から):

// For testing: clients can set this flag to force
// creation of IPv6 sockets to return EAFNOSUPPORT.
var SocketDisableIPv6 bool

type Sockaddr interface {
	sockaddr() (ptr uintptr, len _Socklen, err error) // lowercase; only we can define Sockaddrs
}

type SockaddrInet4 struct {
	Port int
	Addr [4]byte
	raw  RawSockaddrInet4
}

func (sa *SockaddrInet4) sockaddr() (uintptr, _Socklen, error) {
	// ... (implementation details) ...
}

type SockaddrInet6 struct {
	Port   int
	ZoneId uint32
	Addr   [16]byte
	raw    RawSockaddrInet6
}

func (sa *SockaddrInet6) sockaddr() (uintptr, _Socklen, error) {
	// ... (implementation details) ...
}

type SockaddrUnix struct {
	Name string
	raw  RawSockaddrUnix
}

func (sa *SockaddrUnix) sockaddr() (uintptr, _Socklen, error) {
	// ... (implementation details) ...
}

func Bind(fd int, sa Sockaddr) (err error) {
	ptr, n, err := sa.sockaddr()
	if err != nil {
		return err
	}
	return bind(fd, ptr, n)
}

func Connect(fd int, sa Sockaddr) (err error) {
	ptr, n, err := sa.sockaddr()
	if err != nil {
		return err
	}
	return connect(fd, ptr, n)
}

func Socket(domain, typ, proto int) (fd int, err error) {
	if domain == AF_INET6 && SocketDisableIPv6 {
		return -1, EAFNOSUPPORT
	}
	fd, err = socket(domain, typ, proto)
	return
}

func Socketpair(domain, typ, proto int) (fd [2]int, err error) {
	var fdx [2]int32
	err = socketpair(domain, typ, proto, &fdx)
	if err == nil {
		fd[0] = int(fdx[0])
		fd[1] = int(fdx[1])
	}
	return
}

追加されたコード(syscall_unix.go へ):

上記と全く同じコードブロックが src/pkg/syscall/syscall_unix.go に追加されました。

コアとなるコードの解説

移動された各要素の役割は以下の通りです。

  • SocketDisableIPv6:

    • このグローバル変数は、主にテストシナリオで使用されます。IPv6が利用できない環境をシミュレートしたり、IPv6関連のコードパスをテストしたりするために、IPv6ソケットの作成を意図的に失敗させる(EAFNOSUPPORT エラーを返す)ことができます。これはOS固有の動作ではなく、Goの syscall パッケージのテストハーネスの一部として機能するため、共通の syscall_unix.go に配置するのが適切です。
  • Sockaddr インターフェース:

    • Go言語におけるソケットアドレスの抽象化の基盤です。SockaddrInet4, SockaddrInet6, SockaddrUnix といった具体的なソケットアドレス型がこのインターフェースを実装することで、BindConnect のような関数が、具体的なアドレス型に依存せずに動作できるようになります。これにより、コードの柔軟性と再利用性が高まります。sockaddr() メソッドは、Goの内部表現から、OSのシステムコールが期待する sockaddr 構造体へのポインタと長さを返します。
  • SockaddrInet4 構造体:

    • IPv4アドレスとポート番号を表現します。Port フィールドはポート番号、Addr フィールドは4バイトのIPv4アドレスを保持します。raw フィールドは、OSのシステムコールに渡される生の sockaddr_in 構造体に対応します。sockaddr() メソッドの実装では、Goの PortAddr の値を raw 構造体に適切にマッピングし、そのポインタと長さを返します。
  • SockaddrInet6 構造体:

    • IPv6アドレスとポート番号、そしてゾーンIDを表現します。PortAddr はそれぞれポート番号と16バイトのIPv6アドレスを保持し、ZoneId はIPv6のスコープ(特にリンクローカルアドレスの場合)を識別するために使用されます。raw フィールドは、OSのシステムコールに渡される生の sockaddr_in6 構造体に対応します。sockaddr() メソッドは、IPv6アドレスの複雑なマッピングを処理します。
  • SockaddrUnix 構造体:

    • Unixドメインソケットのアドレスを表現します。これはファイルシステム上のパス名 (Name) を使用してソケットを識別します。raw フィールドは、OSのシステムコールに渡される生の sockaddr_un 構造体に対応します。sockaddr() メソッドは、パス名をバイト列に変換し、raw 構造体に格納します。
  • Bind(fd int, sa Sockaddr) (err error) 関数:

    • 指定されたファイルディスクリプタ fd にソケットアドレス sa を結合します。これは、サーバーアプリケーションが特定のIPアドレスとポートで接続を待ち受けるために使用されます。sa.sockaddr() を呼び出すことで、具体的な Sockaddr 型からOSが理解できる形式のアドレス情報を取得し、実際の bind システムコール(OS固有の実装は別途存在する)を呼び出します。
  • Connect(fd int, sa Sockaddr) (err error) 関数:

    • 指定されたファイルディスクリプタ fd のソケットを、リモートのソケットアドレス sa に接続します。これは、クライアントアプリケーションがサーバーに接続するために使用されます。Bind と同様に sa.sockaddr() を利用してアドレス情報を取得し、実際の connect システムコールを呼び出します。
  • Socket(domain, typ, proto int) (fd int, err error) 関数:

    • 新しいソケットを作成し、そのファイルディスクリプタを返します。domain はアドレスファミリー(例: AF_INET, AF_INET6)、typ はソケットのタイプ(例: SOCK_STREAM (TCP), SOCK_DGRAM (UDP))、proto はプロトコル(通常は0でデフォルト)を指定します。SocketDisableIPv6 フラグのチェックもここで行われ、IPv6ソケットの作成を制御します。
  • Socketpair(domain, typ, proto int) (fd [2]int, err error) 関数:

    • 相互に接続されたソケットのペアを作成します。これにより、同じマシン上の2つのプロセスまたはスレッド間で双方向通信を行うことができます。返される fd 配列には、2つのソケットのファイルディスクリプタが含まれます。

これらの型と関数は、ネットワークプログラミングにおける基本的な構成要素であり、Unix系OSの多くで共通のセマンティクスとインターフェースを持っています。そのため、これらを syscall_unix.go に集約することで、コードの重複を排除し、Goの syscall パッケージのクロスプラットフォーム対応をより効率的に行うことができます。

関連リンク

参考にした情報源リンク

  • 上記のGoogle Groupsの議論
  • Go言語の syscall パッケージのソースコード (コミット前後の状態)
  • 一般的なシステムコールとソケットプログラミングに関する知識
  • DRY (Don't Repeat Yourself) 原則に関する一般的なソフトウェア工学の知識