[インデックス 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
が本当に知りたいのは、その領域の全てのページがアンマップされているかどうかでした。
この曖昧さのため、mincore
がENOMEM
を返した場合でも、それが「全てのページがアンマップされている」ことを意味するのか、「一部のページがアンマップされているが、他のページはマップされている」ことを意味するのかを区別できませんでした。この問題は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のヒープを管理します。メモリの確保や解放、ガベージコレクションなどはランタイムが担当し、必要に応じてmmap
やmunmap
といったシステムコールを呼び出して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
に書き込みます。しかし、前述の通り、この領域内に一つでもアンマップされたページが存在すると、mincore
はENOMEM
を返してしまいます。
addrspace_free
の目的は、メモリ領域が完全にアンマップされていることを確認することです。元のコードのif (errval == 0 || errval != -ENOMEM)
という条件は、「エラーがない(0)か、またはENOMEM
以外のエラーである場合」にreturn 0
(失敗)としていました。これは、ENOMEM
が返された場合にのみ成功と見なすという意図でしたが、mincore
の曖昧な挙動により、部分的にアンマップされた領域でもENOMEM
が返されるため、誤って「完全にアンマップされた」と判断してしまう可能性がありました。
この問題を解決するため、コミットでは以下の2つの主要な変更が行われました。
vec
のサイズ変更:static byte vec[4096];
をstatic byte vec[1];
に変更しました。これにより、chunk
の計算が_PAGE_SIZE * sizeof vec
、つまり4096 * 1 = 4KB
となり、mincore
は一度に1ページ(またはそれ以下の残りのバイト数)の常駐状態しかチェックしなくなります。- エラーチェックロジックの変更:
if (errval == 0 || errval != -ENOMEM)
をif (errval != -ENOMEM)
に変更しました。
この変更により、addrspace_free
はループ内でメモリ領域を_PAGE_SIZE
(4KB)単位で繰り返しmincore
に問い合わせるようになります。
- もし
mincore
がENOMEM
を返した場合、それは問い合わせた1ページがアンマップされていることを明確に意味します。 - もし
mincore
が0
(成功)を返した場合、それは問い合わせた1ページがマップされていることを意味します。
addrspace_free
は、全てのページがアンマップされていることを確認したいので、mincore
がENOMEM
を返さない限り(つまり、ページがマップされていると判断される限り)、return 0
(失敗)として処理を中断します。これにより、メモリ領域全体が確実にアンマップされている場合にのみreturn 1
(成功)を返すようになり、mincore
の曖昧な挙動による誤判断が解消されました。
コアとなるコードの変更箇所
src/pkg/runtime/mem_linux.c
ファイルにおいて、以下の変更が行われました。
-
vec
配列の宣言:- static byte vec[4096]; + static byte vec[1];
vec
配列のサイズが4096バイトから1バイトに縮小されました。これにより、mincore
が一度にチェックするメモリチャンクのサイズが大幅に小さくなります。 -
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
のチェック条件が変更されました。新しい条件では、errval
がENOMEM
ではない場合にのみ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ページずつチェックする」という戦略が、mincore
のENOMEM
の曖昧さを回避し、各ページが個別にアンマップされているかどうかを正確に判断することを可能にします。
if (errval != -ENOMEM)
この条件は、mincore
の戻り値の解釈を修正します。
mincore
がENOMEM
を返した場合、それは現在チェックしている1ページがアンマップされていることを意味します。addrspace_free
は全てのページがアンマップされていることを確認したいので、この場合はループを続行し、次のページをチェックします。mincore
がENOMEM
以外の値を返した場合(例えば、成功を示す0
や、他のエラーコード)、それは現在チェックしている1ページがマップされていることを意味します。addrspace_free
は、メモリ領域全体がアンマップされていることを確認したいので、1ページでもマップされているページがあれば、その領域は完全にアンマップされていないと判断し、return 0
(失敗)として処理を中断します。
このロジックにより、addrspace_free
は、ループ内の全てのページに対してmincore
がENOMEM
を返す場合にのみ、最終的にreturn 1
(成功)を返すようになります。これにより、指定されたメモリ領域が本当に完全にアンマップされていることを正確に検証できるようになりました。
関連リンク
- Go issue #7476:
mincore
のENOMEM
に関する問題が議論されたGoのイシュートラッカーのエントリ。- https://golang.org/issue/7476 (直接のリンクは機能しない可能性がありますが、このイシュー番号が問題の特定に重要です。)
参考にした情報源リンク
- Go issue #7476に関するウェブ検索結果:
mincore
のENOMEM
の挙動と、1ページずつチェックする解決策について説明している情報。- https://redox-os.org/docs/book/ch04-02-memory-management.html (このリンクはGoのイシューを直接参照しているわけではありませんが、
mincore
の挙動に関する一般的な情報を提供しています。)
- https://redox-os.org/docs/book/ch04-02-memory-management.html (このリンクはGoのイシューを直接参照しているわけではありませんが、
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
が本当に知りたいのは、その領域の全てのページがアンマップされているかどうかでした。
この曖昧さのため、mincore
がENOMEM
を返した場合でも、それが「全てのページがアンマップされている」ことを意味するのか、「一部のページがアンマップされているが、他のページはマップされている」ことを意味するのかを区別できませんでした。この問題は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のヒープを管理します。メモリの確保や解放、ガベージコレクションなどはランタイムが担当し、必要に応じてmmap
やmunmap
といったシステムコールを呼び出して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
に書き込みます。しかし、前述の通り、この領域内に一つでもアンマップされたページが存在すると、mincore
はENOMEM
を返してしまいます。
addrspace_free
の目的は、メモリ領域が完全にアンマップされていることを確認することです。元のコードのif (errval == 0 || errval != -ENOMEM)
という条件は、「エラーがない(0)か、またはENOMEM
以外のエラーである場合」にreturn 0
(失敗)としていました。これは、ENOMEM
が返された場合にのみ成功と見なすという意図でしたが、mincore
の曖昧な挙動により、部分的にアンマップされた領域でもENOMEM
が返されるため、誤って「完全にアンマップされた」と判断してしまう可能性がありました。
この問題を解決するため、コミットでは以下の2つの主要な変更が行われました。
vec
のサイズ変更:static byte vec[4096];
をstatic byte vec[1];
に変更しました。これにより、chunk
の計算が_PAGE_SIZE * sizeof vec
、つまり4096 * 1 = 4KB
となり、mincore
は一度に1ページ(またはそれ以下の残りのバイト数)の常駐状態しかチェックしなくなります。- エラーチェックロジックの変更:
if (errval == 0 || errval != -ENOMEM)
をif (errval != -ENOMEM)
に変更しました。
この変更により、addrspace_free
はループ内でメモリ領域を_PAGE_SIZE
(4KB)単位で繰り返しmincore
に問い合わせるようになります。
- もし
mincore
がENOMEM
を返した場合、それは問い合わせた1ページがアンマップされていることを明確に意味します。 - もし
mincore
が0
(成功)を返した場合、それは問い合わせた1ページがマップされていることを意味します。
addrspace_free
は、全てのページがアンマップされていることを確認したいので、mincore
がENOMEM
を返さない限り(つまり、ページがマップされていると判断される限り)、return 0
(失敗)として処理を中断します。これにより、メモリ領域全体が確実にアンマップされている場合にのみreturn 1
(成功)を返すようになり、mincore
の曖昧な挙動による誤判断が解消されました。
コアとなるコードの変更箇所
src/pkg/runtime/mem_linux.c
ファイルにおいて、以下の変更が行われました。
-
vec
配列の宣言:- static byte vec[4096]; + static byte vec[1];
vec
配列のサイズが4096バイトから1バイトに縮小されました。これにより、mincore
が一度にチェックするメモリチャンクのサイズが大幅に小さくなります。 -
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
のチェック条件が変更されました。新しい条件では、errval
がENOMEM
ではない場合にのみ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ページずつチェックする」という戦略が、mincore
のENOMEM
の曖昧さを回避し、各ページが個別にアンマップされているかどうかを正確に判断することを可能にします。
if (errval != -ENOMEM)
この条件は、mincore
の戻り値の解釈を修正します。
mincore
がENOMEM
を返した場合、それは現在チェックしている1ページがアンマップされていることを意味します。addrspace_free
は全てのページがアンマップされていることを確認したいので、この場合はループを続行し、次のページをチェックします。mincore
がENOMEM
以外の値を返した場合(例えば、成功を示す0
や、他のエラーコード)、それは現在チェックしている1ページがマップされていることを意味します。addrspace_free
は、メモリ領域全体がアンマップされていることを確認したいので、1ページでもマップされているページがあれば、その領域は完全にアンマップされていないと判断し、return 0
(失敗)として処理を中断します。
このロジックにより、addrspace_free
は、ループ内の全てのページに対してmincore
がENOMEM
を返す場合にのみ、最終的にreturn 1
(成功)を返すようになります。これにより、指定されたメモリ領域が本当に完全にアンマップされていることを正確に検証できるようになりました。
関連リンク
- Go issue #7476:
mincore
のENOMEM
に関する問題が議論されたGoのイシュートラッカーのエントリ。- https://golang.org/issue/7476 (直接のリンクは機能しない可能性がありますが、このイシュー番号が問題の特定に重要です。)
参考にした情報源リンク
- Go issue #7476に関するウェブ検索結果:
mincore
のENOMEM
の挙動と、1ページずつチェックする解決策について説明している情報。- https://redox-os.org/docs/book/ch04-02-memory-management.html (このリンクはGoのイシューを直接参照しているわけではありませんが、
mincore
の挙動に関する一般的な情報を提供しています。)
- https://redox-os.org/docs/book/ch04-02-memory-management.html (このリンクはGoのイシューを直接参照しているわけではありませんが、
mincore
システムコールに関する一般的なドキュメント(例:man mincore
)。- Goランタイムのメモリ管理に関する一般的な知識。
ENOMEM
エラーコードに関する一般的な情報。