[インデックス 16567] ファイルの概要
このコミットは、Goランタイムのメモリ管理に関連するsrc/pkg/runtime/malloc.goc
ファイルに対する変更です。malloc.goc
は、Goプログラムのヒープメモリ割り当て、解放、およびガベージコレクションに関連する低レベルのランタイムコードを定義しています。具体的には、メモリ領域の予約や初期化といった、Goのメモリ管理の根幹をなす部分を扱っています。
コミット
commit b9ddb0d8b3d18c1c2a79eae686fe20fe36c707cb
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Thu Jun 13 16:40:10 2013 +0400
runtime: fix bug introduced in cl/10256043
R=golang-dev, dave
CC=golang-dev
https://golang.org/cl/10260043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b9ddb0d8b3d18c1c2a79eae686fe20fe36c707cb
元コミット内容
--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -399,7 +399,7 @@ runtime·mallocinit(void)
// So adjust it upward a little bit ourselves: 1/4 MB to get
// away from the running binary image and then round up
// to a MB boundary.
-\t\twant = (byte*)ROUND((uintptr)end + 1<<18, 1<<20);\n+\t\twant = (byte*)ROUND((uintptr)end + (1<<18), 1<<20);\n \t\tp = runtime·SysReserve(want, bitmap_size + spans_size + arena_size);\n \t\tif(p == nil)\n \t\t\truntime·throw(\"runtime: cannot reserve arena virtual address space\");\n```
## 変更の背景
このコミットは、`cl/10256043`で導入されたバグを修正するために行われました。`cl/10256043`は、Goランタイムにおいてパニック発生時にスタックオーバーフローが発生する可能性のある問題に対処するための変更でした。しかし、その変更が原因で、メモリ割り当ての計算ロジックに微妙なバグが混入してしまったようです。
具体的には、メモリ領域を予約する際に、既存のバイナリイメージから十分に離れたアドレスを確保し、さらにメガバイト境界にアラインするための計算式に誤りがありました。この誤りにより、`runtime·SysReserve`が期待するアドレスを正しく計算できず、結果として仮想アドレス空間の予約に失敗する可能性がありました。このコミットは、その計算式の優先順位の問題を修正し、メモリ予約が正しく行われるようにすることで、ランタイムの安定性を確保することを目的としています。
## 前提知識の解説
* **Goランタイム (runtime)**: Goプログラムは、Goランタイムと呼ばれる軽量な実行環境上で動作します。ランタイムは、ガベージコレクション、スケジューリング、メモリ管理、システムコールなど、プログラムの実行に必要な低レベルの機能を提供します。
* **メモリ管理 (Memory Management)**: プログラムが実行中に使用するメモリを効率的に割り当て、解放するプロセスです。Goランタイムは、独自のヒープメモリ管理システムとガベージコレクタを持っています。
* **`malloc.goc`**: GoランタイムのC言語で書かれた部分で、メモリ割り当ての低レベルな詳細を扱います。Goのメモリ管理の基盤となるコードが含まれています。
* **`runtime·mallocinit()`**: Goランタイムの初期化時に呼び出される関数の一つで、メモリ管理システムをセットアップします。ヒープ領域の予約や初期化などが行われます。
* **`runtime·SysReserve()`**: オペレーティングシステムに対して、指定されたアドレスとサイズで仮想アドレス空間を予約するランタイム関数です。これは、実際に物理メモリが割り当てられるわけではなく、アドレス空間を確保するだけです。
* **`ROUND(addr, align)`**: アドレス`addr`を指定された`align`の倍数に切り上げる(アラインする)ためのマクロまたは関数です。メモリのアラインメントは、特定のデータ型が特定のメモリアドレスに配置されることを保証し、パフォーマンスの向上やハードウェアの要件を満たすために重要です。
* **`uintptr`**: ポインタを保持できる符号なし整数型です。Goでは、ポインタ演算を直接行うことは推奨されませんが、低レベルのランタイムコードでは、メモリのアドレスを整数として扱うために使用されます。
* **`byte*`**: C言語のポインタ型で、バイト単位のメモリを指します。GoランタイムのCコード部分でメモリを操作する際に使用されます。
* **ビットシフト演算子 (`<<`)**: `1 << N`は、1をNビット左にシフトすることを意味します。これは2のN乗を計算する効率的な方法です。例えば、`1 << 18`は2の18乗(262144バイト、つまり0.25MB)を、`1 << 20`は2の20乗(1048576バイト、つまり1MB)を表します。
## 技術的詳細
このコミットの核心は、`runtime·mallocinit`関数内の以下の行の変更です。
変更前: `want = (byte*)ROUND((uintptr)end + 1<<18, 1<<20);`
変更後: `want = (byte*)ROUND((uintptr)end + (1<<18), 1<<20);`
この変更は、`+`演算子と`<<`(左シフト)演算子の演算子優先順位に関するものです。
* **演算子優先順位**: C言語(Goランタイムのこの部分はCで書かれています)では、左シフト演算子`<<`は加算演算子`+`よりも高い優先順位を持ちます。
* 変更前: `(uintptr)end + 1<<18` は `(uintptr)end + (1<<18)` と解釈されるべきですが、実際には `((uintptr)end + 1) << 18` と解釈される可能性があります。これは、`end`のアドレスに1を加算した後に、その結果を18ビット左にシフトするという、意図しない計算を引き起こします。
* 変更後: `(uintptr)end + (1<<18)` は、明示的に`1<<18`の部分を括弧で囲むことで、`1<<18`が先に計算され、その結果が`(uintptr)end`に加算されることを保証します。
このバグは、Goランタイムがヒープ領域を予約する際に、既存のバイナリイメージの終端(`end`)から一定のオフセット(1/4 MB、つまり`1<<18`バイト)を加えてから、メガバイト境界(`1<<20`バイト)にアラインするというロジックに影響を与えました。演算子優先順位の誤解釈により、計算される`want`アドレスが期待通りにならず、`runtime·SysReserve`がメモリ予約に失敗する可能性がありました。
この修正により、`end`アドレスに正確に1/4 MBが加算され、その後1MB境界にアラインされることが保証されます。これにより、Goランタイムが仮想アドレス空間を正しく予約できるようになり、メモリ管理の初期化が安定して行われるようになります。
## コアとなるコードの変更箇所
`src/pkg/runtime/malloc.goc`ファイルの`runtime·mallocinit`関数内の以下の行です。
```c
want = (byte*)ROUND((uintptr)end + (1<<18), 1<<20);
コアとなるコードの解説
この行は、Goランタイムがヒープメモリ領域を確保する際に、仮想アドレス空間のどこから開始するかを決定するための重要な計算を行っています。
(uintptr)end
: これは、現在実行中のバイナリイメージの終端アドレスをuintptr
型(ポインタを保持できる整数型)にキャストしたものです。Goランタイムは、ヒープ領域をバイナリイメージの直後ではなく、少し離れた場所から開始することを好みます。+ (1<<18)
: ここが修正された箇所です。1<<18
は2の18乗、つまり262144バイト(0.25MB)を表します。この値がend
アドレスに加算され、バイナリイメージの終端から1/4 MBだけ離れたアドレスを計算します。括弧で囲むことで、この加算が正しく行われることを保証しています。ROUND(..., 1<<20)
:ROUND
マクロまたは関数は、最初の引数(計算されたアドレス)を、2番目の引数(1<<20
、つまり1MB)の倍数に切り上げます。これにより、want
アドレスが常にメガバイト境界にアラインされることが保証されます。これは、オペレーティングシステムのメモリ管理単位(ページサイズなど)や、Goランタイム自身のメモリ管理の効率性にとって重要です。(byte*)
: 最終的に計算されたアドレスは、byte*
型にキャストされます。これは、メモリをバイトのシーケンスとして扱うためのC言語のポインタ型です。
この行全体で、Goランタイムは、バイナリイメージの終端から少し離れた、かつメガバイト境界にアラインされた仮想アドレスをwant
として計算し、そのアドレスからメモリ領域を予約しようとします。この修正は、その計算が常に意図通りに行われるようにするための、非常に重要かつ低レベルなバグ修正でした。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/b9ddb0d8b3d18c1c2a79eae686fe20fe36c707cb
- 関連するGo changelist (CL): https://golang.org/cl/10260043
参考にした情報源リンク
- Google Web Search (for
golang cl/10256043
) - Go言語のソースコード (
src/pkg/runtime/malloc.goc
) - C言語の演算子優先順位に関する一般的な知識