[インデックス 16070] ファイルの概要
このコミットは、Go言語の標準ライブラリbytes
パッケージ内のequal_test.go
ファイルに対する変更です。具体的には、TestEqualNearPageBoundary
というテスト関数において、syscall.Mprotect
によって保護されたメモリページがテスト終了後に適切に元の状態に戻されるように修正されています。
コミット
commit e42584effe7b648a8da345c67bbaef3f4053e228
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Wed Apr 3 08:30:20 2013 -0700
bytes: don't leave mprotect-ed pages after unsafe test.
Fixes inscrutable GC faults during testing.
R=golang-dev, bradfitz, dave, fullung
CC=golang-dev
https://golang.org/cl/8300044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e42584effe7b648a8da345c67bbaef3f4053e228
元コミット内容
src/pkg/bytes/equal_test.go
ファイルにおいて、syscall.Mprotect
を用いてメモリページの保護設定を変更した後、その変更を元に戻すdefer
ステートメントが追加されています。
変更の背景
コミットメッセージによると、この変更は「テスト中に不可解なGC(ガベージコレクション)の障害を修正する」ことを目的としています。syscall.Mprotect
はメモリページのアクセス権限を変更するシステムコールであり、テストコード内で特定のメモリ領域を一時的にアクセス不可(または読み取り専用)に設定していた可能性があります。テストが終了した後もこれらのメモリページが保護された状態のままだと、Goランタイムのガベージコレクタがその領域にアクセスしようとした際に予期せぬエラー(GCフォルト)を引き起こす可能性がありました。このコミットは、テストが完了した後にメモリページの保護設定を元の読み書き可能な状態に戻すことで、この問題に対処しています。
前提知識の解説
1. syscall.Mprotect
syscall.Mprotect
は、Go言語のsyscall
パッケージが提供する関数で、Unix系システムにおけるmprotect(2)
システムコールのラッパーです。このシステムコールは、指定されたメモリ領域のアクセス保護(読み取り、書き込み、実行)を変更するために使用されます。
mprotect(addr, len, prot)
:addr
からlen
バイトのメモリ領域の保護設定をprot
で指定された値に変更します。prot
フラグ:syscall.PROT_READ
: ページを読み取り可能にする。syscall.PROT_WRITE
: ページを書き込み可能にする。syscall.PROT_EXEC
: ページを実行可能にする。syscall.PROT_NONE
: ページにアクセス不可にする。
これらのフラグはビットOR演算子(|
)で組み合わせて使用できます。例えば、syscall.PROT_READ | syscall.PROT_WRITE
は、メモリページを読み書き可能に設定します。
2. ガベージコレクション (GC)
ガベージコレクションは、プログラムが動的に割り当てたメモリのうち、もはや使用されていない(参照されていない)領域を自動的に解放するプロセスです。Go言語のガベージコレクタは、プログラムの実行中にバックグラウンドで動作し、不要なメモリを回収して再利用可能にします。
3. メモリ保護とGCの相互作用
Goランタイム、特にガベージコレクタは、メモリ管理のためにmprotect
のようなシステムコールを利用することがあります。例えば、GCサイクル中に特定のメモリ領域への書き込みを一時的に禁止したり、ヒープの健全性をチェックしたりするためにメモリ保護を変更する場合があります。もしテストコードがmprotect
でメモリ保護を変更し、それを元に戻さないままテストが終了すると、GCがその保護されたメモリ領域にアクセスしようとした際に、アクセス違反が発生し、GCフォルトとして現れる可能性があります。これは、GCが期待するメモリの状態と実際のメモリの状態が異なるために起こる競合状態やデッドロックのような問題を引き起こすことがあります。
4. defer
ステートメント
Go言語のdefer
ステートメントは、その関数がリターンする直前に実行される関数呼び出しをスケジュールします。これは、リソースの解放(ファイルのクローズ、ロックの解除など)や、今回のように一時的に変更した状態を元に戻す処理に非常に便利です。defer
された関数は、関数の実行パスが正常終了、パニック、またはreturn
ステートメントのいずれであっても、必ず実行されます。
技術的詳細
bytes.Equal
関数のテストであるTestEqualNearPageBoundary
は、メモリページの境界付近でのバイトスライスの比較が正しく行われるかを検証するためのものです。この種のテストでは、意図的にメモリ保護を変更して、不正なメモリアクセスが発生しないか、あるいは特定の条件下での挙動を確認することがあります。
元のコードでは、syscall.Mprotect
を使用して、テスト対象のバイトスライスの一部を含むメモリページを一時的にアクセス不可(または読み取り専用)に設定していました。これは、syscall.Mprotect(b[i-pagesize:i], 0)
とsyscall.Mprotect(b[i+pagesize:i+2*pagesize], 0)
という行で行われています。ここで、0
はPROT_NONE
を意味し、そのメモリ領域へのアクセスを完全に禁止します。
問題は、このメモリ保護の変更がテスト終了後も持続してしまうことでした。Goのガベージコレクタは、プログラムのヒープ全体をスキャンして到達可能なオブジェクトを特定し、到達不能なオブジェクトを回収します。もしGCが、テストによってPROT_NONE
に設定されたメモリページにアクセスしようとすると、オペレーティングシステムによってアクセス違反が検出され、プログラムがクラッシュする(GCフォルト)原因となります。
このコミットでは、defer
ステートメントを用いて、テスト関数が終了する際に必ずsyscall.Mprotect
を再度呼び出し、該当するメモリページをsyscall.PROT_READ | syscall.PROT_WRITE
(読み書き可能)の状態に戻すようにしています。これにより、テストが一時的に変更したメモリ保護が、テスト終了後にはクリーンアップされ、GCが正常に動作できるようになります。
コアとなるコードの変更箇所
--- a/src/pkg/bytes/equal_test.go
+++ b/src/pkg/bytes/equal_test.go
@@ -30,6 +30,8 @@ func TestEqualNearPageBoundary(t *testing.T) {
}\n \tsyscall.Mprotect(b[i-pagesize:i], 0)\n \tsyscall.Mprotect(b[i+pagesize:i+2*pagesize], 0)\n+\tdefer syscall.Mprotect(b[i-pagesize:i], syscall.PROT_READ|syscall.PROT_WRITE)\n+\tdefer syscall.Mprotect(b[i+pagesize:i+2*pagesize], syscall.PROT_READ|syscall.PROT_WRITE)\n \n \t// both of these should fault\n \t//pagesize += int(b[i-1])\n```
## コアとなるコードの解説
追加された2行の`defer`ステートメントがこのコミットの核心です。
1. `defer syscall.Mprotect(b[i-pagesize:i], syscall.PROT_READ|syscall.PROT_WRITE)`
2. `defer syscall.Mprotect(b[i+pagesize:i+2*pagesize], syscall.PROT_READ|syscall.PROT_WRITE)`
これらの行は、`TestEqualNearPageBoundary`関数がリターンする直前に、以前`0`(`PROT_NONE`)に設定されたメモリ領域`b[i-pagesize:i]`と`b[i+pagesize:i+2*pagesize]`の保護設定を`syscall.PROT_READ | syscall.PROT_WRITE`(読み書き可能)に戻すことを保証します。
`defer`キーワードにより、これらの`syscall.Mprotect`呼び出しは、テスト関数が正常に完了した場合でも、パニックが発生した場合でも、必ず実行されます。これにより、テストが一時的に変更したシステムレベルのメモリ状態が、テスト終了時には常にクリーンな状態に戻され、Goランタイムのガベージコレクタやその他のメモリ管理メカニズムとの干渉が解消されます。結果として、「不可解なGCフォルト」が修正されることになります。
## 関連リンク
* Go言語の`syscall`パッケージドキュメント: [https://pkg.go.dev/syscall](https://pkg.go.dev/syscall)
* `mprotect(2)` manページ (Unix/Linux): 通常、`man 2 mprotect`で参照可能。オンラインでも検索可能。
## 参考にした情報源リンク
* `syscall.Mprotect`に関するGoのドキュメントや関連する議論
* Go言語のガベージコレクションに関する一般的な情報
* `mprotect`システムコールに関するOSのドキュメント
* GoのIssueトラッカーやコードレビューシステム(`https://golang.org/cl/8300044`)
* Stack Overflowや技術ブログでの`mprotect`とGoのGCに関する議論
* [https://stackoverflow.com/questions/tagged/go+mprotect](https://stackoverflow.com/questions/tagged/go+mprotect)
* [https://stackoverflow.com/questions/tagged/go+garbage-collection](https://stackoverflow.com/questions/tagged/go+garbage-collection)
* [https://go.dev/doc/gc-guide](https://go.dev/doc/gc-guide) (Go GC Guide)
* [https://medium.com/a-journey-with-go/go-memory-management-and-garbage-collection-1b1d746f22f](https://medium.com/a-journey-with-go/go-memory-management-and-garbage-collection-1b1d746f22f) (Go Memory Management and Garbage Collection)
* [https://go.dev/src/runtime/mprotect.go](https://go.dev/src/runtime/mprotect.go) (Go runtime source code related to mprotect)
* [https://go.dev/src/runtime/mgc.go](https://go.dev/src/runtime/mgc.go) (Go runtime source code related to garbage collection)