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

[インデックス 14237] ファイルの概要

このコミットは、Go言語のsyscallパッケージにおけるSendfile()システムコールに、データ競合検出器のためのアノテーションを追加するものです。具体的には、各OS固有のSendfile関数の名前を非公開のsendfileに変更し、syscall_unix.goにデータ競合検出器の同期プリミティブを呼び出す新しい公開Sendfileラッパー関数を導入しています。これにより、Sendfileシステムコール使用時のデータ競合の検出精度が向上します。

コミット

commit c242aa34cc3781dcbd53f6e6bd952d357656cd11
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Mon Oct 29 23:15:06 2012 +0400

    syscalls: annotate Sendfile() for race detector
    Fixes #4306.
    
    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/6816054

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/c242aa34cc3781dcbd53f6e6bd952d357656cd11

元コミット内容

syscalls: annotate Sendfile() for race detector
Fixes #4306.

変更の背景

Go言語の並行処理モデルでは、複数のGoroutineが共有リソースにアクセスする際にデータ競合(data race)が発生する可能性があります。データ競合は、少なくとも1つのアクセスが書き込みであり、かつ適切な同期メカニズムが使用されていない場合に、予測不能な動作やバグを引き起こす原因となります。Goには、このようなデータ競合をランタイムで検出するための強力なツールである「データ競合検出器(Race Detector)」が組み込まれています。

Sendfile()システムコールは、ファイルディスクリプタ間でデータを効率的に転送するための低レベルなI/O操作です。このような低レベルのシステムコールは、内部的にカーネルの共有リソースにアクセスすることがあり、Goのランタイムやデータ競合検出器がその操作を正確に追跡できない場合、誤った競合の報告や、本来検出されるべき競合の見逃しが発生する可能性があります。

このコミットは、Sendfile()システムコールがデータ競合検出器によって適切に監視され、正確な競合検出が行われるようにするためのアノテーションを追加することを目的としています。コミットメッセージにあるFixes #4306は、この変更が解決する特定の課題(おそらくSendfileに関連するデータ競合の誤検知や未検知の問題)を示しています。

前提知識の解説

Goのデータ競合検出器 (Race Detector)

Goのデータ競合検出器は、Goプログラムの並行処理におけるデータ競合を特定するための動的解析ツールです。go build -racego run -racego test -raceのように-raceフラグを付けてビルドまたは実行することで有効になります。有効にすると、プログラムの実行中にメモリへのアクセスを監視し、複数のGoroutineが同期なしに同じメモリ位置にアクセスし、少なくとも1つが書き込みを行う場合に警告を発します。これにより、発見が困難な並行処理のバグを特定し、修正するのに役立ちます。

sendfileシステムコール

sendfileは、Unix系OSで提供されるシステムコールの一つで、あるファイルディスクリプタから別のファイルディスクリプタへデータを直接転送するために使用されます。特に、ファイルからネットワークソケットへのデータ転送において非常に効率的です。通常のread()write()の組み合わせでは、データがカーネル空間からユーザー空間へ、そして再びカーネル空間へとコピーされる必要がありますが、sendfileはデータ転送をカーネル内部で完結させることで、これらの余分なデータコピーを排除します(「ゼロコピー」と呼ばれる最適化)。これにより、CPUオーバーヘッドが削減され、I/Oスループットが向上します。

Goのsyscallパッケージは、OSのシステムコールを直接呼び出すための低レベルなインターフェースを提供します。syscall.Sendfile関数は、このsendfileシステムコールをGoから呼び出すためのラッパーです。

raceReleaseMerge

raceReleaseMergeは、Goのランタイム内部、特にデータ競合検出器のコンテキストで使用される可能性のある関数です。これはGoの標準ライブラリの公開APIではありません。データ競合検出器は、共有メモリへのアクセスを追跡し、適切な同期プリミティブ(ミューテックス、チャネルなど)が使用されているかを検証します。しかし、Sendfileのような低レベルのシステムコールは、OSカーネル内で動作するため、Goランタイムがその内部的なメモリ操作を直接監視することは困難です。

raceReleaseMergeのような関数は、特定のメモリ領域(このコミットではioSyncという変数)に対する操作が、データ競合検出器によって「解放」された(つまり、その操作が完了し、他のGoroutineがそのメモリ領域に安全にアクセスできるようになった)ことを通知するために使用されると考えられます。これにより、データ競合検出器は、システムコールのような外部の操作によって引き起こされる可能性のある競合状態を、より正確に理解し、報告できるようになります。これは、データ競合検出器が誤検知を減らし、真の競合を見逃さないようにするための「アノテーション」または「ヒント」として機能します。

技術的詳細

このコミットの主要な変更は、GoのsyscallパッケージにおけるSendfile関数の実装に、データ競合検出器のための明示的な同期アノテーションを追加することです。

  1. OS固有のSendfile関数のリネーム: src/pkg/syscall/syscall_darwin.go, src/pkg/syscall/syscall_freebsd_*.go, src/pkg/syscall/syscall_linux_*.go, src/pkg/syscall/syscall_netbsd.go, src/pkg/syscall/syscall_openbsd.go, src/pkg/syscall/zsyscall_linux_*.goといった各OSおよびアーキテクチャ固有のファイルで定義されていたSendfile関数が、すべて小文字始まりのsendfileにリネームされました。 例: func Sendfile(...) から func sendfile(...) へ。 これにより、これらの関数はGoのパッケージの可視性ルールに従い、syscallパッケージの外部からは直接呼び出せない非公開関数となりました。

  2. syscall_unix.goにおける新しい公開Sendfileラッパー関数の導入: src/pkg/syscall/syscall_unix.goファイルに、新たに公開関数Sendfileが追加されました。この新しいSendfile関数は、各OS固有の非公開sendfile関数を呼び出すラッパーとして機能します。

  3. データ競合検出器のアノテーションの追加: 新しい公開Sendfileラッパー関数の中に、以下のコードが追加されました。

    func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err error) {
        if raceenabled {
            raceReleaseMerge(unsafe.Pointer(&ioSync))
        }
        return sendfile(outfd, infd, offset, count)
    }
    
    • raceenabledは、Goのデータ競合検出器が有効になっているかどうかを示すグローバル変数です。検出器が有効な場合にのみ、内部的なアノテーションが実行されます。
    • raceReleaseMerge(unsafe.Pointer(&ioSync))が呼び出されています。ここでioSyncvar ioSync int64として定義されたグローバル変数です。unsafe.Pointer(&ioSync)は、ioSync変数のメモリアドレスをunsafe.Pointer型に変換してraceReleaseMergeに渡しています。
    • このraceReleaseMergeの呼び出しは、Sendfileシステムコールが実行される際に、ioSyncという共有リソースに対する操作がデータ競合検出器によって適切に「解放」されたことを通知する役割を果たします。これにより、Sendfileのような低レベルのI/O操作が引き起こす可能性のあるデータ競合を、検出器がより正確に追跡できるようになります。これは、システムコールが内部的に共有状態を変更する際に、データ競合検出器がその変更を正しく認識し、誤検知や見逃しを防ぐための重要なステップです。

この変更により、Goのデータ競合検出器はSendfileシステムコールに関連する並行処理の挙動をより正確に分析できるようになり、開発者はSendfileを使用するアプリケーションにおけるデータ競合の問題をより効果的に特定・修正できるようになります。

コアとなるコードの変更箇所

このコミットでは、主に以下のファイルが変更されています。

  • src/pkg/syscall/syscall_darwin.go

  • src/pkg/syscall/syscall_freebsd_386.go

  • src/pkg/syscall/syscall_freebsd_amd64.go

  • src/pkg/syscall/syscall_freebsd_arm.go

  • src/pkg/syscall/syscall_linux_386.go

  • src/pkg/syscall/syscall_linux_amd64.go

  • src/pkg/syscall/syscall_linux_arm.go

  • src/pkg/syscall/syscall_netbsd.go

  • src/pkg/syscall/syscall_openbsd.go

  • src/pkg/syscall/zsyscall_linux_386.go

  • src/pkg/syscall/zsyscall_linux_amd64.go

  • src/pkg/syscall/zsyscall_linux_arm.go

    • これらのファイルでは、既存の公開関数Sendfileが非公開関数sendfileにリネームされています。また、//sysディレクティブを使用している箇所も同様にリネームされています。
  • src/pkg/syscall/syscall_unix.go

    • このファイルに、新しい公開関数Sendfileが追加されました。この関数は、非公開のsendfile関数を呼び出すラッパーであり、データ競合検出器が有効な場合にraceReleaseMergeを呼び出すロジックが含まれています。
    • var ioSync int64というグローバル変数もこのファイルで定義されています。

コアとなるコードの解説

最も重要な変更はsrc/pkg/syscall/syscall_unix.goにあります。

diff --git a/src/pkg/syscall/syscall_unix.go b/src/pkg/syscall/syscall_unix.go
index 978350829b..fee1fc491f 100644
--- a/src/pkg/syscall/syscall_unix.go
+++ b/src/pkg/syscall/syscall_unix.go
@@ -143,4 +143,11 @@ func Write(fd int, p []byte) (n int, err error) {
 	return write(fd, p)
 }
 
+func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err error) {
+	if raceenabled {
+		raceReleaseMerge(unsafe.Pointer(&ioSync))
+	}
+	return sendfile(outfd, infd, offset, count)
+}
+
+var ioSync int64

このコードスニペットは、以下の重要な変更を示しています。

  1. 新しい公開Sendfile関数の追加: func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err error) この関数は、Goのユーザーがsyscall.Sendfileとして呼び出す新しいエントリポイントです。

  2. データ競合検出器のアノテーション: if raceenabled { raceReleaseMerge(unsafe.Pointer(&ioSync)) } この部分が、データ競合検出器のためのアノテーションです。

    • raceenabledは、Goプログラムが-raceフラグ付きでビルド・実行されている場合にtrueとなるブール変数です。これにより、競合検出器が有効な場合にのみ、このアノテーションが実行されます。
    • raceReleaseMergeは、Goランタイムの内部関数であり、データ競合検出器に対して、特定のメモリ領域(ここではioSync変数が指すメモリ)に対する操作が完了し、そのメモリが他のGoroutineに対して「解放」されたことを通知します。これは、Sendfileシステムコールが内部的に共有リソースを操作する際に、データ競合検出器がその操作を正しく追跡し、誤検知や見逃しを防ぐための「ヒント」として機能します。
    • unsafe.Pointer(&ioSync)は、ioSync変数のメモリアドレスをunsafe.Pointer型に変換して渡しています。これは、raceReleaseMergeが特定のメモリ位置を識別するために必要です。
  3. 非公開sendfile関数の呼び出し: return sendfile(outfd, infd, offset, count) この行は、実際にOSのsendfileシステムコールを呼び出す、各OS固有の非公開sendfile関数(以前は公開Sendfileだったもの)を呼び出しています。新しい公開Sendfile関数は、この非公開関数をラップし、その前後にデータ競合検出器のアノテーションを追加する役割を担っています。

  4. ioSync変数の定義: var ioSync int64 このioSync変数は、raceReleaseMerge関数に渡されるダミーの共有リソースとして機能します。この変数の値自体は重要ではなく、そのメモリアドレスがデータ競合検出器への同期ポイントとして使用されます。

この変更により、Sendfileシステムコールが実行されるたびに、データ競合検出器はioSyncに関連する同期イベントを記録し、Sendfileの内部的な並行動作をより正確にモデル化できるようになります。これにより、Sendfileを使用するGoプログラムにおけるデータ競合の検出精度が向上し、より堅牢な並行アプリケーションの開発に貢献します。

関連リンク

参考にした情報源リンク