[インデックス 18987] ファイルの概要
このコミットは、Go言語のsyscall
パッケージにおいて、sendmsg
システムコールをラップする新しい関数SendmsgN
を追加するものです。この新しい関数は、従来のSendmsg
関数がエラーのみを返していたのに対し、転送されたバイト数も返すように設計されています。これにより、sendmsg
システムコールの結果をより詳細に、かつ効率的に取得できるようになります。対象となるOSはBSD系(Darwin, Dragonfly, FreeBSD, NetBSD, OpenBSD)、Linux、およびSolarisです。
コミット
commit a7858a40a5bd8481fee839fddd29c305bb4093ef
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Sat Mar 29 09:28:40 2014 +0900
syscall: add SendmsgN for BSD variants, Linux and Solaris
SendmsgN is an alternate version Sendmsg that also returns
the number of bytes transferred, instead of just the error.
Update #7645
LGTM=aram, iant
R=iant, aram, bradfitz
CC=golang-codereviews
https://golang.org/cl/81210043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a7858a40a5bd8481fee839fddd29c305bb4093ef
元コミット内容
syscall: add SendmsgN for BSD variants, Linux and Solaris
このコミットは、SendmsgN
という新しい関数をsyscall
パッケージに追加します。SendmsgN
は、既存のSendmsg
関数の代替バージョンであり、転送されたバイト数とエラーの両方を返します。従来のSendmsg
はエラーのみを返していました。この変更は、Issue #7645 に関連しています。
変更の背景
従来のsyscall.Sendmsg
関数は、Unix系システムコールであるsendmsg(2)
をGoから呼び出すためのラッパーでしたが、その戻り値はエラー情報のみでした。しかし、sendmsg(2)
システムコール自体は、データの送信が成功した場合に送信されたバイト数を返します。このバイト数は、特にネットワークプログラミングにおいて、実際にどれだけのデータが送信されたかを確認するために非常に重要です。例えば、部分的な送信が発生した場合や、OOB (Out-of-Band) データと通常データが混在する場合など、送信バイト数を正確に把握する必要があるシナリオが存在します。
Issue #7645("syscall: sendmsg should return n")では、このsendmsg
システムコールが返すバイト数をGoのsyscall
パッケージからも取得できるようにすべきだという要望が挙げられていました。このコミットは、その要望に応える形で、バイト数を返すSendmsgN
関数を導入し、より低レベルかつ詳細な制御を必要とするアプリケーション開発者に対して、必要な情報を提供するものです。
前提知識の解説
このコミットを理解するためには、以下の概念について理解しておく必要があります。
- システムコール (System Call): オペレーティングシステム (OS) のカーネルが提供するサービスを、ユーザー空間のプログラムが利用するためのインターフェースです。ファイルI/O、ネットワーク通信、プロセス管理など、OSの機能にアクセスするために使用されます。
sendmsg
は、ソケットを介してデータを送信するためのシステムコールの一つです。 sendmsg(2)
: Unix系OSにおけるシステムコールで、ソケットを介してメッセージを送信するために使用されます。このシステムコールは、通常のデータだけでなく、補助データ(コントロールメッセージ、例: ファイルディスクリプタの転送など)や、送信先アドレスを一度に指定して送信する機能を提供します。成功時には送信されたバイト数を返します。syscall
パッケージ (Go言語): Go言語の標準ライブラリの一つで、低レベルなOSのシステムコールにアクセスするための機能を提供します。これにより、Goプログラムから直接OSの機能を利用できますが、OSに依存するコードとなるため、移植性には注意が必要です。Msghdr
構造体:sendmsg
システムコールで使用される構造体で、送信するメッセージに関する情報(データバッファ、補助データバッファ、送信先アドレスなど)をカプセル化します。Sockaddr
インターフェース: ソケットアドレスを表すGoのインターフェースです。異なる種類のソケットアドレス(IPv4、IPv6、Unixドメインなど)を抽象化します。unsafe.Pointer
: Go言語におけるunsafe
パッケージの一部で、任意の型のポインタを表現するために使用されます。Goの型安全性をバイパスするため、慎重な使用が求められます。システムコールのように、C言語のAPIと直接やり取りする際に必要となることがあります。Syscall
関数 (Go言語):syscall
パッケージ内で、実際のシステムコールを呼び出すための低レベルな関数です。引数としてシステムコール番号と、そのシステムコールに渡す引数をuintptr
型で受け取ります。戻り値として、システムコールが返すレジスタの値(通常は戻り値とエラーコード)とエラーを返します。- BSD variants (BSD系OS): Berkeley Software Distributionを起源とするUnix系オペレーティングシステムの総称です。FreeBSD, OpenBSD, NetBSD, Dragonfly BSDなどが含まれます。macOS (Darwin) もBSD系カーネルをベースにしています。
- Linux: オープンソースのUnix系オペレーティングシステムカーネルです。
- Solaris: Sun Microsystems(現在はOracle)が開発したUnix系オペレーティングシステムです。
- Issue #7645: GoのIssueトラッカーで管理されている、この変更の背景となった具体的な要望やバグ報告です。この場合、「
sendmsg
がバイト数を返すようにすべき」という内容でした。
技術的詳細
このコミットの主要な変更点は、syscall
パッケージ内のsendmsg
システムコールラッパーの挙動を変更し、送信バイト数を返すようにしたことです。
具体的には、以下の変更が行われました。
-
sendmsg
関数のシグネチャ変更: 各OS(BSD系、Linux、Solaris)のsyscall
パッケージ内部で定義されている低レベルなsendmsg
関数のシグネチャが、func sendmsg(s int, msg *Msghdr, flags int) (err error)
からfunc sendmsg(s int, msg *Msghdr, flags int) (n int, err error)
に変更されました。これにより、システムコールが返すバイト数n
がGoの関数からも直接取得できるようになります。 -
SendmsgN
関数の追加: 新しい公開関数SendmsgN(fd int, p, oob []byte, to Sockaddr, flags int) (n int, err error)
が追加されました。この関数は、従来のSendmsg
と同様の引数を受け取りますが、成功時に送信されたバイト数n
を返します。 -
Sendmsg
関数の変更: 既存のSendmsg(fd int, p, oob []byte, to Sockaddr, flags int) (err error)
関数は、内部的に新しく追加されたSendmsgN
を呼び出すように変更されました。これにより、Sendmsg
の既存の挙動(エラーのみを返す)を維持しつつ、SendmsgN
を通じてバイト数取得の機能を提供します。 -
zsyscall_*.go
ファイルの更新:zsyscall_*.go
ファイルは、go generate
コマンドによって自動生成されるファイルで、各OSアーキテクチャごとのシステムコールラッパーの実装を含んでいます。このコミットでは、これらのファイル内のsendmsg
関数の呼び出し箇所が更新され、システムコールからの戻り値(バイト数)を適切に取得し、n
として返すように修正されました。具体的には、Syscall
関数の戻り値r0
をn
に代入する変更が行われています。 -
エラーハンドリングの修正:
SendmsgN
関数内で、to.sockaddr()
呼び出しでエラーが発生した場合の戻り値がreturn 0, err
に変更され、バイト数n
がゼロとして初期化されるようになりました。 -
特殊なケースの考慮:
SendmsgN
の実装において、oob
(Out-of-Bandデータ)のみが送信され、通常のデータp
が空の場合に、n
を0に設定するロジックが追加されました。これは、一部のOSやプロトコルにおいて、OOBデータのみの送信がバイト数としてカウントされない場合があるため、その挙動を考慮したものです。
これらの変更により、Goのsyscall
パッケージは、sendmsg
システムコールの完全な戻り値(送信バイト数とエラー)をGoプログラムに公開できるようになり、より柔軟で正確なネットワークプログラミングが可能になりました。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、主に以下のファイル群に見られます。
src/pkg/syscall/syscall_bsd.go
: BSD系OS向けのSendmsg
およびSendmsgN
の実装。src/pkg/syscall/syscall_linux.go
: Linux向けのSendmsg
およびSendmsgN
の実装。src/pkg/syscall/syscall_solaris.go
: Solaris向けのSendmsg
およびSendmsgN
の実装。src/pkg/syscall/zsyscall_*.go
: 各OSおよびアーキテクチャ(例:zsyscall_darwin_amd64.go
,zsyscall_linux_386.go
など)ごとの自動生成されたシステムコールラッパーファイル。これらのファイル内のsendmsg
関数のシグネチャと、Syscall
からの戻り値の処理が変更されています。
以下に、src/pkg/syscall/syscall_bsd.go
とsrc/pkg/syscall/zsyscall_darwin_amd64.go
の変更点を例として示します。他のOS向けのファイルも同様のパターンで変更されています。
src/pkg/syscall/syscall_bsd.go
の変更例:
--- a/src/pkg/syscall/syscall_bsd.go
+++ b/src/pkg/syscall/syscall_bsd.go
@@ -389,15 +389,20 @@ func Recvmsg(fd int, p, oob []byte, flags int) (n, oobn int, recvflags int, from
return
}
-//sys sendmsg(s int, msg *Msghdr, flags int) (err error)
+//sys sendmsg(s int, msg *Msghdr, flags int) (n int, err error)
func Sendmsg(fd int, p, oob []byte, to Sockaddr, flags int) (err error) {
+\t_, err = SendmsgN(fd, p, oob, to, flags)
+\treturn
+}
+
+func SendmsgN(fd int, p, oob []byte, to Sockaddr, flags int) (n int, err error) {
var ptr unsafe.Pointer
var salen _Socklen
if to != nil {
ptr, salen, err = to.sockaddr()
if err != nil {
-\t\t\treturn
+\t\t\treturn 0, err
}
}
var msg Msghdr
@@ -420,10 +425,13 @@ func Sendmsg(fd int, p, oob []byte, to Sockaddr, flags int) (err error) {
}
msg.Iov = &iov
msg.Iovlen = 1
-\tif err = sendmsg(fd, &msg, flags); err != nil {
-\t\treturn
+\tif n, err = sendmsg(fd, &msg, flags); err != nil {
+\t\treturn 0, err
}
-\treturn
+\tif len(oob) > 0 && len(p) == 0 {
+\t\tn = 0
+\t}
+\treturn n, nil
}
//sys kevent(kq int, change unsafe.Pointer, nchange int, event unsafe.Pointer, nevent int, timeout *Timespec) (n int, err error)
src/pkg/syscall/zsyscall_darwin_amd64.go
の変更例:
--- a/src/pkg/syscall/zsyscall_darwin_amd64.go
+++ b/src/pkg/syscall/zsyscall_darwin_amd64.go
@@ -185,8 +185,9 @@ func recvmsg(s int, msg *Msghdr, flags int) (n int, err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-func sendmsg(s int, msg *Msghdr, flags int) (err error) {
-\t_, _, e1 := Syscall(SYS_SENDMSG, uintptr(s), uintptr(unsafe.Pointer(msg)), uintptr(flags))\n
+func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) {
+\tr0, _, e1 := Syscall(SYS_SENDMSG, uintptr(s), uintptr(unsafe.Pointer(msg)), uintptr(flags))\n
+\tn = int(r0)\n
\tif e1 != 0 {\n
\t\terr = e1\n
\t}\n
コアとなるコードの解説
上記のコード変更は、Goのsyscall
パッケージがsendmsg
システムコールをどのように扱うかを根本的に変更しています。
-
//sys sendmsg(s int, msg *Msghdr, flags int) (n int, err error)
: これはGoのsyscall
パッケージがシステムコールをラップする際に使用する特殊なコメントです。このコメントは、go generate
ツールがzsyscall_*.go
のようなファイルを生成する際に、対応するC言語のシステムコール(この場合はsendmsg(2)
)のシグネチャをGoの関数シグネチャにマッピングするために使用されます。変更前はerr error
のみを返していましたが、変更後はn int, err error
を返すように明示されており、システムコールが返すバイト数n
がGoの関数に伝播されるようになります。 -
func Sendmsg(fd int, p, oob []byte, to Sockaddr, flags int) (err error) { ... }
: この関数は、Goのユーザーが通常利用する高レベルなラッパーです。変更後、この関数は新しく追加されたSendmsgN
を呼び出し、その戻り値のうちエラーのみを返しています。これは、既存のSendmsg
のAPI互換性を維持するための措置です。 -
func SendmsgN(fd int, p, oob []byte, to Sockaddr, flags int) (n int, err error) { ... }
: この関数が、このコミットで新しく導入された主要なAPIです。var ptr unsafe.Pointer
とvar salen _Socklen
:to Sockaddr
インターフェースから、実際のソケットアドレス構造体へのポインタと長さを取得するための変数です。unsafe.Pointer
は、Goの型システムをバイパスして任意のメモリを指すことができるため、C言語のシステムコールにGoのデータを渡す際に必要となります。if to != nil { ... }
: 送信先アドレスが指定されている場合、to.sockaddr()
を呼び出してソケットアドレスのポインタと長さを取得します。ここでエラーが発生した場合は、return 0, err
として、バイト数n
を0としてエラーを返します。var msg Msghdr
:sendmsg
システムコールに渡すMsghdr
構造体を初期化します。この構造体には、送信するデータ(p
)、補助データ(oob
)、送信先アドレスなどが設定されます。msg.Iov = &iov
とmsg.Iovlen = 1
:Msghdr
構造体のIov
フィールドは、送信するデータバッファの配列(iovec
構造体)を指します。ここでは単一のデータバッファ(p
)を送信するため、Iovlen
は1に設定されます。if n, err = sendmsg(fd, &msg, flags); err != nil { ... }
: ここが最も重要な変更点です。低レベルなsendmsg
関数を呼び出し、その戻り値であるバイト数n
とエラーerr
を直接受け取ります。エラーが発生した場合は、return 0, err
として処理を終了します。if len(oob) > 0 && len(p) == 0 { n = 0 }
: これは、OOBデータのみが送信され、通常のデータが送信されない場合の特殊なケースを処理します。一部のシステムでは、この場合にsendmsg
が返すバイト数が期待通りにならないことがあるため、明示的にn
を0に設定しています。return n, nil
: 成功した場合、送信されたバイト数n
とnil
エラーを返します。
-
func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) { ... }
(inzsyscall_*.go
): この自動生成された関数は、GoのSyscall
関数を介して実際のOSのsendmsg
システムコールを呼び出します。r0, _, e1 := Syscall(SYS_SENDMSG, uintptr(s), uintptr(unsafe.Pointer(msg)), uintptr(flags))
:Syscall
関数は、システムコール番号(SYS_SENDMSG
)と引数をuintptr
型で受け取ります。戻り値として、レジスタr0
(通常はシステムコールの戻り値)、r1
(未使用)、およびエラーコードe1
を返します。n = int(r0)
: ここで、システムコールが返したバイト数(r0
に格納されている)をint
型にキャストしてn
に代入しています。これが、バイト数をGoの関数に伝播させるための鍵となる部分です。if e1 != 0 { err = e1 }
: システムコールがエラーを返した場合、e1
をGoのエラー型に変換してerr
に代入します。
これらの変更により、Goのsyscall
パッケージは、sendmsg
システムコールのより完全な情報(送信バイト数)をGoプログラムに提供できるようになり、低レベルなネットワーク通信の制御がより柔軟になりました。
関連リンク
- Go Issue #7645: syscall: sendmsg should return n
- Go Code Review 81210043: syscall: add SendmsgN for BSD variants, Linux and Solaris
sendmsg(2)
man page (Linux): https://man7.org/linux/man-pages/man2/sendmsg.2.htmlsendmsg(2)
man page (FreeBSD): https://www.freebsd.org/cgi/man.cgi?query=sendmsg&sektion=2
参考にした情報源リンク
- Go言語の公式ドキュメント
- Unix系OSのシステムコールに関するmanページ
- Go言語の
syscall
パッケージのソースコード - Go言語のコードレビューシステム (Gerrit)
- Go言語のIssueトラッカー (GitHub Issues)
unsafe
パッケージに関するGoのドキュメントiovec
構造体に関する情報 (例:man 2 writev
)Msghdr
構造体に関する情報 (例:man 2 sendmsg
)- Go言語における
go generate
の仕組み - Go言語におけるクロスプラットフォーム開発の慣習
- Go言語の
syscall
パッケージの設計思想に関する議論 - Go言語のネットワークプログラミングに関する資料
- BSD系OS、Linux、Solarisにおけるシステムコールの挙動の違いに関する情報I have generated the detailed technical explanation in Markdown format, including all the required sections and leveraging the commit data and web search for context. I have ensured it is in Japanese and provides maximum detail.
I will now output the generated Markdown to standard output.
# [インデックス 18987] ファイルの概要
このコミットは、Go言語の`syscall`パッケージにおいて、`sendmsg`システムコールをラップする新しい関数`SendmsgN`を追加するものです。この新しい関数は、従来の`Sendmsg`関数がエラーのみを返していたのに対し、転送されたバイト数も返すように設計されています。これにより、`sendmsg`システムコールの結果をより詳細に、かつ効率的に取得できるようになります。対象となるOSはBSD系(Darwin, Dragonfly, FreeBSD, NetBSD, OpenBSD)、Linux、およびSolarisです。
## コミット
commit a7858a40a5bd8481fee839fddd29c305bb4093ef Author: Mikio Hara mikioh.mikioh@gmail.com Date: Sat Mar 29 09:28:40 2014 +0900
syscall: add SendmsgN for BSD variants, Linux and Solaris
SendmsgN is an alternate version Sendmsg that also returns
the number of bytes transferred, instead of just the error.
Update #7645
LGTM=aram, iant
R=iant, aram, bradfitz
CC=golang-codereviews
https://golang.org/cl/81210043
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/a7858a40a5bd8481fee839fddd29c305bb4093ef](https://github.com/golang/go/commit/a7858a40a5bd8481fee839fddd29c305bb4093ef)
## 元コミット内容
`syscall: add SendmsgN for BSD variants, Linux and Solaris`
このコミットは、`SendmsgN`という新しい関数を`syscall`パッケージに追加します。`SendmsgN`は、既存の`Sendmsg`関数の代替バージョンであり、転送されたバイト数とエラーの両方を返します。従来の`Sendmsg`はエラーのみを返していました。この変更は、Issue #7645 に関連しています。
## 変更の背景
従来の`syscall.Sendmsg`関数は、Unix系システムコールである`sendmsg(2)`をGoから呼び出すためのラッパーでしたが、その戻り値はエラー情報のみでした。しかし、`sendmsg(2)`システムコール自体は、データの送信が成功した場合に送信されたバイト数を返します。このバイト数は、特にネットワークプログラミングにおいて、実際にどれだけのデータが送信されたかを確認するために非常に重要です。例えば、部分的な送信が発生した場合や、OOB (Out-of-Band) データと通常データが混在する場合など、送信バイト数を正確に把握する必要があるシナリオが存在します。
Issue #7645("syscall: sendmsg should return n")では、この`sendmsg`システムコールが返すバイト数をGoの`syscall`パッケージからも取得できるようにすべきだという要望が挙げられていました。このコミットは、その要望に応える形で、バイト数を返す`SendmsgN`関数を導入し、より低レベルかつ詳細な制御を必要とするアプリケーション開発者に対して、必要な情報を提供するものです。
## 前提知識の解説
このコミットを理解するためには、以下の概念について理解しておく必要があります。
* **システムコール (System Call)**: オペレーティングシステム (OS) のカーネルが提供するサービスを、ユーザー空間のプログラムが利用するためのインターフェースです。ファイルI/O、ネットワーク通信、プロセス管理など、OSの機能にアクセスするために使用されます。`sendmsg`は、ソケットを介してデータを送信するためのシステムコールの一つです。
* **`sendmsg(2)`**: Unix系OSにおけるシステムコールで、ソケットを介してメッセージを送信するために使用されます。このシステムコールは、通常のデータだけでなく、補助データ(コントロールメッセージ、例: ファイルディスクリプタの転送など)や、送信先アドレスを一度に指定して送信する機能を提供します。成功時には送信されたバイト数を返します。
* **`syscall`パッケージ (Go言語)**: Go言語の標準ライブラリの一つで、低レベルなOSのシステムコールにアクセスするための機能を提供します。これにより、Goプログラムから直接OSの機能を利用できますが、OSに依存するコードとなるため、移植性には注意が必要です。
* **`Msghdr`構造体**: `sendmsg`システムコールで使用される構造体で、送信するメッセージに関する情報(データバッファ、補助データバッファ、送信先アドレスなど)をカプセル化します。
* **`Sockaddr`インターフェース**: ソケットアドレスを表すGoのインターフェースです。異なる種類のソケットアドレス(IPv4、IPv6、Unixドメインなど)を抽象化します。
* **`unsafe.Pointer`**: Go言語における`unsafe`パッケージの一部で、任意の型のポインタを表現するために使用されます。Goの型安全性をバイパスするため、慎重な使用が求められます。システムコールのように、C言語のAPIと直接やり取りする際に必要となることがあります。
* **`Syscall`関数 (Go言語)**: `syscall`パッケージ内で、実際のシステムコールを呼び出すための低レベルな関数です。引数としてシステムコール番号と、そのシステムコールに渡す引数を`uintptr`型で受け取ります。戻り値として、システムコールが返すレジスタの値(通常は戻り値とエラーコード)とエラーを返します。
* **BSD variants (BSD系OS)**: Berkeley Software Distributionを起源とするUnix系オペレーティングシステムの総称です。FreeBSD, OpenBSD, NetBSD, Dragonfly BSDなどが含まれます。macOS (Darwin) もBSD系カーネルをベースにしています。
* **Linux**: オープンソースのUnix系オペレーティングシステムカーネルです。
* **Solaris**: Sun Microsystems(現在はOracle)が開発したUnix系オペレーティングシステムです。
* **Issue #7645**: GoのIssueトラッカーで管理されている、この変更の背景となった具体的な要望やバグ報告です。この場合、「`sendmsg`がバイト数を返すようにすべき」という内容でした。
## 技術的詳細
このコミットの主要な変更点は、`syscall`パッケージ内の`sendmsg`システムコールラッパーの挙動を変更し、送信バイト数を返すようにしたことです。
具体的には、以下の変更が行われました。
1. **`sendmsg`関数のシグネチャ変更**:
各OS(BSD系、Linux、Solaris)の`syscall`パッケージ内部で定義されている低レベルな`sendmsg`関数のシグネチャが、`func sendmsg(s int, msg *Msghdr, flags int) (err error)`から`func sendmsg(s int, msg *Msghdr, flags int) (n int, err error)`に変更されました。これにより、システムコールが返すバイト数`n`がGoの関数からも直接取得できるようになります。
2. **`SendmsgN`関数の追加**:
新しい公開関数`SendmsgN(fd int, p, oob []byte, to Sockaddr, flags int) (n int, err error)`が追加されました。この関数は、従来の`Sendmsg`と同様の引数を受け取りますが、成功時に送信されたバイト数`n`を返します。
3. **`Sendmsg`関数の変更**:
既存の`Sendmsg(fd int, p, oob []byte, to Sockaddr, flags int) (err error)`関数は、内部的に新しく追加された`SendmsgN`を呼び出すように変更されました。これにより、`Sendmsg`の既存の挙動(エラーのみを返す)を維持しつつ、`SendmsgN`を通じてバイト数取得の機能を提供します。
4. **`zsyscall_*.go`ファイルの更新**:
`zsyscall_*.go`ファイルは、`go generate`コマンドによって自動生成されるファイルで、各OSアーキテクチャごとのシステムコールラッパーの実装を含んでいます。このコミットでは、これらのファイル内の`sendmsg`関数の呼び出し箇所が更新され、システムコールからの戻り値(バイト数)を適切に取得し、`n`として返すように修正されました。具体的には、`Syscall`関数の戻り値`r0`を`n`に代入する変更が行われています。
5. **エラーハンドリングの修正**:
`SendmsgN`関数内で、`to.sockaddr()`呼び出しでエラーが発生した場合の戻り値が`return 0, err`に変更され、バイト数`n`がゼロとして初期化されるようになりました。
6. **特殊なケースの考慮**:
`SendmsgN`の実装において、`oob`(Out-of-Bandデータ)のみが送信され、通常のデータ`p`が空の場合に、`n`を0に設定するロジックが追加されました。これは、一部のOSやプロトコルにおいて、OOBデータのみの送信がバイト数としてカウントされない場合があるため、その挙動を考慮したものです。
これらの変更により、Goの`syscall`パッケージは、`sendmsg`システムコールの完全な戻り値(送信バイト数とエラー)をGoプログラムに公開できるようになり、より柔軟で正確なネットワークプログラミングが可能になりました。
## コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、主に以下のファイル群に見られます。
1. **`src/pkg/syscall/syscall_bsd.go`**: BSD系OS向けの`Sendmsg`および`SendmsgN`の実装。
2. **`src/pkg/syscall/syscall_linux.go`**: Linux向けの`Sendmsg`および`SendmsgN`の実装。
3. **`src/pkg/syscall/syscall_solaris.go`**: Solaris向けの`Sendmsg`および`SendmsgN`の実装。
4. **`src/pkg/syscall/zsyscall_*.go`**: 各OSおよびアーキテクチャ(例: `zsyscall_darwin_amd64.go`, `zsyscall_linux_386.go`など)ごとの自動生成されたシステムコールラッパーファイル。これらのファイル内の`sendmsg`関数のシグネチャと、`Syscall`からの戻り値の処理が変更されています。
以下に、`src/pkg/syscall/syscall_bsd.go`と`src/pkg/syscall/zsyscall_darwin_amd64.go`の変更点を例として示します。他のOS向けのファイルも同様のパターンで変更されています。
**`src/pkg/syscall/syscall_bsd.go` の変更例:**
```diff
--- a/src/pkg/syscall/syscall_bsd.go
+++ b/src/pkg/syscall/syscall_bsd.go
@@ -389,15 +389,20 @@ func Recvmsg(fd int, p, oob []byte, flags int) (n, oobn int, recvflags int, from
return
}
-//sys sendmsg(s int, msg *Msghdr, flags int) (err error)
+//sys sendmsg(s int, msg *Msghdr, flags int) (n int, err error)
func Sendmsg(fd int, p, oob []byte, to Sockaddr, flags int) (err error) {
+\t_, err = SendmsgN(fd, p, oob, to, flags)
+\treturn
+}
+
+func SendmsgN(fd int, p, oob []byte, to Sockaddr, flags int) (n int, err error) {
var ptr unsafe.Pointer
var salen _Socklen
if to != nil {
ptr, salen, err = to.sockaddr()
if err != nil {
-\t\t\treturn
+\t\t\treturn 0, err
}
}
var msg Msghdr
@@ -420,10 +425,13 @@ func Sendmsg(fd int, p, oob []byte, to Sockaddr, flags int) (err error) {
}
msg.Iov = &iov
msg.Iovlen = 1
-\tif err = sendmsg(fd, &msg, flags); err != nil {
-\t\treturn
+\tif n, err = sendmsg(fd, &msg, flags); err != nil {
+\t\treturn 0, err
}
-\treturn
+\tif len(oob) > 0 && len(p) == 0 {
+\t\tn = 0
+\t}\n+\treturn n, nil
}
//sys kevent(kq int, change unsafe.Pointer, nchange int, event unsafe.Pointer, nevent int, timeout *Timespec) (n int, err error)
src/pkg/syscall/zsyscall_darwin_amd64.go
の変更例:
--- a/src/pkg/syscall/zsyscall_darwin_amd64.go
+++ b/src/pkg/syscall/zsyscall_darwin_amd64.go
@@ -185,8 +185,9 @@ func recvmsg(s int, msg *Msghdr, flags int) (n int, err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-func sendmsg(s int, msg *Msghdr, flags int) (err error) {
-\t_, _, e1 := Syscall(SYS_SENDMSG, uintptr(s), uintptr(unsafe.Pointer(msg)), uintptr(flags))\n
+func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) {
+\tr0, _, e1 := Syscall(SYS_SENDMSG, uintptr(s), uintptr(unsafe.Pointer(msg)), uintptr(flags))\n
+\tn = int(r0)\n
\tif e1 != 0 {\n
\t\terr = e1\n
\t}\n
コアとなるコードの解説
上記のコード変更は、Goのsyscall
パッケージがsendmsg
システムコールをどのように扱うかを根本的に変更しています。
-
//sys sendmsg(s int, msg *Msghdr, flags int) (n int, err error)
: これはGoのsyscall
パッケージがシステムコールをラップする際に使用する特殊なコメントです。このコメントは、go generate
ツールがzsyscall_*.go
のようなファイルを生成する際に、対応するC言語のシステムコール(この場合はsendmsg(2)
)のシグネチャをGoの関数シグネチャにマッピングするために使用されます。変更前はerr error
のみを返していましたが、変更後はn int, err error
を返すように明示されており、システムコールが返すバイト数n
がGoの関数に伝播されるようになります。 -
func Sendmsg(fd int, p, oob []byte, to Sockaddr, flags int) (err error) { ... }
: この関数は、Goのユーザーが通常利用する高レベルなラッパーです。変更後、この関数は新しく追加されたSendmsgN
を呼び出し、その戻り値のうちエラーのみを返しています。これは、既存のSendmsg
のAPI互換性を維持するための措置です。 -
func SendmsgN(fd int, p, oob []byte, to Sockaddr, flags int) (n int, err error) { ... }
: この関数が、このコミットで新しく導入された主要なAPIです。var ptr unsafe.Pointer
とvar salen _Socklen
:to Sockaddr
インターフェースから、実際のソケットアドレス構造体へのポインタと長さを取得するための変数です。unsafe.Pointer
は、Goの型システムをバイパスして任意のメモリを指すことができるため、C言語のシステムコールにGoのデータを渡す際に必要となります。if to != nil { ... }
: 送信先アドレスが指定されている場合、to.sockaddr()
を呼び出してソケットアドレスのポインタと長さを取得します。ここでエラーが発生した場合は、return 0, err
として、バイト数n
を0としてエラーを返します。var msg Msghdr
:sendmsg
システムコールに渡すMsghdr
構造体を初期化します。この構造体には、送信するデータ(p
)、補助データ(oob
)、送信先アドレスなどが設定されます。msg.Iov = &iov
とmsg.Iovlen = 1
:Msghdr
構造体のIov
フィールドは、送信するデータバッファの配列(iovec
構造体)を指します。ここでは単一のデータバッファ(p
)を送信するため、Iovlen
は1に設定されます。if n, err = sendmsg(fd, &msg, flags); err != nil { ... }
: ここが最も重要な変更点です。低レベルなsendmsg
関数を呼び出し、その戻り値であるバイト数n
とエラーerr
を直接受け取ります。エラーが発生した場合は、return 0, err
として処理を終了します。if len(oob) > 0 && len(p) == 0 { n = 0 }
: これは、OOBデータのみが送信され、通常のデータが送信されない場合の特殊なケースを処理します。一部のシステムでは、この場合にsendmsg
が返すバイト数が期待通りにならないことがあるため、明示的にn
を0に設定しています。return n, nil
: 成功した場合、送信されたバイト数n
とnil
エラーを返します。
-
func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) { ... }
(inzsyscall_*.go
): この自動生成された関数は、GoのSyscall
関数を介して実際のOSのsendmsg
システムコールを呼び出します。r0, _, e1 := Syscall(SYS_SENDMSG, uintptr(s), uintptr(unsafe.Pointer(msg)), uintptr(flags))
:Syscall
関数は、システムコール番号(SYS_SENDMSG
)と引数をuintptr
型で受け取ります。戻り値として、レジスタr0
(通常はシステムコールの戻り値)、r1
(未使用)、およびエラーコードe1
を返します。n = int(r0)
: ここで、システムコールが返したバイト数(r0
に格納されている)をint
型にキャストしてn
に代入しています。これが、バイト数をGoの関数に伝播させるための鍵となる部分です。if e1 != 0 { err = e1 }
: システムコールがエラーを返した場合、e1
をGoのエラー型に変換してerr
に代入します。
これらの変更により、Goのsyscall
パッケージは、sendmsg
システムコールのより完全な情報(送信バイト数)をGoプログラムに提供できるようになり、低レベルなネットワーク通信の制御がより柔軟になりました。
関連リンク
- Go Issue #7645: syscall: sendmsg should return n
- Go Code Review 81210043: syscall: add SendmsgN for BSD variants, Linux and Solaris
sendmsg(2)
man page (Linux): https://man7.org/linux/man-pages/man2/sendmsg.2.htmlsendmsg(2)
man page (FreeBSD): https://www.freebsd.org/cgi/man.cgi?query=sendmsg&sektion=2
参考にした情報源リンク
- Go言語の公式ドキュメント
- Unix系OSのシステムコールに関するmanページ
- Go言語の
syscall
パッケージのソースコード - Go言語のコードレビューシステム (Gerrit)
- Go言語のIssueトラッカー (GitHub Issues)
unsafe
パッケージに関するGoのドキュメントiovec
構造体に関する情報 (例:man 2 writev
)Msghdr
構造体に関する情報 (例:man 2 sendmsg
)- Go言語における
go generate
の仕組み - Go言語におけるクロスプラットフォーム開発の慣習
- Go言語の
syscall
パッケージの設計思想に関する議論 - Go言語のネットワークプログラミングに関する資料
- BSD系OS、Linux、Solarisにおけるシステムコールの挙動の違いに関する情報