[インデックス 18721] ファイルの概要
このコミットは、Go言語のsyscall
パッケージにFreeBSD向けのAccept4
システムコールを追加するものです。Accept4
は、既存のAccept
システムコールに加えて、ソケットの振る舞いを制御するためのフラグ(例えば、非ブロッキングモードやクローズ・オン・エグゼック)を指定できる機能を提供します。これにより、ソケットの初期設定をより効率的に行えるようになります。
コミット
コミットハッシュ: a918c2c6d82c115fdcbc14e7ae754b3c278c1ae4
作者: Mikio Hara mikioh.mikioh@gmail.com
日付: Tue Mar 4 09:27:48 2014 +0900
概要: syscall: add Accept4 for freebsd
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a918c2c6d82c115fdcbc14e7ae754b3c278c1ae4
元コミット内容
syscall: add Accept4 for freebsd
Update #7186
Update #7428
LGTM=r, bradfitz
R=golang-codereviews, rsc, minux.ma, r, bradfitz
CC=golang-codereviews
https://golang.org/cl/68880043
変更の背景
この変更の背景には、ネットワークプログラミングにおける効率性と堅牢性の向上が挙げられます。従来のaccept()
システムコールでは、新しい接続を受け入れた後に、そのソケットに対して追加の操作(例えば、非ブロッキングモードへの設定や、exec
呼び出し時にソケットを自動的に閉じる設定)を行う必要がありました。これらの操作は通常、fcntl()
やsetsockopt()
といった別のシステムコールを呼び出すことで実現されます。
しかし、accept()
とそれに続くfcntl()
やsetsockopt()
の呼び出しは、特に高負荷なサーバーアプリケーションにおいて、システムコールオーバーヘッドを増加させる要因となります。また、これらの操作がアトミックに行われないため、競合状態(race condition)が発生する可能性もゼロではありません。例えば、accept()
でソケットが作成されてからSOCK_CLOEXEC
フラグが設定されるまでの短い期間にfork()
とexec()
が実行された場合、意図せず子プロセスにソケットディスクリプタが漏洩する可能性があります。
accept4()
システムコールは、これらの問題を解決するために導入されました。accept4()
は、accept()
の機能に加えて、ソケット作成時に直接フラグを指定できる引数(flags
)を持ちます。これにより、SOCK_NONBLOCK
(非ブロッキングモード)やSOCK_CLOEXEC
(クローズ・オン・エグゼック)といった重要なソケットオプションを、単一のシステムコール呼び出しでアトミックに設定することが可能になります。
このコミットは、Go言語のsyscall
パッケージがFreeBSD環境でaccept4()
の機能を利用できるようにすることで、Goで記述されたネットワークアプリケーションのパフォーマンス向上と、より堅牢なソケットハンドリングを実現することを目的としています。参照されているIssue #7186と#7428も、このAccept4
のFreeBSDへの追加を求めるものでした。
前提知識の解説
1. accept()
とaccept4()
システムコール
-
accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
:- 指定されたソケット
sockfd
上で保留中の接続キューの最初の接続を抽出します。 - 新しいソケットディスクリプタを返します。この新しいソケットは、受け入れられた接続のエンドポイントです。
addr
とaddrlen
は、接続しているピアのソケットアドレス情報を格納するために使用されます。- この関数は、新しいソケットディスクリプタを返しますが、そのソケットの振る舞いを制御するためのフラグ(非ブロッキング、クローズ・オン・エグゼックなど)を直接設定する手段はありません。
- 指定されたソケット
-
accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags)
:accept()
と同様に新しい接続を受け入れ、新しいソケットディスクリプタを返します。- 大きな違いは、追加の
flags
引数があることです。このflags
引数を使用することで、新しいソケットの振る舞いをアトミックに設定できます。 - 主なフラグには以下のものがあります。
SOCK_NONBLOCK
: 新しいソケットを非ブロッキングモードに設定します。これにより、ソケットに対する読み書き操作が即座に完了しない場合でも、呼び出し元がブロックされることなく制御を返します。SOCK_CLOEXEC
: 新しいソケットにクローズ・オン・エグゼック(close-on-exec)フラグを設定します。これは、execve()
ファミリーの関数が呼び出されたときに、このソケットディスクリプタが自動的に閉じられることを意味します。これにより、子プロセスへの意図しないディスクリプタの漏洩を防ぎ、リソースリークやセキュリティリスクを低減します。
2. Go言語のsyscall
パッケージ
Go言語のsyscall
パッケージは、オペレーティングシステム(OS)の低レベルなシステムコールへのインターフェースを提供します。Goの標準ライブラリの多くの部分(特にネットワークやファイルI/O)は、このパッケージを介してOSの機能を利用しています。
- OS固有の実装:
syscall
パッケージは、OSごとに異なるシステムコールの定義や呼び出し規約を抽象化しています。例えば、FreeBSD向けのシステムコールはsyscall_freebsd.go
やzsyscall_freebsd_*.go
といったファイルで定義されます。 zsyscall_*.go
ファイル: これらのファイルは通常、Goのツールによって自動生成されます。OSのシステムコール定義(例えば、C言語のヘッダファイル)から、Goの関数シグネチャと、実際のシステムコールを呼び出すための低レベルなコード(Syscall
、Syscall6
などを使用)を生成します。Syscall
/Syscall6
: これらはsyscall
パッケージが提供する低レベルな関数で、直接OSのシステムコールを呼び出します。Syscall6
は、最大6つの引数をシステムコールに渡すことができるバージョンです。引数はuintptr
型にキャストされ、ポインタはunsafe.Pointer
を介して渡されます。
3. RawSockaddrAny
と_Socklen
RawSockaddrAny
: これは、様々な種類のソケットアドレス構造体(IPv4のsockaddr_in
、IPv6のsockaddr_in6
など)を汎用的に扱うためのGoの構造体です。システムコールから返される生のアドレスデータを保持するために使用されます。_Socklen
: ソケットアドレス構造体の長さを表す型です。C言語のsocklen_t
に対応します。
4. FreeBSD
FreeBSDは、UNIX系のオペレーティングシステムの一つで、高性能なネットワークスタックと堅牢性で知られています。Go言語は、FreeBSDを含む複数のOSをサポートしており、それぞれのOSの特性に合わせたシステムコールインターフェースを提供しています。
技術的詳細
このコミットは、Goのsyscall
パッケージ内でFreeBSDのaccept4
システムコールをGoの関数として利用可能にするための実装を含んでいます。
-
syscall_freebsd.go
におけるAccept4
関数の追加:- Goのユーザーが直接呼び出すための高レベルな
Accept4
関数が追加されています。 - この関数は、ファイルディスクリプタ
fd
とフラグflags
を受け取ります。 - 内部的には、
accept4
という低レベルなシステムコールラッパーを呼び出します。 accept4
システムコールから返された生のアドレス情報(RawSockaddrAny
)を、GoのSockaddr
インターフェースに変換するanyToSockaddr
関数を使用しています。- 変換中にエラーが発生した場合や、
RawSockaddrAny
のサイズが不足している場合は、適切なエラー処理(ソケットのクローズやパニック)を行います。
- Goのユーザーが直接呼び出すための高レベルな
-
syscall_freebsd.go
における//sys accept4
ディレクティブの追加:- これはGoの
syscall
パッケージのコード生成ツール(mksyscall.go
など)に対する指示です。 - この行があることで、ツールはFreeBSDの
accept4
システムコールに対応するGoの低レベルなラッパー関数を自動生成します。 - 生成される関数は、
zsyscall_freebsd_386.go
、zsyscall_freebsd_amd64.go
、zsyscall_freebsd_arm.go
といったアーキテクチャ固有のファイルに配置されます。
- これはGoの
-
zsyscall_freebsd_*.go
ファイルにおけるaccept4
の実装:- これらのファイルは自動生成されたもので、実際の
accept4
システムコールを呼び出すためのコードが含まれています。 Syscall6
関数を使用して、OSのカーネルに直接システムコールを要求します。SYS_ACCEPT4
は、FreeBSDにおけるaccept4
システムコールの番号を定義する定数です。- 引数(
fd
,rsa
,addrlen
,flags
)は、uintptr
型にキャストされ、ポインタはunsafe.Pointer
を介して渡されます。これは、Goの型安全性を一時的にバイパスして、C言語のシステムコールインターフェースと互換性のある形式でデータを渡すために必要です。 - システムコールからの戻り値(
r0
)とエラーコード(e1
)をGoの型に変換し、適切なエラーを返します。
- これらのファイルは自動生成されたもので、実際の
この一連の変更により、GoのプログラムはFreeBSD上でAccept4
関数を呼び出すだけで、SOCK_NONBLOCK
やSOCK_CLOEXEC
といったフラグをアトミックに設定した新しいソケット接続を受け入れることができるようになります。これは、特に高性能なネットワークサーバーを構築する際に、システムコールの回数を減らし、競合状態のリスクを低減する上で非常に重要です。
コアとなるコードの変更箇所
src/pkg/syscall/syscall_freebsd.go
@@ -103,6 +103,24 @@ func SetsockoptIPMreqn(fd, level, opt int, mreq *IPMreqn) (err error) {
return setsockopt(fd, level, opt, unsafe.Pointer(mreq), unsafe.Sizeof(*mreq))\n }\n \n+func Accept4(fd, flags int) (nfd int, sa Sockaddr, err error) {\n+\tvar rsa RawSockaddrAny\n+\tvar len _Socklen = SizeofSockaddrAny\n+\tnfd, err = accept4(fd, &rsa, &len, flags)\n+\tif err != nil {\n+\t\treturn\n+\t}\n+\tif len > SizeofSockaddrAny {\n+\t\tpanic(\"RawSockaddrAny too small\")\n+\t}\n+\tsa, err = anyToSockaddr(&rsa)\n+\tif err != nil {\n+\t\tClose(nfd)\n+\t\tnfd = 0\n+\t}\n+\treturn\n+}\n+\n /*\n * Exposed directly\n */\n@@ -191,6 +209,7 @@ func SetsockoptIPMreqn(fd, level, opt int, mreq *IPMreqn) (err error) {\n //sys munmap(addr uintptr, length uintptr) (err error)\n //sys readlen(fd int, buf *byte, nbuf int) (n int, err error) = SYS_READ\n //sys writelen(fd int, buf *byte, nbuf int) (n int, err error) = SYS_WRITE\n+//sys accept4(fd int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (nfd int, err error)\n \n /*\n * Unimplemented
src/pkg/syscall/zsyscall_freebsd_386.go
(amd64, armも同様)
@@ -1301,3 +1301,14 @@ func writelen(fd int, buf *byte, nbuf int) (n int, err error) {\n \t}\n \treturn\n }\n+\n+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n+\n+func accept4(fd int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (nfd int, err error) {\n+\tr0, _, e1 := Syscall6(SYS_ACCEPT4, uintptr(fd), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags), 0, 0)\n+\tnfd = int(r0)\n+\tif e1 != 0 {\n+\t\terr = e1\n+\t}\n+\treturn\n+}\n```
## コアとなるコードの解説
### `src/pkg/syscall/syscall_freebsd.go`の変更点
1. **`Accept4`関数の追加**:
* `func Accept4(fd, flags int) (nfd int, sa Sockaddr, err error)`: この関数は、GoのユーザーがFreeBSDの`accept4`システムコールを呼び出すための公開インターフェースです。
* `fd`: リッスンしているソケットのファイルディスクリプタ。
* `flags`: `SOCK_NONBLOCK`や`SOCK_CLOEXEC`などのフラグ。
* `nfd`: 新しく受け入れられた接続のファイルディスクリプタ。
* `sa`: 接続しているピアのソケットアドレス情報(`Sockaddr`インターフェース型)。
* `err`: エラー情報。
* `var rsa RawSockaddrAny`: 生のソケットアドレスデータを保持するための一時変数。
* `var len _Socklen = SizeofSockaddrAny`: `accept4`システムコールに渡すアドレス構造体のサイズ。
* `nfd, err = accept4(fd, &rsa, &len, flags)`: ここで、自動生成された低レベルな`accept4`ラッパー関数が呼び出され、実際のシステムコールが実行されます。
* `if err != nil { return }`: システムコールがエラーを返した場合、すぐにエラーを返します。
* `if len > SizeofSockaddrAny { panic("RawSockaddrAny too small") }`: `accept4`システムコールが返したアドレスの長さが、Goで確保した`RawSockaddrAny`のサイズを超えている場合、パニックを発生させます。これは通常、予期せぬ状況やOSの挙動の変更を示唆します。
* `sa, err = anyToSockaddr(&rsa)`: 生のソケットアドレスデータ`rsa`を、Goの`Sockaddr`インターフェース型に変換します。これにより、ユーザーは型安全な方法でアドレス情報にアクセスできます。
* `if err != nil { Close(nfd); nfd = 0 }`: `anyToSockaddr`での変換中にエラーが発生した場合、新しく受け入れられたソケットディスクリプタ`nfd`を閉じ、`nfd`をゼロにリセットしてからエラーを返します。これは、ソケットディスクリプタのリークを防ぐための重要なクリーンアップ処理です。
2. **`//sys accept4(...)`ディレクティブの追加**:
* このコメント行は、Goの`mksyscall`ツールが`accept4`システムコールのGoラッパーを生成するための指示です。
* `accept4(fd int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (nfd int, err error)`というシグネチャに基づいて、各アーキテクチャ(386, amd64, arm)向けの`zsyscall_freebsd_*.go`ファイルに実際のシステムコール呼び出しコードが生成されます。
### `src/pkg/syscall/zsyscall_freebsd_*.go`の変更点
1. **`accept4`関数の自動生成**:
* `func accept4(fd int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (nfd int, err error)`: この関数は、`//sys`ディレクティブに基づいて自動生成された、FreeBSDの`accept4`システムコールを直接呼び出すための低レベルなラッパーです。
* `r0, _, e1 := Syscall6(SYS_ACCEPT4, uintptr(fd), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags), 0, 0)`:
* `Syscall6`: 6つの引数を取るシステムコールを呼び出すためのGoの関数。
* `SYS_ACCEPT4`: FreeBSDにおける`accept4`システムコールの番号を表す定数。
* `uintptr(fd)`: ファイルディスクリプタ`fd`を`uintptr`型にキャストして渡します。
* `uintptr(unsafe.Pointer(rsa))`: `RawSockaddrAny`構造体へのポインタを`unsafe.Pointer`を介して`uintptr`にキャストし、システムコールに渡します。これにより、Goの型システムを一時的にバイパスして、C言語のポインタとして扱えるようにします。
* `uintptr(unsafe.Pointer(addrlen))`: `_Socklen`型のアドレス長へのポインタも同様に渡します。
* `uintptr(flags)`: フラグを`uintptr`型にキャストして渡します。
* `0, 0`: 残りの未使用の引数。
* `r0`: システムコールからの戻り値(通常は新しいファイルディスクリプタ)。
* `e1`: システムコールからのエラーコード。
* `nfd = int(r0)`: 戻り値`r0`を`int`型に変換して`nfd`に代入します。
* `if e1 != 0 { err = e1 }`: エラーコード`e1`がゼロでない場合、それをGoのエラーとして返します。
これらの変更により、Goの`syscall`パッケージはFreeBSD上で`accept4`システムコールの高度な機能を利用できるようになり、Goで記述されたネットワークアプリケーションの効率性と信頼性が向上します。
## 関連リンク
* Go Issue 7186: [https://code.google.com/p/go/issues/detail?id=7186](https://code.google.com/p/go/issues/detail?id=7186)
* Go Issue 7428: [https://code.google.com/p/go/issues/detail?id=7428](https://code.google.com/p/go/issues/detail?id=7428)
* Gerrit Code Review: [https://golang.org/cl/68880043](https://golang.org/cl/68880043)
## 参考にした情報源リンク
* `accept4(2)` man page (Linux): [https://man7.org/linux/man-pages/man2/accept4.2.html](https://man7.org/linux/man-pages/man2/accept4.2.html) (FreeBSDのmanページも同様の機能を提供します)
* Go `syscall` package documentation: [https://pkg.go.dev/syscall](https://pkg.go.dev/syscall)
* Go `unsafe` package documentation: [https://pkg.go.dev/unsafe](https://pkg.go.dev/unsafe)
* Go `mksyscall` tool source code (参考): [https://github.com/golang/go/blob/master/src/syscall/mksyscall.go](https://github.com/golang/go/blob/master/src/syscall/mksyscall.go)
* `SOCK_NONBLOCK` and `SOCK_CLOEXEC` flags (general socket programming concepts).