[インデックス 15277] ファイルの概要
このコミットは、Goランタイムにおけるヒープメタデータ構造 mheap のアロケーション方法を変更するものです。以前は mheap 構造体がBSSセグメントに静的に割り当てられていましたが、これが一部のBSDシステムにおける実行時BSSサイズ制限に抵触する問題がありました。この変更により、mheap は実行時に動的にヒープから割り当てられるようになります。
コミット
- コミットハッシュ: 8a6ff3ab3469ea6b448d682ac7ebc3b818208634
- Author: Russ Cox rsc@golang.org
- Date: Fri Feb 15 14:27:03 2013 -0500
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8a6ff3ab3469ea6b448d682ac7ebc3b818208634
元コミット内容
runtime: allocate heap metadata at run time
Before, the mheap structure was in the bss,
but it's quite large (today, 256 MB, much of
which is never actually paged in), and it makes
Go binaries run afoul of exec-time bss size
limits on some BSD systems.
Fixes #4447.
R=golang-dev, dave, minux.ma, remyoudompheng, iant
CC=golang-dev
https://golang.org/cl/7307122
変更の背景
この変更の主な背景は、Goプログラムが一部のBSDシステムで実行時エラーを起こす問題に対処するためです。具体的には、Goランタイムのヒープ管理の中核をなす mheap 構造体が、以前はプログラムのBSS (Block Started by Symbol) セグメントに静的に割り当てられていました。
mheap 構造体は、Goのメモリ管理において非常に重要な役割を担っており、そのサイズは非常に大きくなる可能性があります(コミットメッセージによると、当時で256MB)。BSSセグメントは、初期化されていない静的変数やグローバル変数が配置される領域であり、プログラムの実行ファイルサイズには直接影響しませんが、実行時にOSによってゼロ初期化される必要があります。
一部のBSDシステム(例えばOpenBSDなど)では、セキュリティ上の理由やリソース管理のために、実行時にプログラムが使用できるBSSセグメントのサイズに厳格な制限を設けています。Goの mheap がこの制限を超過すると、プログラムの起動時に「out of memory」のようなエラーが発生し、実行が妨げられる問題が発生していました。
この問題はGoのIssue #4447として報告されており、このコミットはその解決策として提案されました。mheap を静的なBSSセグメントから動的なヒープアロケーションに変更することで、このBSSサイズ制限の問題を回避し、Goプログラムの互換性と移植性を向上させることが目的です。また、mheap の大部分が実際にページインされないという記述から、静的割り当てがメモリ効率の観点からも最適ではなかった可能性も示唆されています。
前提知識の解説
このコミットを理解するためには、以下のGoランタイムおよびOSのメモリ管理に関する概念を理解しておく必要があります。
-
BSS (Block Started by Symbol) セグメント: プログラムのメモリレイアウトの一部で、初期化されていない静的変数やグローバル変数が格納される領域です。実行ファイルにはこれらの変数の値は含まれず、プログラムのロード時にOSによってゼロ初期化されます。これにより、実行ファイルのサイズを小さく保つことができます。しかし、一部のOSでは、このBSSセグメントのサイズに上限を設けている場合があります。
-
ヒープ (Heap): プログラムが実行時に動的にメモリを確保するために使用する領域です。
mallocやnewなどの関数を通じてメモリが要求され、不要になったメモリは解放されます。Goにおいては、ガベージコレクタがヒープの管理を行います。 -
mheap構造体: Goランタイムにおけるグローバルなヒープ管理構造体です。Goのメモリ管理システムの中核をなし、メモリのアロケーション、解放、ガベージコレクションのプロセスを調整します。mheapは、利用可能なメモリのページ(MSpan)の管理、アロケータ(FixAlloc)、キャッシュ(MCache)、中央リスト(MCentral)など、ヒープ全体の状態を追跡するための様々なフィールドを含んでいます。そのサイズは、管理するメモリ量やシステム構成によって大きくなります。 -
SysAlloc: GoランタイムがOSから直接メモリを要求するための低レベルな関数です。これは、Goのヒープ自体を拡張したり、mheapのようなランタイムの重要なデータ構造のためにメモリを確保したりする際に使用されます。 -
PageShiftとPageMask: メモリページに関連する定数です。PageShiftはページサイズを2のべき乗で表したときの指数(例: 12ならば4KBページ)、PageMaskはページ内のオフセットをマスクするための値です。これらは、アドレスをページ境界にアラインしたり、ページ番号を計算したりするために使用されます。 -
MSpan: Goランタイムが管理するメモリの連続したページ群を表す構造体です。ヒープは多数のMSpanに分割され、それぞれが特定のサイズのオブジェクトを格納したり、空きメモリとして管理されたりします。 -
MCache: 各M (Machine、OSスレッドに相当) にローカルなメモリキャッシュです。Goルーチンが頻繁に小さなオブジェクトをアロケートする際に、グローバルなmheapへのロック競合を避けるために使用されます。MCacheはMCentralからMSpanを取得し、そこからオブジェクトをアロケートします。 -
MCentral: 特定のサイズのオブジェクトをアロケートするためのMSpanを管理する中央リストです。MCacheがメモリを使い果たした際に、MCentralからMSpanを補充します。 -
ガベージコレクタ (GC): Goの自動メモリ管理システムです。不要になったメモリを自動的に識別し、解放することで、メモリリークを防ぎ、開発者が手動でメモリを管理する負担を軽減します。
mheapはGCの動作に不可欠な情報を含んでいます。
技術的詳細
このコミットの技術的な核心は、Goランタイムのグローバルヒープ管理構造体である runtime·mheap の宣言と初期化の方法を変更することにあります。
変更前:
runtime·mheap は src/pkg/runtime/malloc.goc で MHeap runtime·mheap; と宣言され、#pragma dataflag 16 ディレクティブによってGCの対象外とされていました。これは、mheap がBSSセグメントに静的に割り当てられることを意味します。
変更後:
- ポインタへの変更:
runtime·mheapはMHeap *runtime·mheap;とポインタとして宣言されるようになりました。これにより、mheap構造体自体は静的に確保されず、そのポインタのみが静的に確保されます。 - 動的アロケーション:
runtime·mallocinit関数内で、runtime·mheap = runtime·SysAlloc(sizeof(*runtime·mheap));という行が追加されました。これは、プログラムの初期化段階でSysAllocを使用してOSから直接メモリを要求し、そのメモリ領域にMHeap構造体を動的に割り当てることを意味します。 - アクセス方法の変更:
mheapがポインタになったため、runtime·mheap.fieldのような直接アクセスはruntime·mheap->fieldのようにポインタを介したアクセスに変更されました。これは、malloc.goc、mcache.c、mcentral.c、mgc0.c、mheap.c、race.c、traceback_arm.c、traceback_x86.cなど、mheapにアクセスするすべてのファイルにわたって行われています。 - ロックの変更:
runtime·lock(&runtime·mheap)のようなmheap構造体自体へのロックも、runtime·lock(runtime·mheap)のようにポインタが指す実体へのロックに変更されました。
この変更により、mheap 構造体はBSSセグメントではなく、OSから動的に確保されたメモリ領域に配置されるため、一部のBSDシステムで発生していたBSSサイズ制限の問題を回避できます。また、mheap の実際のメモリは必要になったときにOSによってページインされるため、静的に確保されていた場合に比べて、起動時のメモリフットプリントが最適化される可能性もあります。
コアとなるコードの変更箇所
主要な変更は src/pkg/runtime/malloc.goc と src/pkg/runtime/malloc.h に集中していますが、mheap のフィールドにアクセスする他の多くのファイルにも影響が及んでいます。
src/pkg/runtime/malloc.goc の変更点:
-
runtime·mheapの宣言:--- a/src/pkg/runtime/malloc.goc +++ b/src/pkg/runtime/malloc.goc @@ -14,8 +14,7 @@ package runtime #include "typekind.h" #include "race.h" -#pragma dataflag 16 /* mark mheap as 'no pointers', hiding from garbage collector */ -MHeap runtime·mheap; +MHeap *runtime·mheap;MHeap型の変数からMHeap型へのポインタに変更され、GCから隠すためのpragmaディレクティブが削除されました。 -
runtime·mallocinitでの動的アロケーション:--- a/src/pkg/runtime/malloc.goc +++ b/src/pkg/runtime/malloc.goc @@ -314,6 +313,9 @@ runtime·mallocinit(void) USED(arena_size); USED(bitmap_size); +\tif((runtime·mheap = runtime·SysAlloc(sizeof(*runtime·mheap))) == nil)\n+\t\truntime·throw("runtime: cannot allocate heap metadata");\n+\n runtime·InitSizes();runtime·mallocinit関数内でruntime·mheapがSysAllocを使って動的に割り当てられるようになりました。 -
mheapメンバーへのアクセス変更:runtime·mheap.fieldの形式からruntime·mheap->fieldの形式への変更が、malloc.goc内のruntime·MHeap_Alloc,runtime·lock,runtime·unlock,runtime·FixAlloc_Alloc,runtime·FixAlloc_Free,runtime·MHeap_Init,runtime·mheap.arena_start,runtime·mheap.arena_used,runtime·mheap.map,runtime·mheap.allspans,runtime·mheap.nspanなど、mheapのメンバーにアクセスするすべての箇所で行われています。
src/pkg/runtime/malloc.h の変更点:
runtime·mheapの外部宣言:
ヘッダーファイルでも--- a/src/pkg/runtime/malloc.h +++ b/src/pkg/runtime/malloc.h @@ -427,7 +427,7 @@ struct MHeap FixAlloc spanalloc; // allocator for Span* FixAlloc cachealloc; // allocator for MCache* }; -extern MHeap runtime·mheap; +extern MHeap *runtime·mheap;runtime·mheapがポインタとして宣言されるように変更されました。
その他のファイル (mcache.c, mcentral.c, mgc0.c, mheap.c, race.c, traceback_arm.c, traceback_x86.c) の変更点:
- これらのファイルでも、
runtime·mheap.fieldの形式からruntime·mheap->fieldの形式へのアクセス変更が同様に行われています。例えば、runtime·mheap.central[sizeclass]はruntime·mheap->central[sizeclass]に、runtime·mheap.arena_startはruntime·mheap->arena_startに変更されています。
コアとなるコードの解説
このコミットの核心は、Goランタイムのメモリ管理において中心的な役割を果たす MHeap 構造体のライフサイクルとメモリ配置を変更した点にあります。
以前は、MHeap runtime·mheap; という宣言により、runtime·mheap はプログラムのBSSセグメントに静的に割り当てられていました。これは、プログラムの起動時にOSによってゼロ初期化される大きなメモリブロックを意味します。しかし、mheap のサイズが非常に大きくなる(256MBなど)と、一部のBSDシステムが持つBSSセグメントのサイズ制限に抵触し、Goプログラムが起動できない問題が発生していました。
この問題を解決するため、コミットでは runtime·mheap を MHeap *runtime·mheap; とポインタとして宣言するように変更しました。これにより、静的に確保されるのは MHeap 構造体そのものではなく、その構造体へのポインタのみとなります。ポインタのサイズは通常、システムのアドレス空間のサイズに依存し、非常に小さい(4バイトまたは8バイト)ため、BSSサイズ制限の問題を回避できます。
実際の MHeap 構造体のメモリは、runtime·mallocinit 関数内で runtime·SysAlloc(sizeof(*runtime·mheap)) を呼び出すことで、プログラムの初期化時に動的にOSから確保されます。SysAlloc はOSのシステムコールを介してメモリを要求するため、このメモリはBSSセグメントの制限とは無関係に、通常のヒープ領域またはOSが管理する他のメモリ領域に配置されます。
この変更に伴い、runtime·mheap のメンバーにアクセスするすべての箇所で、. (ドット) 演算子から -> (アロー) 演算子への変更が必要となりました。例えば、runtime·mheap.arena_start は runtime·mheap->arena_start に変更されています。これは、runtime·mheap がもはや直接の構造体ではなく、構造体へのポインタであるためです。
また、runtime·lock(&runtime·mheap) のようなロック操作も runtime·lock(runtime·mheap) に変更されています。これは、runtime·lock 関数がポインタを受け取るように設計されているため、mheap がポインタになったことで、そのポインタの値を直接渡すようになったことを示しています。
この変更は、Goランタイムのメモリ管理の基盤に影響を与えるものであり、Goプログラムの移植性と堅牢性を向上させる上で重要なステップでした。特に、リソース制限が厳しい環境や、特定のOSのメモリ管理ポリシーに起因する問題を解決する上で効果的です。
関連リンク
- Go Change-Id: https://golang.org/cl/7307122
- Go Issue: https://code.google.com/p/go/issues/detail?id=4447
参考にした情報源リンク
- Go Issue 4447: runtime: large bss causes problems on some BSDs
- Go Change 7307122: runtime: allocate heap metadata at run time
- Understanding the Go runtime: Memory allocation (Goのメモリ管理に関する一般的な情報源として)
- What is the .bss section in an executable? (BSSセグメントに関する一般的な情報源として)
- Go's Memory Allocator (Goのメモリ管理に関する一般的な情報源として)
- Go runtime source code (Goランタイムのソースコード)