[インデックス 15893] ファイルの概要
このコミットは、GoランタイムにおけるDarwin (macOS) および FreeBSD システムコール mmap
の戻り値のチェックに関する修正です。具体的には、mmap
がエラーを返す際の挙動の違いと、それに対応するGoランタイムのメモリ管理関数 SysReserve
および SysMap
の修正に焦点を当てています。
コミット
commit 3b9702c9c8a7e69e5af9b6dafd2932228d6b3210
Author: Joel Sing <jsing@google.com>
Date: Sat Mar 23 02:17:01 2013 +1100
runtime: correct return value checks for mmap on darwin/freebsd
On Darwin and FreeBSD, the mmap syscall return value is returned
unmodified. This means that the return value will either be a
valid address or a positive error number.
Also check return value from mmap in SysReserve - the callers of
SysReserve expect nil to be returned if the allocation failed.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7871043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3b9702c9c8a7e69e5af9b6dafd2932228d6b3210
元コミット内容
GoランタイムがDarwinおよびFreeBSD上で mmap
システムコールを使用する際の戻り値のチェックを修正します。これらのOSでは、mmap
はエラー時に負の値ではなく、正のエラー番号を直接返します。また、SysReserve
関数内で mmap
の戻り値を適切にチェックし、メモリ確保が失敗した場合には nil
を返すようにします。これは SysReserve
の呼び出し元が期待する動作です。
変更の背景
Goランタイムは、プログラムのメモリを管理するためにオペレーティングシステムが提供するメモリマッピング機能(主に mmap
システムコール)を利用しています。mmap
は、ファイルや匿名メモリ領域をプロセスのアドレス空間にマッピングするために使用されます。
一般的なUnix系システム(Linuxなど)では、mmap
システムコールが失敗した場合、エラーを示すために MAP_FAILED
という特殊な値(通常は (void*)-1
)を返します。そして、実際のエラーコードは errno
グローバル変数に設定されます。
しかし、Darwin (macOS) や FreeBSD といったBSD系のシステムでは、mmap
の挙動が異なります。これらのシステムでは、mmap
が失敗した場合、MAP_FAILED
のような特殊な値を返すのではなく、直接エラーコード(正の整数)をポインタ型にキャストして返します。例えば、メモリ不足のエラー ENOMEM
(通常は12) が発生した場合、mmap
は (void*)12
のような値を返します。
Goランタイムの既存の実装では、このBSD系の mmap
の挙動が考慮されておらず、mmap
の戻り値が (void*)-ENOMEM
と比較されていました。このため、mmap
が実際にエラーを返しても、Goランタイムがそれを正しく認識できず、予期せぬ動作やクラッシュを引き起こす可能性がありました。
また、SysReserve
関数は、Goランタイムが将来使用する可能性のあるアドレス空間を予約するために mmap
を PROT_NONE
(アクセス不可) で呼び出します。この関数は、予約が成功した場合は予約されたアドレスを、失敗した場合は nil
を返すことが期待されています。しかし、mmap
の戻り値が適切にチェックされていなかったため、SysReserve
が nil
以外の不正な値を返す可能性がありました。
このコミットは、これらの問題を修正し、GoランタイムがDarwinおよびFreeBSD上でより堅牢にメモリを管理できるようにすることを目的としています。
前提知識の解説
mmap
システムコール
mmap
は、Unix系オペレーティングシステムで利用可能なシステムコールで、プロセスのアドレス空間にメモリ領域をマッピングするために使用されます。主な用途は以下の通りです。
- ファイルマッピング: ファイルの内容を直接メモリにマッピングし、ファイルI/Oを効率化します。
- 匿名メモリマッピング: ファイルに関連付けられていないメモリ領域を確保します。これは、ヒープメモリやスタック領域の確保、共有メモリの実装などに利用されます。Goランタイムのメモリ管理では、主に匿名メモリマッピングが使用されます。
mmap
の基本的なシグネチャは以下のようになります(C言語の例):
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
addr
: マッピングを配置する推奨アドレス。NULL
の場合、カーネルが適切なアドレスを選択します。len
: マッピングするバイト数。prot
: メモリ領域の保護(読み取り、書き込み、実行など)。例:PROT_READ
,PROT_WRITE
,PROT_NONE
。flags
: マッピングの動作を制御するフラグ。例:MAP_ANON
(匿名マッピング),MAP_PRIVATE
(プライベートコピー),MAP_FIXED
(指定されたアドレスに厳密にマッピング)。fd
: マッピングするファイルのファイルディスクリプタ(匿名マッピングの場合は-1
)。offset
: ファイル内のオフセット。
戻り値の挙動:
- 成功時: マッピングされたメモリ領域の開始アドレスを返します。
- 失敗時:
- Linuxなど:
MAP_FAILED
(通常(void*)-1
) を返し、errno
にエラーコードを設定します。 - Darwin/FreeBSD: エラーコード(正の整数)を直接ポインタ型にキャストして返します。例えば、
ENOMEM
(12) の場合、(void*)12
を返します。
- Linuxなど:
PROT_NONE
mmap
の prot
引数に指定できるフラグの一つで、マッピングされたメモリ領域へのアクセスを一切許可しないことを意味します。これは、アドレス空間を予約するだけで、実際にメモリを使用する準備ができていない場合によく使用されます。後で mprotect
などを使って保護レベルを変更できます。
MAP_ANON
と MAP_PRIVATE
MAP_ANON
: ファイルに関連付けられていない匿名メモリ領域を作成します。これは、ヒープやスタックなどのプログラムの動的メモリ割り当てに使用されます。MAP_PRIVATE
: マッピングがプライベートであることを示します。つまり、マッピングされたメモリへの変更は、そのプロセスからのみ見え、基になるファイルや他のプロセスには影響しません。
Goランタイムのメモリ管理関数
Goランタイムは、OSのメモリ管理機能を抽象化し、Goプログラムが効率的にメモリを使用できるように独自のメモリ管理層を持っています。このコミットで関連する関数は以下の通りです。
runtime·SysReserve(void *v, uintptr n)
:- 指定されたアドレス
v
からn
バイトのアドレス空間を予約します。 - この関数は、実際にメモリをコミット(物理メモリを割り当てる)するのではなく、単にアドレス空間を確保するだけです。
- 予約が成功した場合は予約されたアドレスを、失敗した場合は
nil
を返すことが期待されます。 - 内部的には
mmap
をPROT_NONE
フラグで呼び出します。
- 指定されたアドレス
runtime·SysMap(void *v, uintptr n)
:SysReserve
で予約されたアドレス空間v
からn
バイトを実際に使用可能にします(コミットします)。- この関数は、メモリ領域を読み書き可能にするために
mmap
をPROT_READ|PROT_WRITE
フラグで呼び出します。 - 通常、
MAP_FIXED
フラグを使用して、指定されたアドレスに厳密にマッピングしようとします。
nil
ポインタ
GoランタイムのCコードでは、nil
はC言語の NULL
ポインタに相当します。メモリ割り当て関数が失敗した場合に nil
を返すのは一般的な慣習です。
技術的詳細
このコミットの核心は、mmap
システムコールの戻り値の解釈に関するOS間の差異を正しく処理することです。
Darwin/FreeBSDにおける mmap
の戻り値
DarwinとFreeBSDでは、mmap
がエラーを返した場合、その戻り値は負の MAP_FAILED
(通常 (void*)-1
) ではなく、正のエラー番号を直接ポインタ型にキャストした値となります。例えば、メモリ不足を示す ENOMEM
(エラーコード12) が発生した場合、mmap
は (void*)12
を返します。
従来のGoランタイムのコードでは、mmap
の戻り値を (void*)-ENOMEM
と比較していました。これはLinuxなどのシステムでは正しいかもしれませんが、Darwin/FreeBSDでは (void*)12
と (void*)-12
は異なる値であるため、エラーが正しく検出されませんでした。
SysReserve
の修正
SysReserve
関数は、アドレス空間の予約に失敗した場合に nil
を返す必要があります。従来のコードでは、mmap
の戻り値をそのまま返していました。
// Before
void*
runtime·SysReserve(void *v, uintptr n)
{
return runtime·mmap(v, n, PROT_NONE, MAP_ANON|MAP_PRIVATE, -1, 0);
}
この修正では、mmap
の戻り値 p
を受け取り、p
が (void*)4096
(ページサイズ) 未満である場合に nil
を返すように変更されています。これは、有効なメモリ開始アドレスは通常、ページ境界にアラインされ、非常に小さい正の整数(エラーコード)とは区別できるという仮定に基づいています。4096
は一般的なメモリページサイズであり、これより小さい値は有効なアドレスではなく、エラーコードである可能性が高いと判断されます。
// After
void*
runtime·SysReserve(void *v, uintptr n)
{
void *p;
p = runtime·mmap(v, n, PROT_NONE, MAP_ANON|MAP_PRIVATE, -1, 0);
if(p < (void*)4096) // ページサイズ未満であればエラーと判断
return nil;
return p;
}
SysMap
の修正
SysMap
関数では、mmap
がメモリ不足エラー ENOMEM
を返した場合のチェックが修正されています。
// Before
if(p == (void*)-ENOMEM)
// After
if(p == (void*)ENOMEM)
これにより、Darwin/FreeBSDで mmap
が (void*)ENOMEM
を返した場合に、Goランタイムが正しく「out of memory」エラーをスローできるようになります。
ENOMEM
の定義
FreeBSDの src/pkg/runtime/mem_freebsd.c
では、ENOMEM
がローカルで定義されていました。
// Before
enum
{
ENOMEM = 12,
};
この定義は、SysReserve
の修正に伴い、SysAlloc
の直後に移動され、より適切なスコープに配置されています。
コアとなるコードの変更箇所
src/pkg/runtime/mem_darwin.c
--- a/src/pkg/runtime/mem_darwin.c
+++ b/src/pkg/runtime/mem_darwin.c
@@ -37,7 +37,12 @@ runtime·SysFree(void *v, uintptr n)
void*
runtime·SysReserve(void *v, uintptr n)
{
- return runtime·mmap(v, n, PROT_NONE, MAP_ANON|MAP_PRIVATE, -1, 0);
+ void *p;
+
+ p = runtime·mmap(v, n, PROT_NONE, MAP_ANON|MAP_PRIVATE, -1, 0);
+ if(p < (void*)4096)
+ return nil;
+ return p;
}
enum
@@ -52,7 +57,7 @@ runtime·SysMap(void *v, uintptr n)
mstats.sys += n;
p = runtime·mmap(v, n, PROT_READ|PROT_WRITE, MAP_ANON|MAP_FIXED|MAP_PRIVATE, -1, 0);
- if(p == (void*)-ENOMEM)
+ if(p == (void*)ENOMEM)
runtime·throw("runtime: out of memory");
if(p != v)
runtime·throw("runtime: cannot map pages in arena address space");
src/pkg/runtime/mem_freebsd.c
--- a/src/pkg/runtime/mem_freebsd.c
+++ b/src/pkg/runtime/mem_freebsd.c
@@ -8,6 +8,11 @@
#include "os_GOOS.h"\n #include "malloc.h"\n \n+enum\n+{\n+\tENOMEM = 12,\n+};\n+\n void*\n runtime·SysAlloc(uintptr n)\n {\
@@ -36,20 +41,20 @@ runtime·SysFree(void *v, uintptr n)
void*
runtime·SysReserve(void *v, uintptr n)
{
+\tvoid *p;\n+\n \t// On 64-bit, people with ulimit -v set complain if we reserve too\n \t// much address space. Instead, assume that the reservation is okay\n \t// and check the assumption in SysMap.\n \tif(sizeof(void*) == 8)\n \t\treturn v;\n \t\n-\treturn runtime·mmap(v, n, PROT_NONE, MAP_ANON|MAP_PRIVATE, -1, 0);\n+\tp = runtime·mmap(v, n, PROT_NONE, MAP_ANON|MAP_PRIVATE, -1, 0);\n+\tif(p < (void*)4096)\n+\t\treturn nil;\n+\treturn p;\n }\n \n-enum\n-{\n-\tENOMEM = 12,\n-};\n-\n void
runtime·SysMap(void *v, uintptr n)
{
@@ -60,7 +65,7 @@ runtime·SysMap(void *v, uintptr n)
// On 64-bit, we don\'t actually have v reserved, so tread carefully.\n \tif(sizeof(void*) == 8) {\n \t\tp = runtime·mmap(v, n, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0);\n-\t\tif(p == (void*)-ENOMEM)\n+\t\tif(p == (void*)ENOMEM)\n \t\t\truntime·throw(\"runtime: out of memory\");\n \t\tif(p != v) {\n \t\t\truntime·printf(\"runtime: address space conflict: map(%p) = %p\\n\", v, p);\n@@ -70,7 +75,7 @@ runtime·SysMap(void *v, uintptr n)
\t}\n \n \tp = runtime·mmap(v, n, PROT_READ|PROT_WRITE, MAP_ANON|MAP_FIXED|MAP_PRIVATE, -1, 0);\n-\tif(p == (void*)-ENOMEM)\n+\tif(p == (void*)ENOMEM)\n \t\truntime·throw(\"runtime: out of memory\");\n \tif(p != v)\n \t\truntime·throw(\"runtime: cannot map pages in arena address space\");\
コアとなるコードの解説
runtime·SysReserve
の変更
- 変更前:
runtime·mmap
の戻り値を直接返していました。 - 変更後:
runtime·mmap
の戻り値を一時変数p
に格納します。if(p < (void*)4096)
という条件を追加します。これは、mmap
がエラーコード(正の小さい整数)を返した場合にtrue
となります。有効なメモリ開始アドレスは通常、ページサイズ(4096バイト)以上の値になるため、このチェックでエラーと正常なアドレスを区別します。- 条件が
true
の場合、nil
を返します。これにより、SysReserve
の呼び出し元はメモリ予約の失敗を正しく認識できます。 - 条件が
false
の場合、p
(有効なアドレス) を返します。
runtime·SysMap
の変更
- 変更前:
mmap
の戻り値p
が(void*)-ENOMEM
と等しいかどうかをチェックしていました。 - 変更後:
mmap
の戻り値p
が(void*)ENOMEM
と等しいかどうかをチェックするように変更されました。これにより、Darwin/FreeBSDでmmap
が正のエラーコードENOMEM
を返した場合に、Goランタイムが適切に「out of memory」エラーをスローできるようになります。
ENOMEM
の定義の移動 (mem_freebsd.c
のみ)
mem_freebsd.c
では、ENOMEM
のenum
定義がruntime·SysReserve
関数の直前からruntime·SysAlloc
関数の直後に移動されました。これは機能的な変更ではなく、コードの構造と可読性を改善するためのものです。
これらの変更により、GoランタイムはDarwinおよびFreeBSD環境下で mmap
システムコールのエラーを正確に検出し、メモリ管理の堅牢性が向上しました。
関連リンク
mmap(2)
man page (Linux): https://man7.org/linux/man-pages/man2/mmap.2.htmlmmap(2)
man page (FreeBSD): https://www.freebsd.org/cgi/man.cgi?query=mmap&sektion=2mmap(2)
man page (macOS): https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/mmap.2.html- Go issue tracker (関連する可能性のあるissue): https://github.com/golang/go/issues (このコミットのCLリンクは
https://golang.org/cl/7871043
ですが、これは古いGerritのURLであり、現在のGitHubのissueやPRとは直接リンクしていません。しかし、同様の問題が過去に議論されている可能性があります。)
参考にした情報源リンク
- コミットメッセージと差分 (
./commit_data/15893.txt
の内容) mmap
システムコールの一般的なドキュメント (Linux, FreeBSD, macOS の man ページ)- Go言語のランタイムに関する一般的な知識とメモリ管理の概念