[インデックス 13438] ファイルの概要
このコミットは、Go言語のsyscall
パッケージにおけるFreeBSD 386アーキテクチャ向けのsendfile
システムコールの実装に関するバグ修正です。具体的には、32ビットシステム上で64ビットのoff_t
パラメータが正しく扱われていなかった問題を解決し、アーキテクチャ固有のファイルにsendfile
の実装を分離することで、この問題を解消しています。
コミット
commit 7b6541a5932d72781e215b2e8e77928ec7ef6839
Author: David G. Andersen <dave.andersen@gmail.com>
Date: Tue Jul 3 08:16:43 2012 -0700
syscall: fix FreeBSD 386 sendfile
The previous version was not handling an off_t (64 bit)
parameter on 32 bit i386 systems. This patch splits sendfile
into two implementations in their respective arch-specific files.
Tested on FreeBSD amd64 and i386.
R=bradfitz
CC=golang-dev
https://golang.org/cl/6356048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7b6541a5932d72781e215b2e8e77928ec7ef6839
元コミット内容
このコミットは、Go言語のsyscall
パッケージにおいて、FreeBSDオペレーティングシステム上の32ビットi386アーキテクチャでsendfile
システムコールが正しく機能しない問題を修正するものです。以前の実装では、sendfile
システムコールが受け取るoffset
パラメータが64ビットのoff_t
型であるにもかかわらず、32ビットシステム上でその64ビット値を適切に扱えていませんでした。
この修正は、sendfile
の実装をアーキテクチャ固有のファイル(syscall_freebsd_386.go
とsyscall_freebsd_amd64.go
)に分離することで、この問題を解決しています。これにより、各アーキテクチャの特性に合わせたsendfile
の実装が可能となり、特に32ビットi386システムでのoff_t
の扱いが改善されました。この変更はFreeBSDのamd64とi386の両方でテストされ、動作が確認されています。
変更の背景
この変更の背景には、システムコールにおけるデータ型のサイズとアーキテクチャのビット幅の不一致があります。sendfile
システムコールは、ファイルディスクリプタ間でデータを直接転送するための効率的なメカニズムを提供します。このシステムコールは、転送を開始するファイル内のオフセットを指定するためにoff_t
型のパラメータを受け取ります。
問題は、FreeBSDの32ビットi386システムにおいて、off_t
型が64ビット幅であるにもかかわらず、Goのsyscall
パッケージの汎用的な実装がこの64ビット値を32ビットレジスタに収まるように扱おうとしていた点にありました。これにより、offset
が32ビットで表現できる範囲を超えると、正しくないオフセットがシステムコールに渡され、予期せぬ動作やエラーが発生していました。
この問題を解決するためには、32ビットアーキテクチャと64ビットアーキテクチャでsendfile
システムコールへの引数の渡し方を区別する必要がありました。特に、64ビット値を32ビットシステムコールに渡す際には、上位32ビットと下位32ビットに分割して渡すなどの特別な処理が必要になります。
前提知識の解説
システムコール (Syscall)
システムコールは、オペレーティングシステム (OS) のカーネルが提供するサービスを、ユーザー空間のプログラムが利用するためのインターフェースです。ファイル操作(読み書き)、プロセス管理(作成、終了)、ネットワーク通信など、OSの機能にアクセスする際に使用されます。Go言語のsyscall
パッケージは、これらのシステムコールをGoプログラムから直接呼び出すための機能を提供します。
sendfile
システムコール
sendfile
は、あるファイルディスクリプタから別のファイルディスクリプタへデータを直接転送するためのシステムコールです。通常、ファイルを読み込んでネットワークソケットに書き込む場合、データはカーネル空間からユーザー空間へ、そして再びカーネル空間へとコピーされます。sendfile
を使用すると、このユーザー空間へのコピーを省略し、カーネル空間内で直接データ転送を行うため、I/Oの効率が向上し、CPUオーバーヘッドが削減されます。これは特に、静的ファイルのWebサーバーなどで頻繁に利用されます。
sendfile
の一般的なシグネチャは以下のようになります(FreeBSDの場合、引数の順序や型は異なることがあります):
int sendfile(int fd, int s, off_t offset, size_t nbytes, struct sf_hdtr *hdtr, off_t *sbytes, int flags);
fd
: 入力ファイルディスクリプタ(通常はファイル)s
: 出力ファイルディスクリプタ(通常はソケット)offset
: 転送を開始する入力ファイル内のオフセット。このパラメータがoff_t
型です。nbytes
: 転送するバイト数。sbytes
: 実際に転送されたバイト数を格納するポインタ。
off_t
型
off_t
は、ファイルサイズやファイルオフセットを表すために使用されるデータ型です。そのサイズはシステムやアーキテクチャによって異なりますが、多くのUnix系システムでは、32ビットシステムでは32ビット、64ビットシステムでは64ビットとして定義されることが一般的です。しかし、FreeBSDの32ビットi386システムでは、ファイルサイズが2GBを超えるファイルを扱えるようにするため、off_t
が64ビットとして定義されています。これが、今回のバグの根本原因となりました。32ビットのレジスタしか持たないCPUで64ビットの値を扱う場合、通常は2つの32ビットレジスタに分割して扱う必要があります。
Syscall9
Go言語のsyscall
パッケージでは、Syscall
、Syscall6
、Syscall9
などの関数が提供されており、これらはそれぞれ異なる数の引数を持つシステムコールを呼び出すために使用されます。Syscall9
は最大9つの引数をシステムコールに渡すことができる関数です。これらの関数は、Goの型をCのシステムコールが期待するuintptr
型に変換して渡します。
unsafe.Pointer
Go言語のunsafe
パッケージは、Goの型システムが提供する安全性をバイパスする機能を提供します。unsafe.Pointer
は、任意の型のポインタを保持できる特殊なポインタ型で、ポインタ演算を可能にします。システムコールにポインタを渡す場合など、低レベルの操作が必要な場面で利用されますが、Goのメモリ安全性を損なう可能性があるため、慎重に使用する必要があります。
技術的詳細
このコミットの技術的な核心は、32ビットi386アーキテクチャにおける64ビットoff_t
の扱い方です。
元のsrc/pkg/syscall/syscall_freebsd.go
では、Sendfile
関数がSyscall9
を呼び出す際に、offset
パラメータをuintptr(*offset)
として直接渡していました。
// src/pkg/syscall/syscall_freebsd.go (変更前)
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)
// ...
}
ここで問題となるのは、*offset
がint64
(64ビット)であるにもかかわらず、uintptr
にキャストされる点です。32ビットシステムでは、uintptr
は32ビット幅です。したがって、uintptr(*offset)
とすると、*offset
の64ビット値の上位32ビットが切り捨てられてしまい、システムコールには誤ったオフセットが渡されていました。
この修正では、sendfile
の実装をアーキテクチャ固有のファイルに分離し、32ビットi386アーキテクチャ向けのsyscall_freebsd_386.go
でoffset
の扱いを変更しています。
// src/pkg/syscall/syscall_freebsd_386.go (変更後)
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((*offset)>>32), uintptr(count), 0, uintptr(unsafe.Pointer(&writtenOut)), 0, 0)
// ...
}
注目すべきは、Syscall9
の引数リストにuintptr((*offset)>>32)
が追加されている点です。これは、64ビットの*offset
値を2つの32ビット値に分割し、それぞれをシステムコールに渡すための一般的な手法です。*offset
の下位32ビットはuintptr(*offset)
として渡され、上位32ビットはuintptr((*offset)>>32)
として渡されます。FreeBSDの32ビットi386カーネルは、sendfile
システムコールにおいて、この2つの32ビット引数を組み合わせて64ビットのoff_t
として解釈するように設計されています。
一方、64ビットのamd64アーキテクチャでは、uintptr
も64ビット幅であるため、uintptr(*offset)
とすることで64ビット値全体が正しく渡されます。そのため、syscall_freebsd_amd64.go
では元のコードとほぼ同じ形式が維持されています。
// src/pkg/syscall/syscall_freebsd_amd64.go (変更後)
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)
// ...
}
このように、アーキテクチャごとにsendfile
の実装を分離し、それぞれのアーキテクチャの特性(特にポインタや整数型のサイズ)に合わせてシステムコールへの引数の渡し方を調整することで、クロスプラットフォームでの正確な動作を保証しています。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は以下の3つのファイルにわたります。
-
src/pkg/syscall/syscall_freebsd.go
:- 汎用的な
Sendfile
関数の実装が削除されました。このファイルは、アーキテクチャ固有ではないFreeBSDのシステムコール関連の定義を保持する役割に特化されます。
- 汎用的な
-
src/pkg/syscall/syscall_freebsd_386.go
:Sendfile
関数の新しい実装が追加されました。この実装は、32ビットi386アーキテクチャの特性に合わせて、64ビットのoffset
パラメータを2つの32ビット値に分割してSyscall9
に渡すように修正されています。import "unsafe"
が追加されました。
-
src/pkg/syscall/syscall_freebsd_amd64.go
:Sendfile
関数の新しい実装が追加されました。この実装は、64ビットamd64アーキテクチャの特性に合わせて、64ビットのoffset
パラメータを直接Syscall9
に渡すように記述されています。元の実装と論理的には同じですが、ファイルが分離されたことでアーキテクチャ固有のコードとして明確になりました。import "unsafe"
が追加されました。
src/pkg/syscall/syscall_freebsd.go
からの削除
--- a/src/pkg/syscall/syscall_freebsd.go
+++ b/src/pkg/syscall/syscall_freebsd.go
@@ -89,18 +89,6 @@ func Pipe(p []int) (err error) {
return
}
-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)
-
- written = int(writtenOut)
-
- if e1 != 0 {
- err = e1
- }
- return
-}
-
func GetsockoptIPMreqn(fd, level, opt int) (*IPMreqn, error) {
var value IPMreqn
vallen := _Socklen(SizeofIPMreqn)
src/pkg/syscall/syscall_freebsd_386.go
への追加
--- a/src/pkg/syscall/syscall_freebsd_386.go
+++ b/src/pkg/syscall/syscall_freebsd_386.go
@@ -4,6 +4,8 @@
package syscall
+import "unsafe"
+
func Getpagesize() int { return 4096 }
func TimespecToNsec(ts Timespec) int64 { return int64(ts.Sec)*1e9 + int64(ts.Nsec) }\n@@ -41,4 +43,16 @@ func (cmsg *Cmsghdr) SetLen(length int) {
cmsg.Len = uint32(length)
}
+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((*offset)>>32), uintptr(count), 0, uintptr(unsafe.Pointer(&writtenOut)), 0, 0)
+
+ written = int(writtenOut)
+
+ if e1 != 0 {
+ err = e1
+ }
+ return
+}
+
func Syscall9(num, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err Errno) // sic
src/pkg/syscall/syscall_freebsd_amd64.go
への追加
--- a/src/pkg/syscall/syscall_freebsd_amd64.go
+++ b/src/pkg/syscall/syscall_freebsd_amd64.go
@@ -4,6 +4,8 @@
package syscall
+import "unsafe"
+
func Getpagesize() int { return 4096 }
func TimespecToNsec(ts Timespec) int64 { return int64(ts.Sec)*1e9 + int64(ts.Nsec) }\n@@ -41,4 +43,16 @@ func (cmsg *Cmsghdr) SetLen(length int) {
cmsg.Len = uint32(length)
}
+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)
+
+ written = int(writtenOut)
+
+ if e1 != 0 {
+ err = e1
+ }
+ return
+}
+
func Syscall9(num, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err Errno)
コアとなるコードの解説
このコミットの核心は、syscall_freebsd_386.go
におけるSendfile
関数の実装です。
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((*offset)>>32), uintptr(count), 0, uintptr(unsafe.Pointer(&writtenOut)), 0, 0)
written = int(writtenOut)
if e1 != 0 {
err = e1
}
return
}
ここで注目すべきは、Syscall9
の第4引数と第5引数です。
uintptr(*offset)
: これはoffset
ポインタが指すint64
値の下位32ビット(またはシステムが32ビットの場合の全32ビット)をuintptr
にキャストして渡します。uintptr((*offset)>>32)
: これはoffset
ポインタが指すint64
値の上位32ビットを右シフトして抽出し、それをuintptr
にキャストして渡します。
FreeBSDの32ビットi386カーネルでは、sendfile
システムコールは64ビットのオフセットを2つの32ビット引数として受け取るように設計されています。具体的には、下位32ビットが最初の引数として、上位32ビットが次の引数として渡されることを期待します。この修正は、まさにその期待に応える形で引数を渡しています。
一方、syscall_freebsd_amd64.go
では、uintptr(*offset)
のみが渡されます。これは、amd64アーキテクチャではuintptr
が64ビット幅であり、int64
のoffset
値全体を単一のuintptr
引数としてシステムコールに渡すことができるためです。
この変更により、Goのsyscall
パッケージは、FreeBSDの異なるアーキテクチャ(32ビットi386と64ビットamd64)間でsendfile
システムコールを正しく呼び出すことができるようになり、特に32ビットi386システムでの64ビットオフセットの扱いに関するバグが解消されました。
関連リンク
- Go言語の
syscall
パッケージのドキュメント: https://pkg.go.dev/syscall - FreeBSD
sendfile
manページ (通常はman 2 sendfile
で参照可能): https://www.freebsd.org/cgi/man.cgi?query=sendfile&sektion=2 - Go言語のコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージにある
https://golang.org/cl/6356048
は、このGerritの変更リストへのリンクです)
参考にした情報源リンク
- Go言語のソースコード (特に
src/pkg/syscall
ディレクトリ): https://github.com/golang/go/tree/master/src/pkg/syscall - FreeBSDのシステムコールに関するドキュメントやソースコード (特に
sys/syscall.h
やカーネルのsys/kern/vfs_syscalls.c
など、sendfile
の実装に関連する部分) - 32ビットシステムにおける64ビット値の扱いに関する一般的な情報 (例: x86アーキテクチャの呼び出し規約、システムコールインターフェース)
- Go言語の
unsafe
パッケージに関するドキュメント: https://pkg.go.dev/unsafe - Go言語の
uintptr
型に関するドキュメント: https://pkg.go.dev/builtin#uintptr - Go言語の
int64
型に関するドキュメント: https://pkg.go.dev/builtin#int64 - Go言語のシステムプログラミングに関する記事やチュートリアル (例: Goにおけるシステムコールの呼び出し方)
- Stack Overflowや技術ブログでの
sendfile
、off_t
、32ビット/64ビットアーキテクチャ間のデータ型変換に関する議論。 - Go言語のメーリングリスト (golang-dev) のアーカイブ (コミットメッセージに
CC=golang-dev
とあるため、関連する議論が残っている可能性があります)。 - Go言語のIssue Tracker (Go Bug Tracker) (関連するバグ報告や議論がある可能性があります)。
- FreeBSDのハンドブックや開発者向けドキュメント。
man
ページ (特にsendfile(2)
、syscall(2)
、types(3)
など)。- Go言語のコンパイラとランタイムの内部動作に関する情報 (特に、Goの型がシステムコールにどのようにマッピングされるか)。
- Go言語のクロスコンパイルとアーキテクチャ固有のコード生成に関する情報。
- Go言語の
build tags
(例://go:build freebsd,386
) の使用方法に関する情報。