[インデックス 1664] ファイルの概要
このコミットは、Goランタイムのガベージコレクション(GC)におけるバグ修正を目的としています。具体的には、メモリ割り当てとGC参照のルックアップに関連する問題に対処しています。src/runtime/malloc.c
とsrc/runtime/mheap.c
の2つのファイルが変更されており、GCの整合性を保つためのチェックが追加されています。
コミット
commit 7cd24361bd66f11ec2beb1905052a3b73cdf3dac
Author: Russ Cox <rsc@golang.org>
Date: Wed Feb 11 17:54:03 2009 -0800
fix gc bug. i think this is tgs's second bug.
i stumbled across it in all.bash.
TBR=r
OCL=24912
CL=24912
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7cd24361bd66f11ec2beb1905052a3b73cdf3dac
元コミット内容
このコミットの元のメッセージは「fix gc bug. i think this is tgs's second bug. i stumbled across it in all.bash.」です。これは、ガベージコレクションにおけるバグを修正するものであり、作者(Russ Cox)がall.bash
スクリプトの実行中に偶然発見したことを示唆しています。また、「tgs's second bug」という記述から、以前にも同様のバグが報告されていた可能性が示唆されます。
変更の背景
Goの初期のガベージコレクタは、現在のものとは異なり、よりシンプルなマーク&スイープ方式を採用していました。この時期のGCは、まだ開発途上にあり、様々なエッジケースやバグが発見されていました。このコミットは、mlookup
関数におけるGC参照の整合性チェックが不十分であったために発生したバグを修正するものです。
mlookup
関数は、与えられたポインタがどのメモリブロックに属し、そのブロックのサイズやGC参照情報がどこにあるかを特定するために使用されます。この関数が誤った情報を返したり、不正なメモリ領域にアクセスしようとすると、GCの処理が失敗したり、プログラムがクラッシュする原因となります。
また、MHeap_LookupMaybe
関数は、特定のページIDに対応するMSpan
構造体を見つける役割を担っています。この関数が、使用中のMSpan
ではないものを返してしまうと、GCが誤ったメモリ領域を処理しようとし、結果としてバグを引き起こす可能性があります。
このコミットは、これらの問題に対処し、Goランタイムの安定性とGCの正確性を向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、Goランタイムのメモリ管理とガベージコレクションに関する基本的な知識が必要です。
- Goのメモリ管理: Goランタイムは独自のメモリマネージャを持っており、OSから大きなメモリブロック(ヒープ)を確保し、それを細かく分割してGoプログラムに割り当てます。
MHeap
: Goランタイムのヒープ全体を管理する構造体です。メモリの確保や解放、MSpan
の管理などを行います。MSpan
:MHeap
によって管理されるメモリの連続したブロックを表す構造体です。MSpan
は、特定のサイズのオブジェクトを格納するために使用される「サイズクラス」に分類されます。PageShift
: メモリページのサイズを示す定数です。1 << PageShift
でページのバイトサイズが得られます。RefcountOverhead
: オブジェクトの参照カウントを管理するためのオーバーヘッドバイト数です。初期のGo GCでは、参照カウントもGCの一部として利用されていました。- ガベージコレクション (GC): GoのGCは、不要になったメモリを自動的に解放する仕組みです。初期のGoでは、マーク&スイープ方式が採用されており、GCサイクル中に到達可能なオブジェクトをマークし、マークされなかったオブジェクトをスイープ(解放)していました。
gcref
:MSpan
構造体内に存在するフィールドで、そのスパン内のオブジェクトのGC参照情報(例えば、参照カウントやマークビット)を指します。mlookup
関数: 特定のメモリアドレス(ポインタ)がどのMSpan
に属し、そのMSpan
内のどのオブジェクトに対応するかをルックアップする関数です。GCがオブジェクトを走査する際に、この関数を使ってオブジェクトのメタデータ(サイズ、GC参照情報など)を取得します。MHeap_LookupMaybe
関数: 特定のページIDから対応するMSpan
を見つけるためのヘルパー関数です。
技術的詳細
このコミットは、主に以下の2つの変更を含んでいます。
-
src/runtime/malloc.c
におけるmlookup
関数の修正:- 元のコードでは、
mlookup
関数内でep
という変数が宣言されていましたが、使用されていませんでした。これは、コンパイラの警告を避けるため、または以前のコードの名残である可能性があります。このコミットでは、ep
変数の宣言が削除されています。 - 最も重要な変更は、
s->gcref
(GC参照情報へのポインタ)の範囲チェックにおけるデバッグ出力の強化です。以前は、s->gcref
が不正な範囲にある場合に、簡潔な情報しか出力されませんでした。このコミットでは、より多くのデバッグ情報(s->state
,s
,p
,s->sizeclass
,nobj
,n
,npages
,v
,gcref + nobj
,p + (s->npages << PageShift)
など)が追加されています。これにより、bad gcref
エラーが発生した際に、問題の原因を特定しやすくなります。このデバッグ出力の強化は、バグの再現と解析を助けるためのものです。
- 元のコードでは、
-
src/runtime/mheap.c
におけるMHeap_LookupMaybe
関数の修正:- この関数は、与えられたページID
p
に対応するMSpan
をヒープh
から検索します。 - 追加された行
if(s->state != MSpanInUse) return nil;
は、検索されたMSpan
が実際に使用中(MSpanInUse
)の状態であるかどうかを確認します。もし、MSpan
が使用中でない場合(例えば、解放済みであるか、他の目的で使用されている場合)、この関数はnil
を返します。 - このチェックは、GCが不正な
MSpan
を処理しようとするのを防ぎ、GCの整合性を保つために非常に重要です。これにより、GCが既に解放されたメモリや、GCの対象ではないメモリを誤って参照することを防ぎます。
- この関数は、与えられたページID
これらの変更は、Goランタイムのメモリ管理とGCの堅牢性を高めるためのものであり、特にGCのバグを特定し、修正するプロセスを支援することを目的としています。
コアとなるコードの変更箇所
src/runtime/malloc.c
--- a/src/runtime/malloc.c
+++ b/src/runtime/malloc.c
@@ -122,7 +122,7 @@ int32
mlookup(void *v, byte **base, uintptr *size, uint32 **ref)
{
uintptr n, nobj, i;
- byte *p, *ep;
+ byte *p;
MSpan *s;
s = MHeap_LookupMaybe(&mheap, (uintptr)v>>PageShift);
@@ -162,8 +162,11 @@ mlookup(void *v, byte **base, uintptr *size, uint32 **ref)
*size = n;
nobj = (s->npages << PageShift) / (n + RefcountOverhead);
if((byte*)s->gcref < p || (byte*)(s->gcref+nobj) > p+(s->npages<<PageShift)) {
-\t\tprintf("s->base sizeclass %d %p gcref %p block %D\n",
-\t\t\ts->sizeclass, p, s->gcref, s->npages<<PageShift);\
+\t\tprintf("odd span state=%d span=%p base=%p sizeclass=%d n=%d size=%d npages=%d\n",
+\t\t\ts->state, s, p, s->sizeclass, nobj, n, s->npages);\
+\t\tprintf("s->base sizeclass %d v=%p base=%p gcref=%p blocksize=%D nobj=%d size=%D end=%p end=%p\n",
+\t\t\ts->sizeclass, v, p, s->gcref, s->npages<<PageShift,\
+\t\t\tnobj, n, s->gcref + nobj, p+(s->npages<<PageShift));\
throw("bad gcref");
}
if(ref)
src/runtime/mheap.c
--- a/src/runtime/mheap.c
+++ b/src/runtime/mheap.c
@@ -228,6 +228,8 @@ MHeap_LookupMaybe(MHeap *h, PageID p)
s = MHeapMap_GetMaybe(&h->map, p);
if(s == nil || p < s->start || p - s->start >= s->npages)
return nil;
+\tif(s->state != MSpanInUse)
+\t\treturn nil;
return s;
}
コアとなるコードの解説
src/runtime/malloc.c
の変更点
-
byte *p, *ep;
からbyte *p;
への変更:- これは、未使用の変数
ep
の宣言を削除したものです。コードのクリーンアップであり、機能的な変更はありません。
- これは、未使用の変数
-
printf
デバッグ出力の強化:if((byte*)s->gcref < p || (byte*)(s->gcref+nobj) > p+(s->npages<<PageShift))
の条件は、s->gcref
が指すGC参照情報が、現在のメモリブロックの有効な範囲内にあるかどうかをチェックしています。- この条件が真(つまり、
s->gcref
が不正な範囲を指している)の場合、以前は簡潔なエラーメッセージが出力されていました。 - 変更後では、2行にわたる詳細な
printf
文が追加されています。- 1行目では、
MSpan
の状態(s->state
)、MSpan
のアドレス(s
)、ベースアドレス(p
)、サイズクラス(s->sizeclass
)、オブジェクト数(nobj
)、オブジェクトサイズ(n
)、ページ数(npages
)が出力されます。 - 2行目では、サイズクラス(
s->sizeclass
)、ルックアップ対象のポインタ(v
)、ベースアドレス(p
)、GC参照情報のアドレス(s->gcref
)、ブロックサイズ(s->npages<<PageShift
)、オブジェクト数(nobj
)、オブジェクトサイズ(n
)、GC参照情報の終了アドレス(s->gcref + nobj
)、ブロックの終了アドレス(p+(s->npages<<PageShift)
)が出力されます。
- 1行目では、
- これらの追加情報は、
bad gcref
エラーが発生した際に、どのMSpan
がどのような状態であり、GC参照情報がなぜ不正な範囲を指しているのかを詳細に分析するための手がかりとなります。これは、デバッグを大幅に容易にするための変更です。
src/runtime/mheap.c
の変更点
if(s->state != MSpanInUse) return nil;
の追加:MHeap_LookupMaybe
関数は、特定のページIDp
に対応するMSpan
s
を見つけます。- この追加された行は、見つかった
MSpan
s
の状態がMSpanInUse
(使用中)であるかどうかを確認します。 - もし
s->state
がMSpanInUse
ではない場合、それはそのMSpan
が現在有効なメモリブロックとして使用されていないことを意味します(例えば、既に解放されているか、他の目的で予約されているなど)。 - このような場合、この関数は
nil
を返します。これにより、GCや他のメモリ管理ルーチンが、不正な状態のMSpan
を誤って処理することを防ぎ、ランタイムの安定性を向上させます。これは、GCのバグを防ぐための重要な防御的プログラミングです。
これらの変更は、Goランタイムのメモリ管理における潜在的なバグを修正し、デバッグを容易にし、システムの堅牢性を高めるためのものです。特に、GCの正確性と安定性を確保するために不可欠な修正と言えます。
関連リンク
- Goの初期のメモリ管理に関する議論やドキュメントは、Goの公式リポジトリの初期のコミット履歴や、Goの設計ドキュメント(Go Design Documents)に遡って参照すると良いでしょう。
- Goのガベージコレクションの進化については、Goのブログ記事やGopherConの発表資料などが参考になります。
参考にした情報源リンク
- Goの公式GitHubリポジトリ
- Goの初期のコミット履歴 (このコミットより前の履歴を辿ることで、当時のGoランタイムの状況を把握できます)
- Goのメモリ管理とGCに関する一般的な情報源(Goのドキュメント、ブログ記事、技術解説など)