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

[インデックス 14096] ファイルの概要

このコミットは、Go言語のリンカ (cmd/ld) とランタイム (src/pkg/runtime/mgc0.c) におけるメモリ配置(アライメント)の改善に関するものです。特に、大きなデータおよびBSS(Block Started by Symbol)セグメントのオブジェクトに対して64ビットのアライメントを適用し、ガベージコレクタ(GC)における重要なミスアライメントの問題を回避することを目的としています。これは、Go issue 599に対する完全な修正ではありませんが、重要な回避策として導入されました。

コミット

commit f76f120324f449cff3fdaeb05effbe18162e0cf1
Author: Russ Cox <rsc@golang.org>
Date:   Tue Oct 9 12:50:06 2012 -0400

    cmd/ld: use 64-bit alignment for large data and bss objects
    
    Check for specific, important misalignment in garbage collector.
    Not a complete fix for issue 599 but an important workaround.
    
    Update #599.
    
    R=golang-dev, iant, dvyukov
    CC=golang-dev
    https://golang.org/cl/6641049

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/f76f120324f449cff3fdaeb05effbe18162e0cf1

元コミット内容

diff --git a/src/cmd/ld/data.c b/src/cmd/ld/data.c
index 89eccd143c..4afe4b801c 100644
--- a/src/cmd/ld/data.c
+++ b/src/cmd/ld/data.c
@@ -831,7 +831,9 @@ dosymtype(void)
 static int32
 alignsymsize(int32 s)
 {
-\tif(s >= PtrSize)\n+\tif(s >= 8)\n+\t\ts = rnd(s, 8);\n+\telse if(s >= PtrSize)\n \t\ts = rnd(s, PtrSize);\n \telse if(s > 2)\n \t\ts = rnd(s, 4);\
@@ -1054,6 +1056,7 @@ dodata(void)\
 \t\tdatsize += rnd(s->size, PtrSize);\n \t}\n \tsect->len = datsize - sect->vaddr;\n+\tdatsize = rnd(datsize, PtrSize);\
 \n \t/* gcdata */\n \tsect = addsection(&segtext, \".gcdata\", 04);\
diff --git a/src/pkg/runtime/mgc0.c b/src/pkg/runtime/mgc0.c
index 6c2ce00953..dc3b877c4e 100644
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -892,6 +892,12 @@ runtime·gc(int32 force)\
 \tM *m1;\n \tuint32 i;\n \n+\t// The atomic operations are not atomic if the uint64s\n+\t// are not aligned on uint64 boundaries. This has been\n+\t// a problem in the past.\n+\tif((((uintptr)&work.empty) & 7) != 0)\n+\t\truntime·throw(\"runtime: gc work buffer is misaligned\");\n+\n \t// The gc is turned off (via enablegc) until\n \t// the bootstrap has completed.\n \t// Also, malloc gets called in the guts\

変更の背景

このコミットは、Go言語のガベージコレクタ(GC)が、特に32ビットシステム上で64ビット値(uint64など)を扱う際に発生するメモリ配置(アライメント)の問題に対処するために行われました。Go issue 599は、32ビットシステム上で64ビットデータが8バイト境界にアライメントされていない場合に、パフォーマンスの低下や、より深刻な場合はアトミック操作の失敗を引き起こす可能性を指摘していました。

アトミック操作は、複数のゴルーチンが同時に共有データにアクセスする際に、データの整合性を保証するために不可欠です。しかし、これらの操作は、対象となるデータが特定のメモリ境界に適切に配置されている(アライメントされている)ことを前提としています。特に64ビットのアトミック操作は、8バイト境界にアライメントされている必要があります。32ビットシステムでは、デフォルトのメモリ配置が4バイト境界であるため、64ビット値が適切にアライメントされないことがありました。

ガベージコレクタの内部で使用されるワークバッファのような重要なデータ構造がミスアライメントされていると、GCの動作が不安定になったり、クラッシュしたりする可能性があります。このコミットは、リンカが大きなデータオブジェクトやBSSオブジェクトを配置する際に、より厳密な64ビットアライメントを強制することで、この問題に対する重要な回避策を提供します。また、ランタイムにアライメントチェックを追加することで、問題が検出された場合に早期にパニックを発生させ、デバッグを容易にしています。

前提知識の解説

cmd/ld (Goリンカ)

cmd/ldはGo言語のリンカです。コンパイラによって生成されたオブジェクトファイル(.oファイル)を結合し、実行可能なバイナリファイルを作成する役割を担います。このプロセスには、シンボルの解決、ライブラリのリンク、そしてメモリセグメント(コード、データ、BSSなど)の配置とアライメントの調整が含まれます。

データセグメントとBSSセグメント

  • データセグメント (.data): 初期化されたグローバル変数や静的変数が格納されるメモリ領域です。プログラムの実行開始時に、実行可能ファイルからこれらの変数の初期値がメモリにロードされます。
  • BSSセグメント (.bss): 初期化されていないグローバル変数や静的変数が格納されるメモリ領域です。これらの変数は、プログラムの実行開始時にゼロまたはヌルポインタで初期化されます。BSSセグメントは、実行可能ファイル内では領域を占有せず、実行時にメモリが割り当てられます。

メモリ配置(アライメント)

メモリ配置(アライメント)とは、データがメモリ内の特定のアドレス境界に配置されることを保証するプロセスです。例えば、4バイトの整数は4の倍数のアドレスに、8バイトの倍精度浮動小数点数は8の倍数のアドレスに配置されるといった具合です。

  • パフォーマンス: 多くのCPUは、アライメントされたデータにアクセスする方が、アライメントされていないデータにアクセスするよりも効率的です。ミスアライメントされたアクセスは、複数のメモリアクセスを必要としたり、パフォーマンスペナルティを引き起こしたりする可能性があります。
  • ハードウェア要件: 一部のハードウェアアーキテクチャでは、データが特定のアライメントを満たすことを厳密に要求します。ミスアライメントされたデータへのアクセスは、ハードウェア例外やプログラムのクラッシュを引き起こす可能性があります。

PtrSize

Go言語におけるPtrSizeは、ポインタのサイズをバイト単位で表す定数です。これは、Goプログラムがコンパイルされ実行されるシステムのアーキテクチャに依存します。

  • 32ビットシステムでは、PtrSizeは4バイトです。
  • 64ビットシステムでは、PtrSizeは8バイトです。

Goのガベージコレクタ(GC)

Goは、並行性、非世代型、三色マーク&スイープ方式のガベージコレクタを採用しています。GCは、自動的にメモリの割り当てと解放を管理し、開発者が手動でメモリ管理を行う必要をなくします。GCはアプリケーションとほとんど並行して動作し、「Stop-The-World」(STW)ポーズを最小限に抑えます。

アトミック操作

sync/atomicパッケージは、Goにおいて低レベルのアトミック操作を提供します。これらの操作は、ミューテックスのような明示的なロックメカニズムなしに、共有データへの安全な並行アクセスを可能にします。アトミック操作はハードウェアレベルで実装されており、カウンタ、フラグ、または単純な状態遷移を管理するのに効率的です。

特に64ビットのアトミック操作(int64uint64型に対する操作)は、変数のメモリアドレスが8バイト境界にアライメントされていることを要求します。32ビットアーキテクチャでは、Goコンパイラは通常4バイトアライメントしか保証しないため、ミスアライメントされた64ビット値に対するアトミック操作はランタイムパニックを引き起こす可能性があります。

技術的詳細

このコミットの核心は、メモリのアライメント、特に64ビット値のアライメントをリンカレベルで強化し、ランタイムでその整合性をチェックすることにあります。

リンカ (cmd/ld/data.c) におけるアライメントの強化

リンカは、プログラムのデータセグメントとBSSセグメントを構築する際に、各シンボル(変数など)のサイズとアライメント要件に基づいてメモリを配置します。

変更前のalignsymsize関数は、シンボルサイズsPtrSize以上の場合にPtrSizeでアライメントしていました。これは32ビットシステムでは4バイトアライメントを意味します。しかし、64ビット値(uint64など)は、アトミック操作の要件から8バイトアライメントが必要とされます。

このコミットでは、alignsymsize関数に以下の変更が加えられました。

-\tif(s >= PtrSize)\n+\tif(s >= 8)\n+\t\ts = rnd(s, 8);\n+\telse if(s >= PtrSize)\n \t\ts = rnd(s, PtrSize);\

この変更により、シンボルサイズsが8バイト以上の場合、まず8バイト境界にアライメントされるようになりました。これにより、32ビットシステムであっても、大きなデータオブジェクト(特に64ビット値を含むもの)が適切に8バイトアライメントされることが保証されます。PtrSizeによるアライメントは、8バイト未満のPtrSize以上のオブジェクト(例えば、32ビットシステムでのポインタ自体)に対して引き続き適用されます。

また、dodata関数では、データセグメント全体のサイズdatsizePtrSizeでアライメントする処理に加えて、最終的なdatsizeを再度PtrSizeでアライメントする行が追加されました。

 \tsect->len = datsize - sect->vaddr;\n+\tdatsize = rnd(datsize, PtrSize);\

これは、データセグメント全体の終端がポインタサイズのアライメント境界に揃うようにするための追加の保証です。これにより、後続のメモリ領域が適切に配置されることを助けます。

rnd(s, align)関数は、salignの倍数に切り上げる(ラウンドアップする)ユーティリティ関数です。例えば、rnd(5, 8)は8を返します。

ランタイム (src/pkg/runtime/mgc0.c) におけるアライメントチェック

ガベージコレクタの内部では、work.emptyのような重要なデータ構造が使用されます。これらの構造がアトミック操作に関与する場合、そのメモリ配置は極めて重要です。

このコミットでは、runtime·gc関数の冒頭に、work.emptyのアライメントをチェックするコードが追加されました。

+\t// The atomic operations are not atomic if the uint64s\n+\t// are not aligned on uint64 boundaries. This has been\n+\t// a problem in the past.\n+\tif((((uintptr)&work.empty) & 7) != 0)\n+\t\truntime·throw(\"runtime: gc work buffer is misaligned\");\

このコードは、work.emptyのアドレス(&work.empty)をuintptr型にキャストし、その下位3ビットをチェックしています。& 7は、アドレスが8の倍数であるかどうかをチェックするビット演算です(8の倍数であれば下位3ビットはすべて0になります)。もし結果が0でなければ、アドレスは8バイト境界にアライメントされていないことを意味します。

このようなミスアライメントが検出された場合、runtime·throw("runtime: gc work buffer is misaligned");が呼び出され、ランタイムパニックが発生します。これにより、GCの不安定な動作やクラッシュが発生する前に、問題を早期に特定し、デバッグすることが可能になります。これは、Go issue 599で指摘された問題、特に32ビットシステムでの64ビットアトミック操作の信頼性に関する問題に対する直接的な防御策です。

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

src/cmd/ld/data.c

  • alignsymsize関数: シンボルサイズが8バイト以上の場合、8バイトアライメントを強制する条件が追加されました。
  • dodata関数: データセグメント全体のサイズをPtrSizeでアライメントする処理が追加されました。

src/pkg/runtime/mgc0.c

  • runtime·gc関数: ガベージコレクタのワークバッファ(work.empty)が8バイト境界にアライメントされているかをチェックし、ミスアライメントの場合にランタイムパニックを発生させるコードが追加されました。

コアとなるコードの解説

src/cmd/ld/data.c の変更

 static int32
 alignsymsize(int32 s)
 {
-\tif(s >= PtrSize)\n+\tif(s >= 8)\n+\t\ts = rnd(s, 8);\n+\telse if(s >= PtrSize)\n \t\ts = rnd(s, PtrSize);\
 \telse if(s > 2)\
 \t\ts = rnd(s, 4);\

この変更は、リンカが個々のシンボル(変数など)のサイズsに基づいて、そのメモリ配置を決定するロジックを修正しています。

  • if(s >= 8): 新しく追加された条件です。もしシンボルのサイズが8バイト以上であれば、そのシンボルは8バイト境界にアライメントされます。これは、特に64ビット値(int64, uint64, float64など)が適切にアライメントされることを保証します。32ビットシステムではPtrSizeが4バイトであるため、この条件がなければ64ビット値が4バイトアライメントされてしまう可能性がありました。
  • else if(s >= PtrSize): 以前の条件です。シンボルサイズがPtrSize以上(かつ8バイト未満)の場合、PtrSizeでアライメントされます。これは、ポインタやその他のPtrSize以上のデータ型に適用されます。
  • else if(s > 2): 2バイトより大きいシンボルは4バイトアライメントされます。
  • それ以外: デフォルトのアライメントが適用されます。

この修正により、リンカはより厳密なアライメント規則を適用し、特に64ビット値がアトミック操作の要件を満たすようにします。

 \tsect->len = datsize - sect->vaddr;\n+\tdatsize = rnd(datsize, PtrSize);\

この行は、dodata関数内でデータセグメントの最終的なサイズdatsizeを計算した後に追加されました。rnd(datsize, PtrSize)は、datsizePtrSizeの倍数に切り上げます。これにより、データセグメント全体のサイズがポインタサイズのアライメント境界に揃えられ、その後に続くメモリセグメントやオブジェクトが適切に配置されるための基盤が強化されます。

src/pkg/runtime/mgc0.c の変更

+\t// The atomic operations are not atomic if the uint64s\n+\t// are not aligned on uint64 boundaries. This has been\n+\t// a problem in the past.\n+\tif((((uintptr)&work.empty) & 7) != 0)\n+\t\truntime·throw(\"runtime: gc work buffer is misaligned\");\

このコードは、ガベージコレクタのメイン関数であるruntime·gcの開始時に実行されます。

  • &work.empty: ガベージコレクタの内部で使用されるwork.emptyというデータ構造のアドレスを取得します。
  • (uintptr)&work.empty: アドレスをuintptr型(ポインタを保持できる整数型)にキャストします。
  • & 7: ビットAND演算子を使って、アドレスの下位3ビットを抽出します。8バイト境界にアライメントされているアドレスは、常に8の倍数であり、そのバイナリ表現の下位3ビットはすべて0になります(例: ...000_2)。したがって、この演算の結果が0でなければ、アドレスは8バイト境界にアライメントされていないことを意味します。
  • != 0: アライメントが正しくない場合に真となります。
  • runtime·throw("runtime: gc work buffer is misaligned");: アライメントが正しくない場合、この関数が呼び出され、指定されたエラーメッセージと共にランタイムパニックが発生します。これにより、GCが不正なメモリ状態にアクセスする前に、問題を早期に検出し、プログラムのクラッシュを防ぎつつ、デバッグを容易にします。

このランタイムチェックは、リンカによるアライメントの改善が期待通りに機能しているかを確認するためのセーフティネットとして機能します。

関連リンク

参考にした情報源リンク