[インデックス 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.go
とsyscall_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の標準ライブラリ、特にos
やnet
パッケージの多くの機能は、内部的に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.go
とsyscall_linux.go
からコードを削除し、syscall_unix.go
に移動することで、Unix系OSで共通して利用できるコードをsyscall_unix.go
に集約しています。
技術的詳細
このコミットは、Go言語のsyscall
パッケージにおけるUnix系OS間のコード重複を解消するためのリファクタリングです。具体的には、Getpeername
, GetsockoptInt
, Recvfrom
, Sendto
という4つの関数について、以下の変更が行われています。
-
syscall_bsd.go
からの削除:Getpeername
関数GetsockoptInt
関数Recvfrom
関数Sendto
関数 これらの関数はsyscall_bsd.go
から完全に削除されました。
-
syscall_linux.go
からの削除:Getpeername
関数GetsockoptInt
関数Recvfrom
関数Sendto
関数 これらの関数はsyscall_linux.go
から完全に削除されました。
-
syscall_unix.go
への追加:Getpeername
関数GetsockoptInt
関数Recvfrom
関数Sendto
関数 上記4つの関数がsyscall_unix.go
に追加されました。これらの関数は、BSDとLinuxで共通の実装を持つため、syscall_unix.go
に移動することで一元的に管理されるようになりました。
この変更により、syscall_bsd.go
とsyscall_linux.go
は、それぞれのOSに固有のシステムコールや構造体定義のみを保持するようになり、共通のロジックはsyscall_unix.go
に集約されました。これにより、コードの重複が大幅に削減され、Goのsyscall
パッケージ全体の保守性が向上しました。
例えば、Recvfrom
関数の実装を見てみましょう。この関数は、ソケットからデータを受信し、送信元のアドレスも取得するシステムコールをラップしています。BSDとLinuxの両方で、このシステムコールの基本的な呼び出し方や引数の処理は非常に似ています。そのため、共通のRecvfrom
関数をsyscall_unix.go
に定義することで、両OSで同じコードパスを使用できるようになります。
このリファクタリングは、Goの標準ライブラリが成熟していく過程で、コードの品質と保守性を高めるために行われた典型的な改善の一つです。
コアとなるコードの変更箇所
このコミットにおける主要な変更は、以下の3つのファイルにわたります。
src/pkg/syscall/syscall_bsd.go
src/pkg/syscall/syscall_linux.go
src/pkg/syscall/syscall_unix.go
具体的には、syscall_bsd.go
とsyscall_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のクロスプラットフォーム対応がより効率的かつ堅牢になります。
関連リンク
- Go言語の
syscall
パッケージのドキュメント: https://pkg.go.dev/syscall - POSIX.1-2001 (IEEE Std 1003.1-2001) -
getpeername
に関する記述: https://pubs.opengroup.org/onlinepubs/009695399/functions/getpeername.html - POSIX.1-2001 (IEEE Std 1003.1-2001) -
getsockopt
に関する記述: https://pubs.opengroup.org/onlinepubs/009695399/functions/getsockopt.html - POSIX.1-2001 (IEEE Std 1003.1-2001) -
recvfrom
に関する記述: https://pubs.opengroup.org/onlinepubs/009695399/functions/recvfrom.html - POSIX.1-2001 (IEEE Std 1003.1-2001) -
sendto
に関する記述: https://pubs.opengroup.org/onlinepubs/009695399/functions/sendto.html
参考にした情報源リンク
- Go言語のソースコード (特に
src/pkg/syscall
ディレクトリ): https://github.com/golang/go/tree/master/src/pkg/syscall - Go言語のビルドタグに関する公式ドキュメント: https://pkg.go.dev/cmd/go#hdr-Build_constraints
- システムコールに関する一般的な情報 (Wikipediaなど): https://ja.wikipedia.org/wiki/%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%82%B3%E3%83%BC%E3%83%AB
- Go言語のクロスコンパイルに関する情報: https://go.dev/doc/install/source#environment