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

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

このコミットは、Go言語のsyscallパッケージにおけるコードの重複を削減することを目的としています。具体的には、BSD系OSとLinux系OSの間で共通するシステムコール関連のコードをsyscall_unix.goに移動することで、コードベースの保守性と一貫性を向上させています。

コミット

commit 3b76d70e11f7800bfca64f4e267f3df14881a7e5
Author: Dave Cheney <dave@cheney.net>
Date:   Tue Jun 25 10:14:40 2013 +1000

    syscall: reduce duplication between *bsd and linux
    
    Part 2 of several.
    
    R=rsc, mikioh.mikioh, r
    CC=golang-dev
    https://golang.org/cl/10462043

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

https://github.com/golang/go/commit/3b76d70e11f7800bfca64f4e267f3df14881a7e5

元コミット内容

このコミットは、syscallパッケージ内のGetpeername, GetsockoptInt, Recvfrom, Sendtoといった関数について、syscall_bsd.gosyscall_linux.goから重複する実装を削除し、それらをsyscall_unix.goに集約しています。これにより、各OS固有のファイルは、そのOSに特有のシステムコールや構造体のみを保持するようになり、共通のロジックはsyscall_unix.goで一元的に管理されるようになります。

変更の背景

Go言語はクロスプラットフォーム対応を重視しており、様々なオペレーティングシステム上で動作するように設計されています。しかし、OSごとにシステムコールのインターフェースや挙動が微妙に異なるため、それぞれのOS向けに個別の実装が必要となる場合があります。

このコミットが行われた2013年頃のGo言語のsyscallパッケージは、各OS(Linux、BSD、Windowsなど)向けに多くのコードが重複して存在していました。特に、Unix系のOSであるLinuxとBSDは、多くのシステムコールで共通のインターフェースやセマンティクスを持っていました。このような重複は、コードの保守性を低下させ、バグの温床となる可能性がありました。例えば、あるOSでバグが修正されても、他のOSの類似コードに同じ修正が適用されないといった問題が発生しやすくなります。

このコミットは、このようなコードの重複を削減し、共通のロジックをsyscall_unix.goに集約することで、コードベースの健全性を高め、将来的なメンテナンスを容易にすることを目的とした一連の変更("Part 2 of several"とコミットメッセージにある)の一部です。これにより、開発者は共通のロジックを一度修正すれば、それが複数のUnix系OSに適用されるようになり、開発効率とコード品質の向上が期待されます。

前提知識の解説

1. システムコール (System Call)

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

Go言語のような高水準言語で書かれたプログラムも、最終的にはOSの機能を利用するためにシステムコールを呼び出す必要があります。Goの標準ライブラリ、特にosnetパッケージの多くの機能は、内部的にsyscallパッケージを介してOSのシステムコールを呼び出しています。

2. syscallパッケージ

Go言語のsyscallパッケージは、OSの低レベルなシステムコールへの直接的なアクセスを提供します。このパッケージは、OS固有の定数、構造体、関数などを定義しており、GoプログラムからC言語のシステムコールAPIを呼び出すのと同様の操作を可能にします。

syscallパッケージは、Goの標準ライブラリの中でも特にOS依存性が高い部分であり、各OS向けに異なる実装が提供されています。例えば、syscall_linux.goはLinux固有のシステムコールを、syscall_bsd.goはBSD系OS(FreeBSD, OpenBSD, NetBSDなど)固有のシステムコールを扱います。

3. クロスプラットフォーム開発とコードの重複

クロスプラットフォーム開発では、異なるOS間で共通の機能を提供しつつ、OS固有の差異を吸収する必要があります。システムコールの場合、多くのUnix系OS(Linux, macOS, BSDなど)はPOSIX標準に準拠しているため、共通のシステムコールインターフェースを持っています。しかし、引数の型、戻り値、エラーコード、あるいは特定のフラグの有無など、細かな違いが存在することがあります。

このような状況で、各OS向けに完全に独立したコードを書くと、共通のロジックが何度も複製され、コードの重複が発生します。コードの重複は、以下のような問題を引き起こします。

  • 保守性の低下: 同じバグが複数の場所で修正される必要があり、修正漏れが発生しやすい。
  • 開発効率の低下: 同じ機能の実装やテストを複数回行う必要がある。
  • コードサイズの増大: 不要なコードの複製により、バイナリサイズが増加する可能性がある。
  • 一貫性の欠如: 異なる実装間で微妙な差異が生じ、予期せぬ挙動を引き起こす可能性がある。

このため、共通のロジックは一箇所に集約し、OS固有の差異のみを各OS固有のファイルで扱うという設計パターンが推奨されます。

4. go buildのタグとファイル名規則

Go言語のビルドシステムは、特定のファイル名規則やビルドタグ(build tags)を使用して、異なるOSやアーキテクチャ向けのコードを条件付きでコンパイルする機能を提供します。

  • ファイル名規則: _GOOS.go_GOARCH.goといったサフィックスを持つファイルは、特定のOSやアーキテクチャでのみコンパイルされます。
    • 例: syscall_linux.goはLinuxでのみコンパイルされる。
    • 例: syscall_bsd.goはBSD系OSでのみコンパイルされる。
    • 例: syscall_unix.goはUnix系OS(Linux, BSD, macOSなど)で共通してコンパイルされる。これは、ファイル冒頭に// +build darwin dragonfly freebsd linux netbsd openbsdのようなビルドタグが記述されているか、あるいはGoのビルドシステムが内部的にUnix系OSを識別する仕組みを持っているためです。

このコミットでは、syscall_bsd.gosyscall_linux.goからコードを削除し、syscall_unix.goに移動することで、Unix系OSで共通して利用できるコードをsyscall_unix.goに集約しています。

技術的詳細

このコミットは、Go言語のsyscallパッケージにおけるUnix系OS間のコード重複を解消するためのリファクタリングです。具体的には、Getpeername, GetsockoptInt, Recvfrom, Sendtoという4つの関数について、以下の変更が行われています。

  1. syscall_bsd.goからの削除:

    • Getpeername関数
    • GetsockoptInt関数
    • Recvfrom関数
    • Sendto関数 これらの関数はsyscall_bsd.goから完全に削除されました。
  2. syscall_linux.goからの削除:

    • Getpeername関数
    • GetsockoptInt関数
    • Recvfrom関数
    • Sendto関数 これらの関数はsyscall_linux.goから完全に削除されました。
  3. syscall_unix.goへの追加:

    • Getpeername関数
    • GetsockoptInt関数
    • Recvfrom関数
    • Sendto関数 上記4つの関数がsyscall_unix.goに追加されました。これらの関数は、BSDとLinuxで共通の実装を持つため、syscall_unix.goに移動することで一元的に管理されるようになりました。

この変更により、syscall_bsd.gosyscall_linux.goは、それぞれのOSに固有のシステムコールや構造体定義のみを保持するようになり、共通のロジックはsyscall_unix.goに集約されました。これにより、コードの重複が大幅に削減され、Goのsyscallパッケージ全体の保守性が向上しました。

例えば、Recvfrom関数の実装を見てみましょう。この関数は、ソケットからデータを受信し、送信元のアドレスも取得するシステムコールをラップしています。BSDとLinuxの両方で、このシステムコールの基本的な呼び出し方や引数の処理は非常に似ています。そのため、共通のRecvfrom関数をsyscall_unix.goに定義することで、両OSで同じコードパスを使用できるようになります。

このリファクタリングは、Goの標準ライブラリが成熟していく過程で、コードの品質と保守性を高めるために行われた典型的な改善の一つです。

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

このコミットにおける主要な変更は、以下の3つのファイルにわたります。

  1. src/pkg/syscall/syscall_bsd.go
  2. src/pkg/syscall/syscall_linux.go
  3. src/pkg/syscall/syscall_unix.go

具体的には、syscall_bsd.gosyscall_linux.goから以下の関数が削除され、syscall_unix.goに移動されました。

  • Getpeername
  • GetsockoptInt
  • Recvfrom
  • Sendto

src/pkg/syscall/syscall_bsd.go の変更点:

--- a/src/pkg/syscall/syscall_bsd.go
+++ b/src/pkg/syscall/syscall_bsd.go
@@ -309,15 +309,6 @@ func Getsockname(fd int) (sa Sockaddr, err error) {
 	return anyToSockaddr(&rsa)
 }
 
-func Getpeername(fd int) (sa Sockaddr, err error) {
-	var rsa RawSockaddrAny
-	var len _Socklen = SizeofSockaddrAny
-	if err = getpeername(fd, &rsa, &len); err != nil {
-		return
-	}
-	return anyToSockaddr(&rsa)
-}
-
 //sysnb socketpair(domain int, typ int, proto int, fd *[2]int32) (err error)
 
 func GetsockoptByte(fd, level, opt int) (value byte, err error) {
@@ -327,13 +318,6 @@ func GetsockoptByte(fd, level, opt int) (value byte, err error) {
 	return n, err
 }
 
-func GetsockoptInt(fd, level, opt int) (value int, err error) {
-	var n int32
-	vallen := _Socklen(4)
-	err = getsockopt(fd, level, opt, uintptr(unsafe.Pointer(&n)), &vallen)
-	return int(n), err
-}
-
 func GetsockoptInet4Addr(fd, level, opt int) (value [4]byte, err error) {
 	vallen := _Socklen(4)
 	err = getsockopt(fd, level, opt, uintptr(unsafe.Pointer(&value[0])), &vallen)
@@ -406,30 +390,8 @@ func SetsockoptString(fd, level, opt int, s string) (err error) {
 	return setsockopt(fd, level, opt, uintptr(unsafe.Pointer(&[]byte(s)[0])), uintptr(len(s)))\n }\n \n-//sys recvfrom(fd int, p []byte, flags int, from *RawSockaddrAny, fromlen *_Socklen) (n int, err error)\n-\n-func Recvfrom(fd int, p []byte, flags int) (n int, from Sockaddr, err error) {\n-\tvar rsa RawSockaddrAny\n-\tvar len _Socklen = SizeofSockaddrAny\n-\tif n, err = recvfrom(fd, p, flags, &rsa, &len); err != nil {\n-\t\treturn\n-\t}\n-\tif rsa.Addr.Family != AF_UNSPEC {\n-\t\tfrom, err = anyToSockaddr(&rsa)\n-\t}\n-\treturn\n-}\n-\n-//sys sendto(s int, buf []byte, flags int, to uintptr, addrlen _Socklen) (err error)\n-\n-func Sendto(fd int, p []byte, flags int, to Sockaddr) (err error) {\n-\tptr, n, err := to.sockaddr()\n-\tif err != nil {\n-\t\treturn err\n-\t}\n-\treturn sendto(fd, p, flags, ptr, n)\n-}\n-\n+//sys   recvfrom(fd int, p []byte, flags int, from *RawSockaddrAny, fromlen *_Socklen) (n int, err error)\n+//sys   sendto(s int, buf []byte, flags int, to uintptr, addrlen _Socklen) (err error)\n //sys	recvmsg(s int, msg *Msghdr, flags int) (n int, err error)\n \n func Recvmsg(fd int, p, oob []byte, flags int) (n, oobn int, recvflags int, from Sockaddr, err error) {

src/pkg/syscall/syscall_linux.go の変更点:

--- a/src/pkg/syscall/syscall_linux.go
+++ b/src/pkg/syscall/syscall_linux.go
@@ -437,22 +437,6 @@ func Getsockname(fd int) (sa Sockaddr, err error) {
 	return anyToSockaddr(&rsa)
 }
 
-func Getpeername(fd int) (sa Sockaddr, err error) {
-	var rsa RawSockaddrAny
-	var len _Socklen = SizeofSockaddrAny
-	if err = getpeername(fd, &rsa, &len); err != nil {
-		return
-	}
-	return anyToSockaddr(&rsa)
-}
-
-func GetsockoptInt(fd, level, opt int) (value int, err error) {
-	var n int32
-	vallen := _Socklen(4)
-	err = getsockopt(fd, level, opt, uintptr(unsafe.Pointer(&n)), &vallen)
-	return int(n), err
-}
-
 func GetsockoptInet4Addr(fd, level, opt int) (value [4]byte, err error) {
 	vallen := _Socklen(4)
 	err = getsockopt(fd, level, opt, uintptr(unsafe.Pointer(&value[0])), &vallen)
@@ -537,26 +521,6 @@ 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 Recvfrom(fd int, p []byte, flags int) (n int, from Sockaddr, err error) {
-	var rsa RawSockaddrAny
-	var len _Socklen = SizeofSockaddrAny
-	if n, err = recvfrom(fd, p, flags, &rsa, &len); err != nil {
-		return
-	}
-	if rsa.Addr.Family != AF_UNSPEC {
-		from, err = anyToSockaddr(&rsa)
-	}
-	return
-}
-
-func Sendto(fd int, p []byte, flags int, to Sockaddr) (err error) {
-	ptr, n, err := to.sockaddr()
-	if err != nil {
-		return err
-	}
-	return sendto(fd, p, flags, ptr, n)
-}
-
 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
@@ -194,6 +194,42 @@ func Connect(fd int, sa Sockaddr) (err error) {
 	return connect(fd, ptr, n)
 }
 
+func Getpeername(fd int) (sa Sockaddr, err error) {
+	var rsa RawSockaddrAny
+	var len _Socklen = SizeofSockaddrAny
+	if err = getpeername(fd, &rsa, &len); err != nil {
+		return
+	}
+	return anyToSockaddr(&rsa)
+}
+
+func GetsockoptInt(fd, level, opt int) (value int, err error) {
+	var n int32
+	vallen := _Socklen(4)
+	err = getsockopt(fd, level, opt, uintptr(unsafe.Pointer(&n)), &vallen)
+	return int(n), err
+}
+
+func Recvfrom(fd int, p []byte, flags int) (n int, from Sockaddr, err error) {
+	var rsa RawSockaddrAny
+	var len _Socklen = SizeofSockaddrAny
+	if n, err = recvfrom(fd, p, flags, &rsa, &len); err != nil {
+		return
+	}
+	if rsa.Addr.Family != AF_UNSPEC {
+		from, err = anyToSockaddr(&rsa)
+	}
+	return
+}
+
+func Sendto(fd int, p []byte, flags int, to Sockaddr) (err error) {
+	ptr, n, err := to.sockaddr()
+	if err != nil {
+		return err
+	}
+	return sendto(fd, p, flags, ptr, n)
+}
+
 func Socket(domain, typ, proto int) (fd int, err error) {
 	if domain == AF_INET6 && SocketDisableIPv6 {
 		return -1, EAFNOSUPPORT

コアとなるコードの解説

このコミットで移動された各関数は、ソケットプログラミングにおける基本的な操作をGo言語から行うためのラッパーです。これらの関数がsyscall_unix.goに集約されたことで、Unix系OS(BSD、Linuxなど)では共通のコードパスが使用されるようになりました。

以下に、移動された主要な関数の役割と、その実装の共通性について解説します。

Getpeername(fd int) (sa Sockaddr, err error)

  • 役割: 指定されたソケットディスクリプタfdに接続されているピア(リモートエンド)のアドレスを取得します。
  • 共通性: getpeernameシステムコールは、POSIX標準で定義されており、Unix系OS間でインターフェースが非常に似ています。RawSockaddrAny構造体と_Socklen型を使用して、汎用的にソケットアドレスを扱うことができます。

GetsockoptInt(fd, level, opt int) (value int, err error)

  • 役割: 指定されたソケットディスクリプタfdのソケットオプションの整数値を取得します。levelはオプションのプロトコルレベル(例: SOL_SOCKET)、optはオプション名(例: SO_REUSEADDR)を指定します。
  • 共通性: getsockoptシステムコールもPOSIX標準で定義されており、整数値のオプションを取得する際のパターンはUnix系OS間で共通しています。unsafe.Pointerを使用して、int32型の変数nのアドレスをシステムコールに渡しています。

Recvfrom(fd int, p []byte, flags int) (n int, from Sockaddr, err error)

  • 役割: 指定されたソケットディスクリプタfdからデータを受信し、受信したバイト数n、送信元のアドレスfrom、およびエラーerrを返します。pは受信バッファ、flagsは受信オプションです。
  • 共通性: recvfromシステムコールは、データグラムソケット(UDPなど)でデータを送受信する際に、送信元のアドレスも同時に取得するために使用されます。このシステムコールのインターフェースと挙動は、Unix系OS間で非常に共通しています。受信したアドレスがAF_UNSPEC(アドレスが指定されていない)でない場合にのみ、anyToSockaddr関数でSockaddr型に変換しています。

Sendto(fd int, p []byte, flags int, to Sockaddr) (err error)

  • 役割: 指定されたソケットディスクリプタfdから、指定されたアドレスtoへデータpを送信します。flagsは送信オプションです。
  • 共通性: sendtoシステムコールは、データグラムソケットで特定の宛先へデータを送信するために使用されます。to.sockaddr()メソッドでSockaddr型からシステムコールが要求する形式(ポインタと長さ)に変換し、sendtoシステムコールを呼び出します。このパターンもUnix系OS間で共通しています。

これらの関数は、Goのsyscallパッケージが提供する低レベルなインターフェースであり、Goのネットワークパッケージ(net)などが内部的に利用しています。共通のロジックをsyscall_unix.goに集約することで、Goのクロスプラットフォーム対応がより効率的かつ堅牢になります。

関連リンク

参考にした情報源リンク