[インデックス 11724] ファイルの概要
このコミットは、Goランタイムにおける32ビットシステム上での「SysReserveがアラインされていないアドレスを返した」というバグを修正するものです。具体的には、runtime/malloc.goc
ファイル内のメモリ割り当て初期化処理において、ポインタの初期化に関する変更が加えられています。
コミット
commit 073aeff785c8fc2e187e48842f795012addfdf49
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Thu Feb 9 09:25:10 2012 +1100
runtime: fix "SysReserve returned unaligned address" bug on 32-bit systems
R=rsc
CC=golang-dev
https://golang.org/cl/5642064
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/073aeff785c8fc2e187e48842f795012addfdf49
元コミット内容
runtime: fix "SysReserve returned unaligned address" bug on 32-bit systems
変更の背景
このコミットは、Goランタイムが32ビットシステム上でメモリを予約する際に、アラインメント(メモリ配置の整列)の問題が発生するバグを修正するために行われました。特に、SysReserve
関数(またはruntime/malloc.go
に見られるsysReserveAligned
のような関数)がアドレス空間を予約する際に、後続の操作、特に64ビットのアトミック操作に必要なアラインメントが満たされない場合に問題が発生しました。
32ビットシステムでは、int64
やuint64
のような64ビット型のデフォルトのメモリ配置は通常4バイトであり、8バイトではありません。これにより、8バイトのアラインメントを必要とする64ビットのアトミック操作を実行する際に問題が生じます。もし64ビットの値が8バイトにアラインされていない場合、それに対するアトミック操作はランタイムパニックを引き起こしたり、誤った動作につながる可能性がありました。
この問題は、Goの古いバージョンで特に顕著であり、Goのメモリ管理とアラインメントの厳密な要件に起因しています。このコミットは、このようなアラインメントの不整合によって引き起こされる潜在的なクラッシュや不正な動作を防ぐための重要な修正でした。
前提知識の解説
メモリのアラインメント
メモリのアラインメントとは、コンピュータのメモリ上でデータが配置される際の、特定の境界への整列を指します。CPUは、特定のデータ型(例えば、整数やポインタ)を特定のバイト境界(例えば、4バイト境界や8バイト境界)に配置されている場合に、最も効率的にアクセスできます。
- アラインされたアクセス: データがそのデータ型に適した境界に配置されている場合。CPUは単一のメモリ操作でデータを読み書きできます。
- アラインされていないアクセス: データがそのデータ型に適した境界に配置されていない場合。CPUはデータを読み書きするために複数のメモリ操作を実行する必要があるか、あるいはハードウェアによっては例外(アラインメントフォルト)を発生させる可能性があります。特にアトミック操作のような厳密なメモリ操作では、アラインメントが必須となることが多いです。
32ビットシステムと64ビットシステム
- 32ビットシステム: CPUのレジスタが32ビット幅であり、一度に32ビットのデータを処理します。ポインタのサイズも通常32ビット(4バイト)です。これにより、最大4GBのメモリ空間を直接アドレス指定できます。
- 64ビットシステム: CPUのレジスタが64ビット幅であり、一度に64ビットのデータを処理します。ポインタのサイズも通常64ビット(8バイト)です。これにより、非常に広大なメモリ空間をアドレス指定できます。
問題は、32ビットシステム上で64ビットのデータ型(例: int64
)を扱う際に発生します。32ビットシステムでは、デフォルトのアラインメントが4バイトであることが多く、64ビットデータが4バイト境界に配置されると、64ビットのアトミック操作に必要な8バイトアラインメントが満たされない場合があります。
SysReserve
関数
SysReserve
は、Goランタイムがオペレーティングシステムからメモリ領域を予約するために使用する低レベルの関数です。これは、Goのガベージコレクタやメモリマネージャが使用するアリーナ(連続したメモリ領域)を確保する際に呼び出されます。この関数が返すアドレスは、Goランタイムがその後のメモリ割り当てや操作を正しく実行するために、特定のアラインメント要件を満たす必要があります。
アトミック操作
アトミック操作とは、複数のCPUコアやスレッドから同時にアクセスされた場合でも、その操作全体が不可分(中断されない)であることを保証する操作です。例えば、64ビットのカウンタを複数のスレッドから同時にインクリメントする場合、アトミック操作を使用しないと競合状態が発生し、カウンタの値が不正になる可能性があります。アトミック操作は、通常、特定のメモリ境界にアラインされたデータに対してのみ正しく機能します。
技術的詳細
このバグは、Goランタイムが32ビットシステム上でSysReserve
を呼び出してメモリを予約する際に、返されるアドレスが64ビットデータ型に必要な8バイトアラインメントを満たさない場合に発生しました。
Goのメモリ割り当てシステムは、mheap
というヒープ構造を管理し、そこからオブジェクトを割り当てます。mheap
は、mspan
と呼ばれる連続したメモリブロックのリストで構成されています。これらのmspan
は、SysReserve
によって予約された大きなメモリ領域から取得されます。
問題は、32ビットシステムでは、SysReserve
が返すアドレスが必ずしも8バイト境界にアラインされているとは限らない点にありました。Goランタイムは、内部的に64ビットの値を扱うことがあり、特にアトミック操作が必要な場面では、これらの値が8バイトにアラインされていることが期待されます。しかし、SysReserve
が4バイト境界にアラインされたアドレスを返した場合、その後のメモリ割り当てで64ビットの値が8バイト境界に配置されない可能性がありました。
このようなアラインメントの不整合は、特に以下の問題を引き起こす可能性があります。
- アトミック操作の失敗: 64ビットのアトミック操作(例:
sync/atomic
パッケージの関数)は、対象のデータが8バイトにアラインされていることを前提としています。アラインされていないデータに対してアトミック操作を実行しようとすると、CPUがアラインメントフォルトを発生させたり、操作が非アトミックになりデータ破損を引き起こしたりする可能性があります。 - パフォーマンスの低下: 一部のCPUアーキテクチャでは、アラインされていないメモリアクセスは、アラインされたアクセスよりも大幅に遅くなることがあります。
- 未定義の動作: 最悪の場合、アラインメントの不整合は、予測不能なプログラムのクラッシュや誤った計算結果につながる可能性があります。
このコミットは、runtime·mallocinit
関数内で、メモリ割り当てアリーナを設定する前に、ポインタp
をnil
に初期化することで、この問題を回避しようとしています。これは、SysReserve
が返すアドレスがアラインされていない場合に、その後の処理で不正なメモリ参照やアラインメントの問題が発生するのを防ぐための防御的な措置と考えられます。
コアとなるコードの変更箇所
--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -261,6 +261,8 @@ runtime·mallocinit(void)
extern byte end[];
byte *want;
+ p = nil;
+
runtime·InitSizes();
// Set up the allocation arena, a contiguous area of memory where
コアとなるコードの解説
変更はsrc/pkg/runtime/malloc.goc
ファイルのruntime·mallocinit
関数内で行われています。
runtime·mallocinit
関数は、Goランタイムのメモリ割り当てシステムを初期化する役割を担っています。この関数は、ヒープアリーナの設定、メモリサイズの初期化など、重要なメモリ管理タスクを実行します。
追加された行は以下の通りです。
p = nil;
この行は、runtime·mallocinit
関数の冒頭近くで、ポインタ変数p
をnil
(ヌルポインタ)に明示的に初期化しています。
この変更の意図は、SysReserve
がアラインされていないアドレスを返した場合の潜在的な問題を軽減することにあります。p
は、メモリ割り当てアリーナのベースアドレスを保持するために使用されるポインタであると推測されます。もしp
が初期化されずに、SysReserve
が不正な(アラインされていない)アドレスを返した場合、その後のコードがこの不正なアドレスを使用してメモリ操作を行おうとすると、問題が発生する可能性があります。
p = nil;
とすることで、SysReserve
が呼び出される前にp
が既知の安全な状態(ヌル)に設定されます。これにより、もしSysReserve
が何らかの理由で失敗したり、予期せぬ値がp
に代入されたりした場合でも、その後のコードが未初期化の、あるいは不正なアドレスを誤って使用することを防ぐことができます。これは、特に32ビットシステムにおけるメモリのアラインメント問題のような、デバッグが困難な低レベルのバグに対する防御的なプログラミング手法と言えます。
この修正は、直接的にアラインメント問題を解決するものではなく、むしろアラインメント問題によって引き起こされる可能性のある、より深刻なランタイムエラー(例えば、ヌルポインタデリファレンスや不正なメモリアクセス)を防ぐための安全策として機能します。
関連リンク
- Go CL 5642064: runtime: fix "SysReserve returned unaligned address" bug on 32-bit systems
- Go Issue #599: runtime: 64-bit atomic operations on 32-bit systems require 8-byte alignment (このコミットの背景にある、より広範なアラインメント問題に関する議論)
- Go Proposal #36606: runtime: make 64-bit fields 64-bit aligned on 32-bit systems (32ビットシステムでの64ビットフィールドのアラインメントに関する提案)
参考にした情報源リンク
- Go 101: Memory Alignment
- Google Groups: golang-dev - Re: [go-nuts] 64-bit atomic operations on 32-bit systems (具体的なURLは検索結果から推測)
- Narkive: [go-nuts] 64-bit atomic operations on 32-bit systems (具体的なURLは検索結果から推測)
- Stack Overflow: Why is memory alignment important?
- Hubert Siwik: Go memory alignment
- Chewxy: Go Memory Alignment
- Reddit: Go 1.19 sync/atomic.Int64 and atomic.Uint64 are guaranteed to be 8-byte aligned even on 32-bit architectures