Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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.Pointeruintptr

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点です。

  1. Sendfile関数のoffset引数の型変更:

    • 変更前: offset int64 (値渡し)
    • 変更後: offset *int64 (ポインタ渡し) これは、FreeBSDのsendfileシステムコール自体のAPI変更に起因します。FreeBSDカーネルがoffsetの値を直接更新するようになったため、Go側でもポインタとして受け取るように変更されました。
  2. Syscall9へのoffset引数の渡し方変更:

    • 変更前: uintptr(offset)
    • 変更後: uintptr(*offset) Sendfile関数のoffset引数が*int64int64へのポインタ)になったため、Syscall9に渡す際には、そのポインタが指すint64の値(*offset)をuintptrに変換して渡す必要があります。これは、sendfileシステムコールがoffsetの「値」を期待しているためです。ただし、FreeBSDのsendfileシステムコールがoffsetをポインタとして受け取る場合、GoのSyscall9に渡す引数もポインタであるべきですが、このコミットではuintptr(*offset)と値に逆参照して渡しています。これは、FreeBSDのsendfileoffsetを「値」として受け取りつつ、内部でその値を更新するような特殊なセマンティクスを持っているか、あるいはGoのsyscallパッケージがFreeBSDのAPIをラップする際に、offsetのポインタを直接渡すのではなく、そのポインタが指す値を渡すように設計されているためと考えられます。

    補足: 一般的に、システムコールがポインタを期待する場合、uintptr(unsafe.Pointer(myPointer))のようにポインタ自体をuintptrに変換して渡します。しかし、このケースではuintptr(*offset)と値に逆参照しているため、FreeBSDのsendfileoffsetを「値」として受け取るが、その値が内部で更新される(例えば、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の呼び出しです。offsetint64型であったため、そのまま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の値が正しく渡されるようになります。writtenOutuint64型で定義され、そのアドレスがunsafe.Pointerを介してuintptrに変換され、システムコールに渡されています。これは、システムコールが書き込まれたバイト数をこのメモリ位置に書き込むことを期待しているためです。

この変更により、GoのsyscallパッケージがFreeBSDの最新のsendfile APIと互換性を持つようになり、ビルドエラーが解消されました。

関連リンク

参考にした情報源リンク