[インデックス 13407] ファイルの概要
このコミットは、Go言語のsyscall
パッケージにおけるFreeBSD固有の実装、具体的にはPipe
関数とSendfile
関数に関連するビルドエラーを修正するものです。FreeBSDのAPI変更にGoのコードが追従できていなかったために発生した問題を解決しています。
コミット
commit 596762e9a16a250ed5670b227639f9e66140b616
Author: Russ Cox <rsc@golang.org>
Date: Mon Jun 25 20:45:18 2012 -0400
syscall: fix build
I missed the API change in the last FreeBSD CL, but the tool caught it.
TBR=bradfitz
CC=golang-dev
https://golang.org/cl/6331063
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/596762e9a16a250ed5670b227639f9e66140b616
元コミット内容
syscall: fix build
I missed the API change in the last FreeBSD CL, but the tool caught it.
TBR=bradfitz
CC=golang-dev
https://golang.org/cl/6331063
変更の背景
このコミットは、Go言語のsyscall
パッケージがFreeBSD上でビルドエラーを起こしていた問題を修正するために行われました。コミットメッセージにある「I missed the API change in the last FreeBSD CL, but the tool caught it.」という記述から、FreeBSDのシステムコールAPI、特にsendfile
関数のシグネチャが最近変更されたにもかかわらず、Goのsyscall
パッケージがその変更に追従できていなかったことが伺えます。
2012年頃のFreeBSDでは、sendfile
システムコールのAPIに重要な変更が加えられていました。これは、特に高スループットのアプリケーション向けにパフォーマンスと効率を向上させることを目的としており、ディスクI/Oでのブロッキングを防ぐための非同期I/Oやゼロコピー最適化が導入され始めていました。このようなAPIの変更は、既存のコードベースに影響を与え、Goのような低レベルのシステムコールをラップする言語では、その変更に合わせてコードを更新する必要が生じます。
このコミットは、Goの開発者がFreeBSDのAPI変更を見落としていたものの、ビルドツール(おそらくGoのリンターやコンパイラ)がその不整合を検出し、ビルドエラーとして報告したため、修正が必要になったという経緯を示しています。
前提知識の解説
Go言語のsyscall
パッケージ
Go言語のsyscall
パッケージは、オペレーティングシステムが提供する低レベルのシステムコールにアクセスするためのインターフェースを提供します。これにより、Goプログラムからファイル操作、ネットワーク通信、プロセス管理など、OSカーネルの機能に直接アクセスすることが可能になります。しかし、システムコールはOSに強く依存するため、syscall
パッケージの具体的な実装はOSごとに異なります(例: syscall_freebsd.go
, syscall_linux.go
など)。
Syscall9
関数
syscall
パッケージには、引数の数に応じてSyscall
, Syscall6
, Syscall9
などの関数が用意されています。これらは、指定されたシステムコール番号(トラップ番号)と最大9つの引数を受け取り、システムコールを実行します。これらの引数はすべてuintptr
型として渡されます。uintptr
はポインタを保持できる整数型であり、Goのガベージコレクタの管理外のメモリを指す場合や、システムコールに直接アドレスを渡す場合に使用されます。
unsafe.Pointer
とuintptr
Go言語では、通常、ポインタは型安全に扱われますが、syscall
パッケージのように低レベルの操作を行う際には、unsafe
パッケージのunsafe.Pointer
を使用することがあります。
unsafe.Pointer
: 任意の型のポインタを保持できる汎用ポインタです。型チェックをバイパスするため、非常に注意して使用する必要があります。uintptr
: ポインタを保持できる整数型です。unsafe.Pointer
と相互変換が可能ですが、uintptr
自体はポインタではないため、ガベージコレクタの対象外です。
Syscall9
にポインタを引数として渡す場合、Goのポインタ(例: *int64
)をunsafe.Pointer
に変換し、さらにuintptr
に変換して渡す必要があります。この変換は、システムコール呼び出しの引数リスト内で直接行うことが重要です。uintptr(unsafe.Pointer(&myVar))
のように記述することで、Goのガベージコレクタに対して、システムコールが完了するまで&myVar
が参照するメモリを移動したり解放したりしないように指示する役割も果たします。
sendfile
システムコール
sendfile
は、ファイルディスクリプタから別のファイルディスクリプタへデータを直接転送するためのシステムコールです。特にネットワークソケットへのファイル転送において、ユーザー空間へのデータコピーを介さずにカーネル空間内で直接転送を行う「ゼロコピー」を実現することで、高いパフォーマンスを発揮します。これは、Webサーバーが静的ファイルを配信する際などに非常に有効です。
FreeBSDのsendfile
API変更 (2012年頃)
2012年頃のFreeBSDでは、sendfile(2)
システムコールのAPIが変更され、特にoffset
引数の扱いが変わりました。以前は値渡しだったものが、ポインタ渡しに変更された可能性があります。これは、sendfile
が非同期I/Oをサポートし、オフセット値をカーネルが内部で更新できるようにするため、あるいはより効率的なデータ転送メカニズムを導入するための一環として行われたと考えられます。このような変更は、システムコールのシグネチャに直接影響を与え、それを利用するアプリケーションやライブラリは対応が必要となります。
技術的詳細
このコミットの技術的詳細は、syscall_freebsd.go
ファイル内のSendfile
関数のシグネチャと、Syscall9
への引数渡し方の変更に集約されます。
変更前:
func Sendfile(outfd int, infd int, offset int64, count int) (written int, err error) {
var writtenOut uint64 = 0
_, _, e1 := Syscall9(SYS_SENDFILE, uintptr(infd), uintptr(outfd), uintptr(offset), uintptr(count), 0, uintptr(unsafe.Pointer(&writtenOut)), 0, 0, 0)
// ...
}
変更後:
func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err error) {
var writtenOut uint64 = 0
_, _, e1 := Syscall9(SYS_SENDFILE, uintptr(infd), uintptr(outfd), uintptr(*offset), uintptr(count), 0, uintptr(unsafe.Pointer(&writtenOut)), 0, 0, 0)
// ...
}
主な変更点は以下の2点です。
-
Sendfile
関数のoffset
引数の型変更:- 変更前:
offset int64
(値渡し) - 変更後:
offset *int64
(ポインタ渡し) これは、FreeBSDのsendfile
システムコール自体のAPI変更に起因します。FreeBSDカーネルがoffset
の値を直接更新するようになったため、Go側でもポインタとして受け取るように変更されました。
- 変更前:
-
Syscall9
へのoffset
引数の渡し方変更:- 変更前:
uintptr(offset)
- 変更後:
uintptr(*offset)
Sendfile
関数のoffset
引数が*int64
(int64
へのポインタ)になったため、Syscall9
に渡す際には、そのポインタが指すint64
の値(*offset
)をuintptr
に変換して渡す必要があります。これは、sendfile
システムコールがoffset
の「値」を期待しているためです。ただし、FreeBSDのsendfile
システムコールがoffset
をポインタとして受け取る場合、GoのSyscall9
に渡す引数もポインタであるべきですが、このコミットではuintptr(*offset)
と値に逆参照して渡しています。これは、FreeBSDのsendfile
がoffset
を「値」として受け取りつつ、内部でその値を更新するような特殊なセマンティクスを持っているか、あるいはGoのsyscall
パッケージがFreeBSDのAPIをラップする際に、offset
のポインタを直接渡すのではなく、そのポインタが指す値を渡すように設計されているためと考えられます。
補足: 一般的に、システムコールがポインタを期待する場合、
uintptr(unsafe.Pointer(myPointer))
のようにポインタ自体をuintptr
に変換して渡します。しかし、このケースではuintptr(*offset)
と値に逆参照しているため、FreeBSDのsendfile
がoffset
を「値」として受け取るが、その値が内部で更新される(例えば、sendfile
が完了した時点での転送済みバイト数を返すために、offset
の値を更新する)というセマンティクスを持っている可能性が高いです。Goのsyscall
パッケージは、このようなOS固有のセマンティクスに合わせて実装されています。 - 変更前:
コアとなるコードの変更箇所
--- a/src/pkg/syscall/syscall_freebsd.go
+++ b/src/pkg/syscall/syscall_freebsd.go
@@ -89,9 +89,9 @@ func Pipe(p []int) (err error) {
return
}
-func Sendfile(outfd int, infd int, offset int64, count int) (written int, err error) {
+func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err error) {
var writtenOut uint64 = 0
- _, _, e1 := Syscall9(SYS_SENDFILE, uintptr(infd), uintptr(outfd), uintptr(offset), uintptr(count), 0, uintptr(unsafe.Pointer(&writtenOut)), 0, 0, 0)
+ _, _, e1 := Syscall9(SYS_SENDFILE, uintptr(infd), uintptr(outfd), uintptr(*offset), uintptr(count), 0, uintptr(unsafe.Pointer(&writtenOut)), 0, 0, 0)
written = int(writtenOut)
コアとなるコードの解説
このコミットでは、src/pkg/syscall/syscall_freebsd.go
ファイル内のSendfile
関数の定義が変更されています。
-
func Sendfile(outfd int, infd int, offset int64, count int) (written int, err error)
- 変更前の
Sendfile
関数のシグネチャです。offset
引数がint64
型で、値渡しされていました。
- 変更前の
-
func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err error)
- 変更後の
Sendfile
関数のシグネチャです。offset
引数が*int64
型に変更され、ポインタ渡しになりました。これは、FreeBSDのsendfile
システムコールがoffset
をポインタとして受け取るように変更されたことに対応するためです。
- 変更後の
-
_, _, e1 := Syscall9(SYS_SENDFILE, uintptr(infd), uintptr(outfd), uintptr(offset), uintptr(count), 0, uintptr(unsafe.Pointer(&writtenOut)), 0, 0, 0)
- 変更前の
Syscall9
の呼び出しです。offset
がint64
型であったため、そのままuintptr(offset)
として渡されていました。
- 変更前の
-
_, _, e1 := Syscall9(SYS_SENDFILE, uintptr(infd), uintptr(outfd), uintptr(*offset), uintptr(count), 0, uintptr(unsafe.Pointer(&writtenOut)), 0, 0, 0)
- 変更後の
Syscall9
の呼び出しです。offset
が*int64
型になったため、ポインタが指す値(*offset
)を逆参照し、その値をuintptr(*offset)
としてSyscall9
に渡しています。これにより、FreeBSDのsendfile
システムコールが期待するoffset
の値が正しく渡されるようになります。writtenOut
はuint64
型で定義され、そのアドレスがunsafe.Pointer
を介してuintptr
に変換され、システムコールに渡されています。これは、システムコールが書き込まれたバイト数をこのメモリ位置に書き込むことを期待しているためです。
- 変更後の
この変更により、Goのsyscall
パッケージがFreeBSDの最新のsendfile
APIと互換性を持つようになり、ビルドエラーが解消されました。
関連リンク
- Go CL 6331063: https://golang.org/cl/6331063
参考にした情報源リンク
- Go言語の
syscall
パッケージとSyscall9
、unsafe.Pointer
、uintptr
に関する情報: - FreeBSDの
sendfile
API変更に関する情報 (2012年頃): - Go言語の
syscall
パッケージのFreeBSD固有の実装に関する情報: