[インデックス 16532] ファイルの概要
このコミットは、GoランタイムにおけるLinux環境での一度限りのメモリリークを修正するものです。具体的には、src/pkg/runtime/mem_linux.c
ファイル内のメモリ予約処理 (runtime·SysReserve
) における mmap_fixed
の誤用が原因で発生していた問題に対処しています。
コミット
commit fd41926347c6f1cf1cc06079050912b9c48a1f09
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Mon Jun 10 22:59:39 2013 +0400
runtime: fix one-time memory leak on linux
Update #5641.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/10144043
---
src/pkg/runtime/mem_linux.c | 9 ++++++---\n 1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/src/pkg/runtime/mem_linux.c b/src/pkg/runtime/mem_linux.c
index 1bae755faf..bacd568d9e 100644
--- a/src/pkg/runtime/mem_linux.c
+++ b/src/pkg/runtime/mem_linux.c
@@ -95,14 +95,17 @@ runtime·SysReserve(void *v, uintptr n)
// Only user-mode Linux (UML) rejects these requests.
if(sizeof(void*) == 8 && (uintptr)v >= 0xffffffffU) {
p = mmap_fixed(v, 64<<10, PROT_NONE, MAP_ANON|MAP_PRIVATE, -1, 0);\n-\t\tif (p != v)\n+\t\tif (p != v) {\n+\t\t\tif(p >= (void*)4096)\n+\t\t\t\truntime·munmap(p, 64<<10);\n \t\t\treturn nil;\n+\t\t}\n \t\truntime·munmap(p, 64<<10);\n \t\treturn v;\n \t}\n-\t\n+\n \tp = runtime·mmap(v, n, PROT_NONE, MAP_ANON|MAP_PRIVATE, -1, 0);\n-\tif((uintptr)p < 4096 || -(uintptr)p < 4096)\n+\tif((uintptr)p < 4096)\n \t\treturn nil;\n \treturn p;\n }\n```
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/fd41926347c6f1cf1cc06079050912b9c48a1f09](https://github.com/golang/go/commit/fd41926347c6f1cf1cc06079050912b9c48a1f09)
## 元コミット内容
runtime: fix one-time memory leak on linux Update #5641.
R=golang-dev, bradfitz CC=golang-dev https://golang.org/cl/10144043
## 変更の背景
このコミットは、GoランタイムがLinux上でメモリを予約する際に発生していた、一度限りのメモリリークを修正するために導入されました。コミットメッセージにある「Update #5641」は、この問題がGoのイシュートラッカーで追跡されていたことを示唆しています。
Goランタイムは、プログラムの実行に必要なメモリをOSから取得し、管理します。このプロセスには、`mmap`システムコールが頻繁に利用されます。特に、特定のメモリアドレスにメモリをマップしようとする場合、`MAP_FIXED`フラグが使用されることがあります。しかし、`MAP_FIXED`は非常に強力なフラグであり、その使用には細心の注意が必要です。指定されたアドレスが既にマップされている場合、既存のマッピングを上書きしてしまう可能性があります。また、指定されたアドレスにマップできなかった場合、`mmap`はエラーを返すか、異なるアドレスを返すことがあります。
このコミットが修正しようとしている問題は、`mmap_fixed`が要求されたアドレスにメモリをマップできなかった場合に、`mmap_fixed`が返した別の有効なアドレスのメモリが解放されずにリークしてしまうというものでした。これは、Goランタイムが特定のメモリ領域を予約しようとした際に、その予約が失敗した場合のクリーンアップ処理が不完全であったために発生しました。
## 前提知識の解説
### メモリリーク (Memory Leak)
メモリリークとは、プログラムが確保したメモリ領域を、不要になった後も解放せずに保持し続けてしまう現象です。これにより、利用可能なメモリが徐々に減少し、最終的にはシステムのパフォーマンス低下やクラッシュを引き起こす可能性があります。Go言語はガベージコレクションを備えていますが、OSレベルのメモリ管理(`mmap`など)の誤用によってもメモリリークは発生し得ます。
### `mmap` システムコール
`mmap` (memory map) は、Unix系OSで利用されるシステムコールで、ファイルやデバイス、または匿名メモリ領域をプロセスのアドレス空間にマッピングするために使用されます。これにより、ファイルの内容をメモリとして直接アクセスしたり、プロセス間でメモリを共有したりすることが可能になります。
`mmap`の主な引数:
* `addr`: マッピングを開始するアドレスのヒント。OSは可能な限りこのアドレスに近い場所を試みます。`NULL`を指定するとOSが適切なアドレスを選択します。
* `length`: マッピングするバイト数。
* `prot`: メモリ領域の保護(読み取り、書き込み、実行など)。
* `PROT_NONE`: アクセス不可。メモリを予約するが、アクセスは許可しない場合などに使用されます。
* `flags`: マッピングの動作を制御するフラグ。
* `MAP_ANON`: ファイルではなく、匿名メモリ領域をマッピングします。
* `MAP_PRIVATE`: マッピングがプライベートであり、他のプロセスと共有されないことを示します。
* `MAP_FIXED`: `addr`で指定されたアドレスに厳密にマッピングすることを要求します。このフラグを使用すると、`addr`が`NULL`でない限り、OSは指定されたアドレスにマッピングを試みます。指定されたアドレスが利用できない場合、`mmap`は失敗します。**このフラグは非常に強力であり、誤用すると既存のマッピングを上書きしたり、予期せぬメモリリークを引き起こしたりする可能性があります。**
* `fd`: マッピングするファイルのファイルディスクリプタ。匿名マッピングの場合は`-1`。
* `offset`: ファイル内のオフセット。
### `munmap` システムコール
`munmap`は、`mmap`によってマッピングされたメモリ領域をアンマッピング(解放)するために使用されるシステムコールです。これにより、プロセスのアドレス空間から指定されたメモリ領域が削除され、OSにメモリが返却されます。
### `runtime·SysReserve` 関数
Goランタイム内部の関数で、OSからメモリを予約するために使用されます。Goのガベージコレクタやメモリ管理システムが利用する、より低レベルなメモリ確保のメカニズムの一部です。
## 技術的詳細
このコミットが修正する問題は、`runtime·SysReserve`関数内で発生していました。この関数は、特定の条件下(特に64ビットシステムで、非常に高いアドレスにメモリを予約しようとする場合)で、`mmap_fixed`を使用してメモリを予約しようとします。
元のコードでは、`mmap_fixed(v, 64<<10, PROT_NONE, MAP_ANON|MAP_PRIVATE, -1, 0)`が呼び出され、`v`という特定のアドレスに`64KB`のメモリを`PROT_NONE`(アクセス不可)で匿名かつプライベートにマッピングしようとしていました。
問題は、`mmap_fixed`が`v`にマッピングできなかった場合です。この場合、`mmap_fixed`は`v`とは異なるアドレス`p`を返す可能性があります(またはエラーを示す`MAP_FAILED`を返す)。元のコードでは、`p != v`の場合に`return nil`としていましたが、この`p`が有効なアドレスであった場合、そのメモリ領域は`munmap`されずにリークしていました。つまり、`mmap_fixed`が意図しないアドレスにメモリをマップしてしまったが、そのメモリを解放する処理が欠けていたのです。
また、もう一つの変更点として、`mmap`の戻り値`p`が不正なアドレス(4096未満)であるかどうかのチェックがありました。元のコードでは`if((uintptr)p < 4096 || -(uintptr)p < 4096)`という条件でしたが、これは符号なし整数と符号付き整数の比較が混在しており、特に負の値のチェックが意図しない挙動を引き起こす可能性がありました。Linuxの`mmap`は成功した場合、常にページ境界にアラインされた非負のアドレスを返します。したがって、`p < 4096`(通常、NULLポインタや非常に低いアドレスは不正と見なされる)のチェックで十分です。
## コアとなるコードの変更箇所
```diff
--- a/src/pkg/runtime/mem_linux.c
+++ b/src/pkg/runtime/mem_linux.c
@@ -95,14 +95,17 @@ runtime·SysReserve(void *v, uintptr n)
// Only user-mode Linux (UML) rejects these requests.
if(sizeof(void*) == 8 && (uintptr)v >= 0xffffffffU) {
p = mmap_fixed(v, 64<<10, PROT_NONE, MAP_ANON|MAP_PRIVATE, -1, 0);\n-\t\tif (p != v)\n+\t\t\tif (p != v) {\n+\t\t\t\tif(p >= (void*)4096)\n+\t\t\t\t\truntime·munmap(p, 64<<10);\n \t\t\treturn nil;\n+\t\t}\n \t\truntime·munmap(p, 64<<10);\
\t\treturn v;\
\t}\
-\t\n+\n \tp = runtime·mmap(v, n, PROT_NONE, MAP_ANON|MAP_PRIVATE, -1, 0);\
-\tif((uintptr)p < 4096 || -(uintptr)p < 4096)\n+\tif((uintptr)p < 4096)\
\t\treturn nil;\
\treturn p;\
}\
コアとなるコードの解説
このコミットには主に2つの重要な変更点があります。
-
mmap_fixed
によるメモリリークの修正:- 変更前:
このコードでは、if (p != v) return nil; runtime·munmap(p, 64<<10);
mmap_fixed
が要求されたアドレスv
とは異なるアドレスp
を返した場合(つまりp != v
)、すぐにnil
を返して関数を終了していました。しかし、このp
が有効なメモリ領域を指していた場合、そのメモリはmunmap
されることなくリークしていました。その後のruntime·munmap(p, 64<<10);
は、p == v
の場合にのみ実行されるため、リークが発生していました。 - 変更後:
この修正では、if (p != v) { if(p >= (void*)4096) runtime·munmap(p, 64<<10); return nil; }
p != v
の場合に、まずp
が有効なアドレス(4096
以上、つまりNULLや非常に低い不正なアドレスではない)であるかをチェックします。もし有効であれば、そのメモリ領域をruntime·munmap
で明示的に解放してからnil
を返します。これにより、mmap_fixed
が意図しないアドレスにメモリをマップしてしまった場合でも、そのメモリが適切に解放されるようになり、メモリリークが解消されます。
- 変更前:
-
mmap
戻り値の不正アドレスチェックの簡素化:- 変更前:
この条件は、if((uintptr)p < 4096 || -(uintptr)p < 4096) return nil;
mmap
が返したアドレスp
が不正であるかをチェックしています。4096
という値は、通常、NULLポインタや非常に低いアドレス(カーネルによって保護されている可能性のあるアドレス)を不正と見なすための閾値として使われます。しかし、-(uintptr)p < 4096
という部分は、符号なし整数uintptr
に負の演算子を適用しており、これは通常、非常に大きな正の数になります。この条件は、mmap
がエラーを示すMAP_FAILED
(通常は-1
)を返した場合に、それがuintptr
にキャストされると非常に大きな正の数になるため、それを検出するための試みだった可能性があります。しかし、これはGoのランタイムがmmap
の戻り値をどのように扱うかによって、よりシンプルにできる可能性があります。 - 変更後:
この修正では、if((uintptr)p < 4096) return nil;
-(uintptr)p < 4096
の条件が削除され、p
が4096
未満であるかどうかのチェックのみに簡素化されました。これは、Linuxのmmap
が成功した場合、常にページ境界にアラインされた非負のアドレスを返すという事実に基づいています。したがって、MAP_FAILED
のようなエラー値は、uintptr
にキャストされたとしても、通常は4096
未満の小さな値にはならないため、この簡素化されたチェックで十分であると判断されたと考えられます。これにより、コードの可読性と正確性が向上しています。
- 変更前:
これらの変更により、GoランタイムはLinux環境でより堅牢なメモリ管理を行うことができるようになりました。
関連リンク
- Go CL 10144043: https://golang.org/cl/10144043
- Go Issue #5641 (言及されているが、直接的なイシューページは特定できず): 関連する議論や修正は、Goのイシュートラッカーやメーリングリストで追跡されていた可能性があります。
参考にした情報源リンク
mmap
man page: https://man7.org/linux/man-pages/man2/mmap.2.htmlmunmap
man page: https://man7.org/linux/man-pages/man2/munmap.2.html- Go runtime memory management discussions (一般的な情報源):
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGSdpIDFZHZeW-bZQjOTkD1NG5ivAyPIOH-6X02ezhv5lUxRiPiGFSLLFBNjNNokkbW2ZELoCYNby_E_Z69zveTqpRz4dNmgg8LIsuZdpdVaCWugcdcGCjetfOv7awLCCERqB6h (MAP_FIXED Usage Risks)
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG7pYmCN7twmlDPZrjlKhnYu67nuCdrNEKznlBKKP5f3O7Y843nCRmuo2f4qedTVNDoSUTu3euCRVE9mvhUnJURsYtZZjTTlU5dUId0hTQHqOImdfFKxLq3BIwlPnjjMv0_lS36GVkbHKcqrs0WAMRiMG-dxOuJsUL0r3pGoqv4P3KkIHmnCmfOTMLd8f1DGSnWi7M= (MAP_FIXED Usage Risks)
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGWXNi-618Hp2qtMLxSULQoJtRb8cNqwtk2HcSLK0JfRvuAfcr9RL0Eelc9p3OgrL3zqkhMG2xdhSsphT6RFGle69eX4W8WtR0eGIacUT-HmF_nrJkKUhTfsh80fYRp8kT4Qjjr-px6OnxuI53DPVwbUmd9nY8ZpTJMBLeSNDw7kqanbtEklh-X8Q== (MAP_FIXED Usage Risks)
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGnCnpLCGKstxSoijJksQBgjnRJqwkxwoEydIoCA71SfhgUNPXGQFqWh2fvzGFBpmwyIHfKpi1XYiwbsUAaB15-VmmLWq-5GtjyjRfuDsBkO3c2o92ZvEDPBk2dFrFRD5tpGxyh (Race Conditions with mmap_fixed)
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEtbO4NkZzGsyo2iuWBrlqopB9pq1j3PXfQtbjsNh7-NCVMPAo5j3S6LQeDOlX0BB0qe0YRNhaETm0AjXqbife5WfIxEwSLQpoy5GDzLa2jEOM3mEQ7D8AfHInnL1fgvCqMvXI8Iy7z6rbFuAwnJhSKDp9rKdn9NgKRGqN5zbWnQ0PQB4nPiByH-w== (mmap considered harmful in Go)
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHBjYsJkKIl_cSZNYGXbRSLMIfBvKHISoJlUZdhOzsubqFupPue0czohv2BzZpRxdnOSutlcRFgOfNCndbh9vkU3k-NSphIYHM5dm9bqqsv8YH9zf0BAhcLzrAgW0oRoo8RNxD_Gadlg15x-eEScPCnOpovscCwgIutVCsADrkyCft9 (Go runtime conservative about returning memory)