[インデックス 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ランタイムのソースコード)