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

[インデックス 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ビットシステムでは、int64uint64のような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バイト境界に配置されない可能性がありました。

このようなアラインメントの不整合は、特に以下の問題を引き起こす可能性があります。

  1. アトミック操作の失敗: 64ビットのアトミック操作(例: sync/atomicパッケージの関数)は、対象のデータが8バイトにアラインされていることを前提としています。アラインされていないデータに対してアトミック操作を実行しようとすると、CPUがアラインメントフォルトを発生させたり、操作が非アトミックになりデータ破損を引き起こしたりする可能性があります。
  2. パフォーマンスの低下: 一部のCPUアーキテクチャでは、アラインされていないメモリアクセスは、アラインされたアクセスよりも大幅に遅くなることがあります。
  3. 未定義の動作: 最悪の場合、アラインメントの不整合は、予測不能なプログラムのクラッシュや誤った計算結果につながる可能性があります。

このコミットは、runtime·mallocinit関数内で、メモリ割り当てアリーナを設定する前に、ポインタpnilに初期化することで、この問題を回避しようとしています。これは、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関数の冒頭近くで、ポインタ変数pnil(ヌルポインタ)に明示的に初期化しています。

この変更の意図は、SysReserveがアラインされていないアドレスを返した場合の潜在的な問題を軽減することにあります。pは、メモリ割り当てアリーナのベースアドレスを保持するために使用されるポインタであると推測されます。もしpが初期化されずに、SysReserveが不正な(アラインされていない)アドレスを返した場合、その後のコードがこの不正なアドレスを使用してメモリ操作を行おうとすると、問題が発生する可能性があります。

p = nil;とすることで、SysReserveが呼び出される前にpが既知の安全な状態(ヌル)に設定されます。これにより、もしSysReserveが何らかの理由で失敗したり、予期せぬ値がpに代入されたりした場合でも、その後のコードが未初期化の、あるいは不正なアドレスを誤って使用することを防ぐことができます。これは、特に32ビットシステムにおけるメモリのアラインメント問題のような、デバッグが困難な低レベルのバグに対する防御的なプログラミング手法と言えます。

この修正は、直接的にアラインメント問題を解決するものではなく、むしろアラインメント問題によって引き起こされる可能性のある、より深刻なランタイムエラー(例えば、ヌルポインタデリファレンスや不正なメモリアクセス)を防ぐための安全策として機能します。

関連リンク

参考にした情報源リンク