Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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): プログラムが実行時に動的にメモリを確保するために使用する領域です。mallocnew などの関数を通じてメモリが要求され、不要になったメモリは解放されます。Goにおいては、ガベージコレクタがヒープの管理を行います。

  • mheap 構造体: Goランタイムにおけるグローバルなヒープ管理構造体です。Goのメモリ管理システムの中核をなし、メモリのアロケーション、解放、ガベージコレクションのプロセスを調整します。mheap は、利用可能なメモリのページ(MSpan)の管理、アロケータ(FixAlloc)、キャッシュ(MCache)、中央リスト(MCentral)など、ヒープ全体の状態を追跡するための様々なフィールドを含んでいます。そのサイズは、管理するメモリ量やシステム構成によって大きくなります。

  • SysAlloc: GoランタイムがOSから直接メモリを要求するための低レベルな関数です。これは、Goのヒープ自体を拡張したり、mheap のようなランタイムの重要なデータ構造のためにメモリを確保したりする際に使用されます。

  • PageShiftPageMask: メモリページに関連する定数です。PageShift はページサイズを2のべき乗で表したときの指数(例: 12ならば4KBページ)、PageMask はページ内のオフセットをマスクするための値です。これらは、アドレスをページ境界にアラインしたり、ページ番号を計算したりするために使用されます。

  • MSpan: Goランタイムが管理するメモリの連続したページ群を表す構造体です。ヒープは多数の MSpan に分割され、それぞれが特定のサイズのオブジェクトを格納したり、空きメモリとして管理されたりします。

  • MCache: 各M (Machine、OSスレッドに相当) にローカルなメモリキャッシュです。Goルーチンが頻繁に小さなオブジェクトをアロケートする際に、グローバルな mheap へのロック競合を避けるために使用されます。MCacheMCentral から MSpan を取得し、そこからオブジェクトをアロケートします。

  • MCentral: 特定のサイズのオブジェクトをアロケートするための MSpan を管理する中央リストです。MCache がメモリを使い果たした際に、MCentral から MSpan を補充します。

  • ガベージコレクタ (GC): Goの自動メモリ管理システムです。不要になったメモリを自動的に識別し、解放することで、メモリリークを防ぎ、開発者が手動でメモリを管理する負担を軽減します。mheap はGCの動作に不可欠な情報を含んでいます。

技術的詳細

このコミットの技術的な核心は、Goランタイムのグローバルヒープ管理構造体である runtime·mheap の宣言と初期化の方法を変更することにあります。

変更前: runtime·mheapsrc/pkg/runtime/malloc.gocMHeap runtime·mheap; と宣言され、#pragma dataflag 16 ディレクティブによってGCの対象外とされていました。これは、mheap がBSSセグメントに静的に割り当てられることを意味します。

変更後:

  1. ポインタへの変更: runtime·mheapMHeap *runtime·mheap; とポインタとして宣言されるようになりました。これにより、mheap 構造体自体は静的に確保されず、そのポインタのみが静的に確保されます。
  2. 動的アロケーション: runtime·mallocinit 関数内で、runtime·mheap = runtime·SysAlloc(sizeof(*runtime·mheap)); という行が追加されました。これは、プログラムの初期化段階で SysAlloc を使用してOSから直接メモリを要求し、そのメモリ領域に MHeap 構造体を動的に割り当てることを意味します。
  3. アクセス方法の変更: mheap がポインタになったため、runtime·mheap.field のような直接アクセスは runtime·mheap->field のようにポインタを介したアクセスに変更されました。これは、malloc.gocmcache.cmcentral.cmgc0.cmheap.crace.ctraceback_arm.ctraceback_x86.c など、mheap にアクセスするすべてのファイルにわたって行われています。
  4. ロックの変更: runtime·lock(&runtime·mheap) のような mheap 構造体自体へのロックも、runtime·lock(runtime·mheap) のようにポインタが指す実体へのロックに変更されました。

この変更により、mheap 構造体はBSSセグメントではなく、OSから動的に確保されたメモリ領域に配置されるため、一部のBSDシステムで発生していたBSSサイズ制限の問題を回避できます。また、mheap の実際のメモリは必要になったときにOSによってページインされるため、静的に確保されていた場合に比べて、起動時のメモリフットプリントが最適化される可能性もあります。

コアとなるコードの変更箇所

主要な変更は src/pkg/runtime/malloc.gocsrc/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·mheapSysAlloc を使って動的に割り当てられるようになりました。

  • 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_startruntime·mheap->arena_start に変更されています。

コアとなるコードの解説

このコミットの核心は、Goランタイムのメモリ管理において中心的な役割を果たす MHeap 構造体のライフサイクルとメモリ配置を変更した点にあります。

以前は、MHeap runtime·mheap; という宣言により、runtime·mheap はプログラムのBSSセグメントに静的に割り当てられていました。これは、プログラムの起動時にOSによってゼロ初期化される大きなメモリブロックを意味します。しかし、mheap のサイズが非常に大きくなる(256MBなど)と、一部のBSDシステムが持つBSSセグメントのサイズ制限に抵触し、Goプログラムが起動できない問題が発生していました。

この問題を解決するため、コミットでは runtime·mheapMHeap *runtime·mheap; とポインタとして宣言するように変更しました。これにより、静的に確保されるのは MHeap 構造体そのものではなく、その構造体へのポインタのみとなります。ポインタのサイズは通常、システムのアドレス空間のサイズに依存し、非常に小さい(4バイトまたは8バイト)ため、BSSサイズ制限の問題を回避できます。

実際の MHeap 構造体のメモリは、runtime·mallocinit 関数内で runtime·SysAlloc(sizeof(*runtime·mheap)) を呼び出すことで、プログラムの初期化時に動的にOSから確保されます。SysAlloc はOSのシステムコールを介してメモリを要求するため、このメモリはBSSセグメントの制限とは無関係に、通常のヒープ領域またはOSが管理する他のメモリ領域に配置されます。

この変更に伴い、runtime·mheap のメンバーにアクセスするすべての箇所で、. (ドット) 演算子から -> (アロー) 演算子への変更が必要となりました。例えば、runtime·mheap.arena_startruntime·mheap->arena_start に変更されています。これは、runtime·mheap がもはや直接の構造体ではなく、構造体へのポインタであるためです。

また、runtime·lock(&runtime·mheap) のようなロック操作も runtime·lock(runtime·mheap) に変更されています。これは、runtime·lock 関数がポインタを受け取るように設計されているため、mheap がポインタになったことで、そのポインタの値を直接渡すようになったことを示しています。

この変更は、Goランタイムのメモリ管理の基盤に影響を与えるものであり、Goプログラムの移植性と堅牢性を向上させる上で重要なステップでした。特に、リソース制限が厳しい環境や、特定のOSのメモリ管理ポリシーに起因する問題を解決する上で効果的です。

関連リンク

参考にした情報源リンク