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

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

このコミットは、Goランタイムのガベージコレクション(GC)におけるバグ修正を目的としています。具体的には、メモリ割り当てとGC参照のルックアップに関連する問題に対処しています。src/runtime/malloc.csrc/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つの変更を含んでいます。

  1. 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エラーが発生した際に、問題の原因を特定しやすくなります。このデバッグ出力の強化は、バグの再現と解析を助けるためのものです。
  2. 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の対象ではないメモリを誤って参照することを防ぎます。

これらの変更は、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))が出力されます。
    • これらの追加情報は、bad gcrefエラーが発生した際に、どのMSpanがどのような状態であり、GC参照情報がなぜ不正な範囲を指しているのかを詳細に分析するための手がかりとなります。これは、デバッグを大幅に容易にするための変更です。

src/runtime/mheap.cの変更点

  • if(s->state != MSpanInUse) return nil; の追加:
    • MHeap_LookupMaybe関数は、特定のページID p に対応するMSpan s を見つけます。
    • この追加された行は、見つかったMSpan s の状態がMSpanInUse(使用中)であるかどうかを確認します。
    • もしs->stateMSpanInUseではない場合、それはそのMSpanが現在有効なメモリブロックとして使用されていないことを意味します(例えば、既に解放されているか、他の目的で予約されているなど)。
    • このような場合、この関数はnilを返します。これにより、GCや他のメモリ管理ルーチンが、不正な状態のMSpanを誤って処理することを防ぎ、ランタイムの安定性を向上させます。これは、GCのバグを防ぐための重要な防御的プログラミングです。

これらの変更は、Goランタイムのメモリ管理における潜在的なバグを修正し、デバッグを容易にし、システムの堅牢性を高めるためのものです。特に、GCの正確性と安定性を確保するために不可欠な修正と言えます。

関連リンク

  • Goの初期のメモリ管理に関する議論やドキュメントは、Goの公式リポジトリの初期のコミット履歴や、Goの設計ドキュメント(Go Design Documents)に遡って参照すると良いでしょう。
  • Goのガベージコレクションの進化については、Goのブログ記事やGopherConの発表資料などが参考になります。

参考にした情報源リンク