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

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

このコミットは、GoランタイムのLinuxにおけるメモリ管理部分、具体的にはsrc/pkg/runtime/mem_linux.cファイル内のaddrspace_free関数におけるmincoreシステムコールの使用方法のバグを修正するものです。mincoreがメモリ領域のページがアンマップされているかどうかを正確に判断できない問題に対処し、Goランタイムがメモリを適切に解放できることを保証します。

コミット

commit 0e1b6bb5470701090cd8dadacc6eb5074a86cf82
Author: Russ Cox <rsc@golang.org>
Date:   Thu Apr 3 19:04:47 2014 -0400

    runtime: use mincore correctly in addrspace_free
    
    Fixes #7476.
    
    LGTM=iant
    R=iant
    CC=golang-codereviews
    https://golang.org/cl/84000043

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

https://github.com/golang/go/commit/0e1b6bb5470701090cd8dadacc6eb5074a86cf82

元コミット内容

diff --git a/src/pkg/runtime/mem_linux.c b/src/pkg/runtime/mem_linux.c
index 3f997be96b..635594c365 100644
--- a/src/pkg/runtime/mem_linux.c
+++ b/src/pkg/runtime/mem_linux.c
@@ -20,15 +20,22 @@ addrspace_free(void *v, uintptr n)
 	int32 errval;
 	uintptr chunk;
 	uintptr off;
-	static byte vec[4096];
+	
+	// NOTE: vec must be just 1 byte long here.
+	// Mincore returns ENOMEM if any of the pages are unmapped,
+	// but we want to know that all of the pages are unmapped.
+	// To make these the same, we can only ask about one page
+	// at a time. See golang.org/issue/7476.
+	static byte vec[1];
 
 	for(off = 0; off < n; off += chunk) {
 		chunk = _PAGE_SIZE * sizeof vec;
 		if(chunk > (n - off))
 			chunk = n - off;
 		errval = runtime·mincore((int8*)v + off, chunk, vec);
-\t\t// errval is 0 if success, or -(error_code) if error.
-\t\tif (errval == 0 || errval != -ENOMEM)\
+\t\t// ENOMEM means unmapped, which is what we want.
+\t\t// Anything else we assume means the pages are mapped.
+\t\tif (errval != -ENOMEM)\
 			return 0;
 	}
 	return 1;

変更の背景

このコミットは、GoランタイムがLinux上でメモリを解放する際に使用するmincoreシステムコールの誤った使用方法を修正するために行われました。mincoreは、指定されたメモリ領域のページが物理メモリに存在するか(常駐しているか)どうかを報告するシステムコールです。Goランタイムのaddrspace_free関数は、あるメモリ領域が完全にアンマップされていることを確認するためにmincoreを利用していました。

しかし、mincoreシステムコールには、クエリ対象のメモリ範囲内に一つでもアンマップされたページが存在する場合にENOMEM(No memory)エラーを返すという曖昧な挙動がありました。これは、addrspace_freeが「このメモリ領域全体がアンマップされているか?」という問いに対して、mincoreが「一部でもアンマップされているページがある」という情報しか提供しないため、正確な判断ができないという問題を引き起こしていました。addrspace_freeが本当に知りたいのは、その領域の全てのページがアンマップされているかどうかでした。

この曖昧さのため、mincoreENOMEMを返した場合でも、それが「全てのページがアンマップされている」ことを意味するのか、「一部のページがアンマップされているが、他のページはマップされている」ことを意味するのかを区別できませんでした。この問題はGo issue #7476として報告され、このコミットによって解決されました。

前提知識の解説

mincoreシステムコール

mincoreはUnix系OSで利用可能なシステムコールで、プロセスが持つ仮想メモリ領域のページが物理メモリに常駐しているかどうか(つまり、ディスクにスワップアウトされていないか)を報告します。通常、メモリの常駐状態を監視したり、メモリ使用量を最適化したりするために使用されます。 mincore(void *addr, size_t length, unsigned char *vec)という形式で呼び出され、addrからlengthバイトのメモリ領域について、各ページの常駐状態をvecに1バイトずつ格納します。

ENOMEMエラー

ENOMEMは「メモリ不足」を意味するエラーコードです。mincoreがこのエラーを返す場合、通常はシステムがリソース(例えば、カーネル内部のデータ構造のためのメモリ)を割り当てられないことを示します。しかし、mincoreの特殊な挙動として、クエリ対象の範囲内にアンマップされたページが含まれる場合にもENOMEMを返すことがあります。これが今回の問題の核心でした。

Goランタイムのメモリ管理

Goランタイムは独自のメモリマネージャを持ち、OSから大きな仮想メモリ領域を確保し、その中でGoのヒープを管理します。メモリの確保や解放、ガベージコレクションなどはランタイムが担当し、必要に応じてmmapmunmapといったシステムコールを呼び出してOSと連携します。addrspace_freeのような関数は、このメモリ管理の一部として、特定の仮想アドレス空間をOSに返却する役割を担います。

addrspace_free関数

Goランタイムのaddrspace_free関数は、指定された仮想アドレス空間を解放する責任を持ちます。この関数は、解放しようとしているメモリ領域が実際にOSによってアンマップされていることを確認する必要があります。この確認プロセスでmincoreが使用されていました。

_PAGE_SIZE

_PAGE_SIZEは、システムにおけるメモリページのサイズ(通常は4KB)を表す定数です。メモリ管理はページ単位で行われるため、多くのメモリ関連の操作はこのページサイズを基準に行われます。

技術的詳細

元のコードでは、mincoreシステムコールを呼び出す際に、static byte vec[4096];という4096バイトの配列をvecとして使用していました。chunk変数は_PAGE_SIZE * sizeof vecとして計算され、これは4096 * 4096 = 16MBという非常に大きなチャンクサイズを意味していました。

mincoreは、指定されたメモリ領域(v + offからchunkバイト)内の各ページの常駐状態をvecに書き込みます。しかし、前述の通り、この領域内に一つでもアンマップされたページが存在すると、mincoreENOMEMを返してしまいます。

addrspace_freeの目的は、メモリ領域が完全にアンマップされていることを確認することです。元のコードのif (errval == 0 || errval != -ENOMEM)という条件は、「エラーがない(0)か、またはENOMEM以外のエラーである場合」にreturn 0(失敗)としていました。これは、ENOMEMが返された場合にのみ成功と見なすという意図でしたが、mincoreの曖昧な挙動により、部分的にアンマップされた領域でもENOMEMが返されるため、誤って「完全にアンマップされた」と判断してしまう可能性がありました。

この問題を解決するため、コミットでは以下の2つの主要な変更が行われました。

  1. vecのサイズ変更: static byte vec[4096];static byte vec[1];に変更しました。これにより、chunkの計算が_PAGE_SIZE * sizeof vec、つまり4096 * 1 = 4KBとなり、mincoreは一度に1ページ(またはそれ以下の残りのバイト数)の常駐状態しかチェックしなくなります。
  2. エラーチェックロジックの変更: if (errval == 0 || errval != -ENOMEM)if (errval != -ENOMEM)に変更しました。

この変更により、addrspace_freeはループ内でメモリ領域を_PAGE_SIZE(4KB)単位で繰り返しmincoreに問い合わせるようになります。

  • もしmincoreENOMEMを返した場合、それは問い合わせた1ページがアンマップされていることを明確に意味します。
  • もしmincore0(成功)を返した場合、それは問い合わせた1ページがマップされていることを意味します。

addrspace_freeは、全てのページがアンマップされていることを確認したいので、mincoreENOMEMを返さない限り(つまり、ページがマップされていると判断される限り)、return 0(失敗)として処理を中断します。これにより、メモリ領域全体が確実にアンマップされている場合にのみreturn 1(成功)を返すようになり、mincoreの曖昧な挙動による誤判断が解消されました。

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

src/pkg/runtime/mem_linux.cファイルにおいて、以下の変更が行われました。

  1. vec配列の宣言:

    -	static byte vec[4096];
    +	static byte vec[1];
    

    vec配列のサイズが4096バイトから1バイトに縮小されました。これにより、mincoreが一度にチェックするメモリチャンクのサイズが大幅に小さくなります。

  2. mincoreの戻り値の解釈:

    -		// errval is 0 if success, or -(error_code) if error.
    -		if (errval == 0 || errval != -ENOMEM)
    +		// ENOMEM means unmapped, which is what we want.
    +		// Anything else we assume means the pages are mapped.
    +		if (errval != -ENOMEM)
    

    mincoreの戻り値errvalのチェック条件が変更されました。新しい条件では、errvalENOMEMではない場合にのみreturn 0(失敗)となります。これは、ENOMEMが返された場合を「アンマップされている」という望ましい状態として扱い、それ以外の全ての場合(成功を示す0を含む)を「マップされている」と見なして処理を中断するというロジックです。

コアとなるコードの解説

static byte vec[1];

この変更は、mincoreシステムコールの挙動を制御するために非常に重要です。mincoreは、第3引数で渡されたvec配列のサイズと、第2引数で渡されたlength(ここではchunk)に基づいて、メモリ領域の常駐状態を報告します。 元のコードではvecが4096バイトあったため、chunk_PAGE_SIZE * sizeof vec、つまり4096 * 4096 = 16MBという大きな値になっていました。この大きなチャンクに対してmincoreを呼び出すと、その16MBの範囲内に一つでもアンマップされたページがあればENOMEMが返されてしまい、部分的なアンマップと完全なアンマップを区別できませんでした。

vecのサイズを1バイトにすることで、chunk_PAGE_SIZE * sizeof vec、つまり4096 * 1 = 4KBとなります。これにより、mincoreは一度に1ページ(またはループの最後の残りのバイト数)の常駐状態しかチェックしなくなります。この「1ページずつチェックする」という戦略が、mincoreENOMEMの曖昧さを回避し、各ページが個別にアンマップされているかどうかを正確に判断することを可能にします。

if (errval != -ENOMEM)

この条件は、mincoreの戻り値の解釈を修正します。

  • mincoreENOMEMを返した場合、それは現在チェックしている1ページがアンマップされていることを意味します。addrspace_freeは全てのページがアンマップされていることを確認したいので、この場合はループを続行し、次のページをチェックします。
  • mincoreENOMEM以外の値を返した場合(例えば、成功を示す0や、他のエラーコード)、それは現在チェックしている1ページがマップされていることを意味します。addrspace_freeは、メモリ領域全体がアンマップされていることを確認したいので、1ページでもマップされているページがあれば、その領域は完全にアンマップされていないと判断し、return 0(失敗)として処理を中断します。

このロジックにより、addrspace_freeは、ループ内の全てのページに対してmincoreENOMEMを返す場合にのみ、最終的にreturn 1(成功)を返すようになります。これにより、指定されたメモリ領域が本当に完全にアンマップされていることを正確に検証できるようになりました。

関連リンク

  • Go issue #7476: mincoreENOMEMに関する問題が議論されたGoのイシュートラッカーのエントリ。
    • https://golang.org/issue/7476 (直接のリンクは機能しない可能性がありますが、このイシュー番号が問題の特定に重要です。)

参考にした情報源リンク

  • Go issue #7476に関するウェブ検索結果: mincoreENOMEMの挙動と、1ページずつチェックする解決策について説明している情報。
  • mincoreシステムコールに関する一般的なドキュメント(例: man mincore)。
  • Goランタイムのメモリ管理に関する一般的な知識。
  • ENOMEMエラーコードに関する一般的な情報。# [インデックス 19029] ファイルの概要

このコミットは、GoランタイムのLinuxにおけるメモリ管理部分、具体的にはsrc/pkg/runtime/mem_linux.cファイル内のaddrspace_free関数におけるmincoreシステムコールの使用方法のバグを修正するものです。mincoreがメモリ領域のページがアンマップされているかどうかを正確に判断できない問題に対処し、Goランタイムがメモリを適切に解放できることを保証します。

コミット

commit 0e1b6bb5470701090cd8dadacc6eb5074a86cf82
Author: Russ Cox <rsc@golang.org>
Date:   Thu Apr 3 19:04:47 2014 -0400

    runtime: use mincore correctly in addrspace_free
    
    Fixes #7476.
    
    LGTM=iant
    R=iant
    CC=golang-codereviews
    https://golang.org/cl/84000043

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

https://github.com/golang/go/commit/0e1b6bb5470701090cd8dadacc6eb5074a86cf82

元コミット内容

diff --git a/src/pkg/runtime/mem_linux.c b/src/pkg/runtime/mem_linux.c
index 3f997be96b..635594c365 100644
--- a/src/pkg/runtime/mem_linux.c
+++ b/src/pkg/runtime/mem_linux.c
@@ -20,15 +20,22 @@ addrspace_free(void *v, uintptr n)
 	int32 errval;
 	uintptr chunk;
 	uintptr off;
-	static byte vec[4096];
+	
+	// NOTE: vec must be just 1 byte long here.
+	// Mincore returns ENOMEM if any of the pages are unmapped,
+	// but we want to know that all of the pages are unmapped.
+	// To make these the same, we can only ask about one page
+	// at a time. See golang.org/issue/7476.
+	static byte vec[1];
 
 	for(off = 0; off < n; off += chunk) {
 		chunk = _PAGE_SIZE * sizeof vec;
 		if(chunk > (n - off))
 			chunk = n - off;
 		errval = runtime·mincore((int8*)v + off, chunk, vec);
-\t\t// errval is 0 if success, or -(error_code) if error.
-\t\tif (errval == 0 || errval != -ENOMEM)\
+\t\t// ENOMEM means unmapped, which is what we want.
+\t\t// Anything else we assume means the pages are mapped.
+\t\tif (errval != -ENOMEM)\
 			return 0;
 	}
 	return 1;

変更の背景

このコミットは、GoランタイムがLinux上でメモリを解放する際に使用するmincoreシステムコールの誤った使用方法を修正するために行われました。mincoreは、指定されたメモリ領域のページが物理メモリに存在するか(常駐しているか)どうかを報告するシステムコールです。Goランタイムのaddrspace_free関数は、あるメモリ領域が完全にアンマップされていることを確認するためにmincoreを利用していました。

しかし、mincoreシステムコールには、クエリ対象のメモリ範囲内に一つでもアンマップされたページが存在する場合にENOMEM(No memory)エラーを返すという曖昧な挙動がありました。これは、addrspace_freeが「このメモリ領域全体がアンマップされているか?」という問いに対して、mincoreが「一部でもアンマップされているページがある」という情報しか提供しないため、正確な判断ができないという問題を引き起こしていました。addrspace_freeが本当に知りたいのは、その領域の全てのページがアンマップされているかどうかでした。

この曖昧さのため、mincoreENOMEMを返した場合でも、それが「全てのページがアンマップされている」ことを意味するのか、「一部のページがアンマップされているが、他のページはマップされている」ことを意味するのかを区別できませんでした。この問題はGo issue #7476として報告され、このコミットによって解決されました。

前提知識の解説

mincoreシステムコール

mincoreはUnix系OSで利用可能なシステムコールで、プロセスが持つ仮想メモリ領域のページが物理メモリに常駐しているかどうか(つまり、ディスクにスワップアウトされていないか)を報告します。通常、メモリの常駐状態を監視したり、メモリ使用量を最適化したりするために使用されます。 mincore(void *addr, size_t length, unsigned char *vec)という形式で呼び出され、addrからlengthバイトのメモリ領域について、各ページの常駐状態をvecに1バイトずつ格納します。

ENOMEMエラー

ENOMEMは「メモリ不足」を意味するエラーコードです。mincoreがこのエラーを返す場合、通常はシステムがリソース(例えば、カーネル内部のデータ構造のためのメモリ)を割り当てられないことを示します。しかし、mincoreの特殊な挙動として、クエリ対象の範囲内にアンマップされたページが含まれる場合にもENOMEMを返すことがあります。これが今回の問題の核心でした。

Goランタイムのメモリ管理

Goランタイムは独自のメモリマネージャを持ち、OSから大きな仮想メモリ領域を確保し、その中でGoのヒープを管理します。メモリの確保や解放、ガベージコレクションなどはランタイムが担当し、必要に応じてmmapmunmapといったシステムコールを呼び出してOSと連携します。addrspace_freeのような関数は、このメモリ管理の一部として、特定の仮想アドレス空間をOSに返却する役割を担います。

addrspace_free関数

Goランタイムのaddrspace_free関数は、指定された仮想アドレス空間を解放する責任を持ちます。この関数は、解放しようとしているメモリ領域が実際にOSによってアンマップされていることを確認する必要があります。この確認プロセスでmincoreが使用されていました。

_PAGE_SIZE

_PAGE_SIZEは、システムにおけるメモリページのサイズ(通常は4KB)を表す定数です。メモリ管理はページ単位で行われるため、多くのメモリ関連の操作はこのページサイズを基準に行われます。

技術的詳細

元のコードでは、mincoreシステムコールを呼び出す際に、static byte vec[4096];という4096バイトの配列をvecとして使用していました。chunk変数は_PAGE_SIZE * sizeof vecとして計算され、これは4096 * 4096 = 16MBという非常に大きなチャンクサイズを意味していました。

mincoreは、指定されたメモリ領域(v + offからchunkバイト)内の各ページの常駐状態をvecに書き込みます。しかし、前述の通り、この領域内に一つでもアンマップされたページが存在すると、mincoreENOMEMを返してしまいます。

addrspace_freeの目的は、メモリ領域が完全にアンマップされていることを確認することです。元のコードのif (errval == 0 || errval != -ENOMEM)という条件は、「エラーがない(0)か、またはENOMEM以外のエラーである場合」にreturn 0(失敗)としていました。これは、ENOMEMが返された場合にのみ成功と見なすという意図でしたが、mincoreの曖昧な挙動により、部分的にアンマップされた領域でもENOMEMが返されるため、誤って「完全にアンマップされた」と判断してしまう可能性がありました。

この問題を解決するため、コミットでは以下の2つの主要な変更が行われました。

  1. vecのサイズ変更: static byte vec[4096];static byte vec[1];に変更しました。これにより、chunkの計算が_PAGE_SIZE * sizeof vec、つまり4096 * 1 = 4KBとなり、mincoreは一度に1ページ(またはそれ以下の残りのバイト数)の常駐状態しかチェックしなくなります。
  2. エラーチェックロジックの変更: if (errval == 0 || errval != -ENOMEM)if (errval != -ENOMEM)に変更しました。

この変更により、addrspace_freeはループ内でメモリ領域を_PAGE_SIZE(4KB)単位で繰り返しmincoreに問い合わせるようになります。

  • もしmincoreENOMEMを返した場合、それは問い合わせた1ページがアンマップされていることを明確に意味します。
  • もしmincore0(成功)を返した場合、それは問い合わせた1ページがマップされていることを意味します。

addrspace_freeは、全てのページがアンマップされていることを確認したいので、mincoreENOMEMを返さない限り(つまり、ページがマップされていると判断される限り)、return 0(失敗)として処理を中断します。これにより、メモリ領域全体が確実にアンマップされている場合にのみreturn 1(成功)を返すようになり、mincoreの曖昧な挙動による誤判断が解消されました。

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

src/pkg/runtime/mem_linux.cファイルにおいて、以下の変更が行われました。

  1. vec配列の宣言:

    -	static byte vec[4096];
    +	static byte vec[1];
    

    vec配列のサイズが4096バイトから1バイトに縮小されました。これにより、mincoreが一度にチェックするメモリチャンクのサイズが大幅に小さくなります。

  2. mincoreの戻り値の解釈:

    -		// errval is 0 if success, or -(error_code) if error.
    -		if (errval == 0 || errval != -ENOMEM)
    +		// ENOMEM means unmapped, which is what we want.
    +		// Anything else we assume means the pages are mapped.
    +		if (errval != -ENOMEM)
    

    mincoreの戻り値errvalのチェック条件が変更されました。新しい条件では、errvalENOMEMではない場合にのみreturn 0(失敗)となります。これは、ENOMEMが返された場合を「アンマップされている」という望ましい状態として扱い、それ以外の全ての場合(成功を示す0を含む)を「マップされている」と見なして処理を中断するというロジックです。

コアとなるコードの解説

static byte vec[1];

この変更は、mincoreシステムコールの挙動を制御するために非常に重要です。mincoreは、第3引数で渡されたvec配列のサイズと、第2引数で渡されたlength(ここではchunk)に基づいて、メモリ領域の常駐状態を報告します。 元のコードではvecが4096バイトあったため、chunk_PAGE_SIZE * sizeof vec、つまり4096 * 4096 = 16MBという大きな値になっていました。この大きなチャンクに対してmincoreを呼び出すと、その16MBの範囲内に一つでもアンマップされたページがあればENOMEMが返されてしまい、部分的なアンマップと完全なアンマップを区別できませんでした。

vecのサイズを1バイトにすることで、chunk_PAGE_SIZE * sizeof vec、つまり4096 * 1 = 4KBとなります。これにより、mincoreは一度に1ページ(またはループの最後の残りのバイト数)の常駐状態しかチェックしなくなります。この「1ページずつチェックする」という戦略が、mincoreENOMEMの曖昧さを回避し、各ページが個別にアンマップされているかどうかを正確に判断することを可能にします。

if (errval != -ENOMEM)

この条件は、mincoreの戻り値の解釈を修正します。

  • mincoreENOMEMを返した場合、それは現在チェックしている1ページがアンマップされていることを意味します。addrspace_freeは全てのページがアンマップされていることを確認したいので、この場合はループを続行し、次のページをチェックします。
  • mincoreENOMEM以外の値を返した場合(例えば、成功を示す0や、他のエラーコード)、それは現在チェックしている1ページがマップされていることを意味します。addrspace_freeは、メモリ領域全体がアンマップされていることを確認したいので、1ページでもマップされているページがあれば、その領域は完全にアンマップされていないと判断し、return 0(失敗)として処理を中断します。

このロジックにより、addrspace_freeは、ループ内の全てのページに対してmincoreENOMEMを返す場合にのみ、最終的にreturn 1(成功)を返すようになります。これにより、指定されたメモリ領域が本当に完全にアンマップされていることを正確に検証できるようになりました。

関連リンク

  • Go issue #7476: mincoreENOMEMに関する問題が議論されたGoのイシュートラッカーのエントリ。
    • https://golang.org/issue/7476 (直接のリンクは機能しない可能性がありますが、このイシュー番号が問題の特定に重要です。)

参考にした情報源リンク

  • Go issue #7476に関するウェブ検索結果: mincoreENOMEMの挙動と、1ページずつチェックする解決策について説明している情報。
  • mincoreシステムコールに関する一般的なドキュメント(例: man mincore)。
  • Goランタイムのメモリ管理に関する一般的な知識。
  • ENOMEMエラーコードに関する一般的な情報。