[インデックス 11714] ファイルの概要
このコミットは、Go言語のランタイムにおけるメモリ管理の改善に関するものです。特に、64ビット環境、中でもUser-Mode Linux (UML)のような特定の環境で発生する、高位仮想メモリ空間の予約に関する問題を解決することを目的としています。Goランタイムが特定の高位アドレスでのメモリ予約に失敗した場合に、32ビットのメモリ割り当てメカニズムにフォールバックするよう変更が加えられています。
コミット
commit d37a8b73c504c232084666b292f20debb397bd27
Author: Paul Borman <borman@google.com>
Date: Wed Feb 8 14:39:16 2012 -0500
runtime: drop to 32 bit malloc if 64 bit will not work
On 64 bit UML it is not possible to reserve memory at 0xF8<<32.
Detect when linux cannot use these high virtual memory addresses
and drop back to the 32 bit memory allocator.
R=rsc, cw
CC=golang-dev
https://golang.org/cl/5634050
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d37a8b73c504c232084666b292f20debb397bd27
元コミット内容
このコミットは、Goランタイムが64ビットシステム上でメモリを予約する際の挙動を修正します。特に、User-Mode Linux (UML)のような環境で、0xF8<<32
のような非常に高い仮想メモリアドレスにメモリを予約できない場合に、Goランタイムがクラッシュするのではなく、32ビットのメモリ割り当てメカニズムに切り替えるように変更されています。
変更されたファイルは以下の2つです。
src/pkg/runtime/malloc.goc
: メモリ初期化ロジックとヒープ割り当てロジックが変更され、高位アドレスの予約失敗時に32ビットフォールバックを考慮するようになりました。src/pkg/runtime/mem_linux.c
: Linux固有のメモリ予約システムコール(SysReserve
)に、64ビットUML環境での高位アドレス予約失敗を検出するロジックが追加されました。
変更の背景
Go言語のランタイムは、効率的なメモリ管理のために、起動時に広大な仮想アドレス空間を予約しようとします。64ビットシステムでは、この予約は非常に高い仮想メモリ領域(例: 0xF8<<32
、約64GBの仮想アドレス空間の開始点)で行われることが一般的です。これは、ヒープの成長やガベージコレクションの効率化のために、連続した大きなアドレス空間を確保するためです。
しかし、特定の64ビットLinux環境、特にUser-Mode Linux (UML)では、この高位仮想メモリアドレスでの予約がOSによって拒否されるという問題がありました。UMLは、通常のLinuxカーネル上でユーザープロセスとして動作するLinuxカーネルであり、そのメモリ管理の挙動がネイティブなLinuxとは異なる場合があります。この予約失敗は、Goプログラムの起動失敗やクラッシュに直結していました。
このコミットの目的は、このような環境でもGoプログラムが動作できるように、高位アドレスの予約が不可能であることを検出し、より制限された32ビットのメモリ割り当て戦略に自動的にフォールバックすることです。これにより、Goプログラムの互換性と堅牢性が向上します。
前提知識の解説
仮想メモリ (Virtual Memory)
オペレーティングシステムが提供するメモリ管理の抽象化層です。各プロセスは、物理メモリとは独立した、連続した仮想アドレス空間を持っているかのように見えます。OSは、この仮想アドレスを物理メモリのアドレスにマッピングします。これにより、プログラムは物理メモリの制約から解放され、より大きなアドレス空間を利用したり、メモリ保護を実現したりできます。
mmap
と munmap
システムコール
Unix系OSにおけるメモリマッピングのためのシステムコールです。
mmap()
: ファイルやデバイス、または匿名メモリ領域をプロセスの仮想アドレス空間にマッピングするために使用されます。メモリを予約(reserve)する際にも、PROT_NONE
(アクセス不可)フラグと共に使用されることがあります。munmap()
:mmap()
でマッピングされた領域をアンマッピング(解放)します。
Goランタイムのメモリ管理
Goランタイムは独自のメモリ管理システム(Goスケジューラ、ガベージコレクタ、ヒープアロケータなど)を持っています。Goのヒープは、OSから大きな仮想アドレス空間を予約し、その中から小さなチャンクを割り当てて使用します。この予約は、将来のメモリ要求に備えて行われ、メモリの断片化を防ぎ、ガベージコレクションの効率を高めるのに役立ちます。
User-Mode Linux (UML)
UMLは、Linuxカーネルをユーザー空間のアプリケーションとして実行できるようにする技術です。これにより、通常のLinuxシステム上で別のLinuxシステムを「ゲスト」として実行できます。UMLは、カーネル開発、システムエミュレーション、サンドボックス環境の構築などに利用されます。UML環境では、ホストOSの制約や設定により、ゲストOSが利用できる仮想メモリ空間に制限がある場合があり、特に高位アドレスの予約が困難なことがあります。
32ビットと64ビットのアドレス空間
- 32ビットシステム: 仮想アドレス空間は2^32バイト(約4GB)に制限されます。プログラムが直接アクセスできるメモリは最大4GBです。
- 64ビットシステム: 仮想アドレス空間は2^64バイト(非常に広大)に拡張されます。これにより、プログラムは理論上、はるかに大きなメモリを直接アドレス指定できます。Goランタイムが
0xF8<<32
のような高位アドレスを予約しようとするのは、この64ビットアドレス空間の広さを活用するためです。0xffffffffU
は32ビット符号なし整数の最大値であり、これを超えるアドレスは64ビットアドレス空間に属します。
技術的詳細
このコミットの核心は、Goランタイムが64ビットシステムでメモリを初期化する際に、高位仮想アドレス空間の予約が成功するかどうかをより堅牢にチェックし、失敗した場合に適切なフォールバックパスを提供することです。
-
runtime·mallocinit
の変更:- Goランタイムのメモリ初期化関数である
runtime·mallocinit
は、64ビットシステムでは通常、16LL<<30
(16GB) のアリーナサイズと、そのビットマップのために0x00f8ULL<<32
という非常に高い仮想アドレスからメモリを予約しようとします。 - 変更前は、この
runtime·SysReserve
呼び出しがnil
を返した場合(予約失敗)、即座にruntime·throw("runtime: cannot reserve arena virtual address space")
でパニックを起こしていました。 - 変更後は、
if (p == nil)
ブロックが追加され、予約が失敗した場合でも即座にパニックを起こさず、その後の32ビットメモリ割り当てロジック(else
ブロックの内容)に処理が流れるように修正されました。これにより、高位アドレスの予約が不可能でも、32ビットモードで動作を継続できるようになります。
- Goランタイムのメモリ初期化関数である
-
runtime·MHeap_SysAlloc
の変更:runtime·MHeap_SysAlloc
は、GoヒープがOSからメモリを要求する際に呼び出される関数です。- 64ビットシステムにおいて、この関数が
nil
を返す条件がsizeof(void*) == 8
からsizeof(void*) == 8 && (uintptr)h->bitmap >= 0xffffffffU
に変更されました。 - これは、64ビットシステムであっても、ヒープのビットマップアドレスが32ビットアドレス空間の範囲内にある場合は、予約がまだ有効である可能性があることを意味します。
nil
を返すのは、64ビットシステムであり、かつビットマップアドレスが32ビットアドレス空間の限界を超えている(つまり、高位アドレス空間に依存している)場合に限られます。これにより、より正確な条件でメモリ予約の失敗を判断し、不必要なnil
返却を防ぎます。
-
runtime·SysReserve
(Linux固有) の変更:src/pkg/runtime/mem_linux.c
内のruntime·SysReserve
関数は、Linuxシステムコールmmap
をラップしてメモリを予約します。- 64ビットシステムで、要求されたアドレス
v
が0xffffffffU
(32ビットアドレス空間の限界)を超える場合、この関数はまず、v
から64KBの小さな領域をPROT_NONE
(アクセス不可)でmmap
しようと試みます。 - もしこの小さな
mmap
が失敗し、p != v
(つまり、要求されたアドレスにマッピングできなかった)であれば、それはその高位アドレス空間が利用できないことを意味します。この場合、runtime·SysReserve
はnil
を返し、Goランタイムに予約失敗を通知します。 - この小さな
mmap
によるテストは、特にUser-Mode Linux (UML)のような環境で、Goランタイムが予約しようとする広大なアドレス空間全体をmmap
する前に、そのアドレスがそもそも利用可能かどうかを効率的に確認するためのものです。UMLでは、広大な予約要求が拒否されることがありますが、小さなテストマッピングであれば成功することがあります。この変更は、UMLがこれらの要求を拒否する唯一の環境であるというコメントによって補強されています。
-
runtime·SysMap
(Linux固有) の変更:runtime·SysMap
は、予約された仮想メモリ領域を実際に使用可能にする(読み書き可能にする)ためにmmap
を呼び出す関数です。- ここでも、64ビットシステムで
v
が0xffffffffU
を超える場合にのみ、特別な処理を行うように条件が追加されました。これはSysReserve
と同様に、高位アドレス空間での挙動をより正確に制御するためです。
これらの変更により、Goランタイムは、特定の64ビットLinux環境(特にUML)で高位仮想メモリの予約が失敗した場合でも、堅牢に動作し、32ビットのメモリ割り当てにフォールバックすることで、プログラムの実行を可能にします。
コアとなるコードの変更箇所
src/pkg/runtime/malloc.goc
--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -289,12 +289,13 @@ runtime·mallocinit(void)
// Actually we reserve 17 GB (because the bitmap ends up being 1 GB)
// but it hardly matters: fc is not valid UTF-8 either, and we have to
// allocate 15 GB before we get that far.
+ //
+ // If this fails we fall back to the 32 bit memory mechanism
arena_size = 16LL<<30;
bitmap_size = arena_size / (sizeof(void*)*8/4);
p = runtime·SysReserve((void*)(0x00f8ULL<<32), bitmap_size + arena_size);
- if(p == nil)
- runtime·throw("runtime: cannot reserve arena virtual address space");
- } else {
+ }
+ if (p == nil) {
// On a 32-bit machine, we can't typically get away
// with a giant virtual address space reservation.
// Instead we map the memory information bitmap
@@ -359,8 +360,8 @@ runtime·MHeap_SysAlloc(MHeap *h, uintptr n)
return p;
}
- // On 64-bit, our reservation is all we have.
- if(sizeof(void*) == 8)
+ // If using 64-bit, our reservation is all we have.
+ if(sizeof(void*) == 8 && (uintptr)h->bitmap >= 0xffffffffU)
return nil;
// On 32-bit, once the reservation is gone we can
src/pkg/runtime/mem_linux.c
--- a/src/pkg/runtime/mem_linux.c
+++ b/src/pkg/runtime/mem_linux.c
@@ -73,9 +73,18 @@ runtime·SysReserve(void *v, uintptr n)
// On 64-bit, people with ulimit -v set complain if we reserve too
// much address space. Instead, assume that the reservation is okay
- // and check the assumption in SysMap.
- if(sizeof(void*) == 8)
+ // if we can reserve at least 64K and check the assumption in SysMap.
+ // Only user-mode Linux (UML) rejects these requests.
+ if(sizeof(void*) == 8 && (uintptr)v >= 0xffffffffU) {
+ p = runtime·mmap(v, 64<<10, PROT_NONE, MAP_ANON|MAP_PRIVATE, -1, 0);
+ if (p != v) {
+ return nil;
+ }
+ runtime·munmap(p, 64<<10);
+
+
return v;
+ }
p = runtime·mmap(v, n, PROT_NONE, MAP_ANON|MAP_PRIVATE, -1, 0);
if((uintptr)p < 4096 || -(uintptr)p < 4096) {
@@ -92,7 +101,7 @@ runtime·SysMap(void *v, uintptr n)
mstats.sys += n;
// On 64-bit, we don't actually have v reserved, so tread carefully.
- if(sizeof(void*) == 8) {
+ if(sizeof(void*) == 8 && (uintptr)v >= 0xffffffffU) {
p = runtime·mmap(v, n, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_ANON|MAP_PRIVATE, -1, 0);
if(p != v && addrspace_free(v, n)) {
// On some systems, mmap ignores v without
コアとなるコードの解説
src/pkg/runtime/malloc.goc
の変更点
-
runtime·mallocinit
関数:- 変更前は、64ビットシステムで
runtime·SysReserve
が高位アドレスの予約に失敗すると、runtime·throw
でプログラムが強制終了していました。 - 変更後は、
if (p == nil)
ブロックが追加され、予約失敗時に即座に終了するのではなく、その後の32ビットメモリ割り当てロジック(元々else
ブロックにあった内容)に処理が流れるように修正されました。これにより、64ビット環境で高位アドレスが利用できない場合でも、32ビットのメモリ管理モードでGoランタイムが起動できるようになります。これは、Goプログラムの起動時の堅牢性を高めるための重要な変更です。
- 変更前は、64ビットシステムで
-
runtime·MHeap_SysAlloc
関数:- この関数は、GoのヒープがOSからメモリを要求する際に呼び出されます。
- 変更前は、64ビットシステムであれば無条件に
nil
を返す可能性がありました。 - 変更後は、
if(sizeof(void*) == 8 && (uintptr)h->bitmap >= 0xffffffffU)
という条件が追加されました。これは、「64ビットシステムであり、かつヒープのビットマップが32ビットアドレス空間の限界(0xffffffffU
)を超えている場合」にのみnil
を返すことを意味します。これにより、高位アドレス空間に依存している場合にのみ予約失敗と判断し、より正確なフォールバック判断が可能になります。
src/pkg/runtime/mem_linux.c
の変更点
-
runtime·SysReserve
関数:- この関数は、GoランタイムがOSに仮想メモリ空間の予約を要求する際に使用されます。
- 64ビットシステムで、要求されたアドレス
v
が32ビットアドレス空間の限界(0xffffffffU
)を超える場合、新しいロジックが追加されました。 - まず、
runtime·mmap(v, 64<<10, PROT_NONE, ...)
を呼び出して、要求された高位アドレスv
から64KBの小さな領域をPROT_NONE
(アクセス不可)でマッピングしようと試みます。 - もしこのテストマッピングが失敗し、
p != v
(つまり、要求されたアドレスにマッピングできなかった)であれば、その高位アドレス空間は利用できないと判断し、runtime·SysReserve
はnil
を返します。 - このテストは、特にUser-Mode Linux (UML)のような環境で、Goランタイムが予約しようとする広大なアドレス空間全体を
mmap
する前に、そのアドレスがそもそも利用可能かどうかを効率的に確認するためのものです。UMLでは、広大な予約要求が拒否されることがありますが、小さなテストマッピングであれば成功することがあります。この変更により、UML環境でのGoランタイムの起動失敗を防ぎます。
-
runtime·SysMap
関数:- この関数は、
SysReserve
で予約された仮想メモリ領域を実際に使用可能にする(読み書き可能にする)ためにmmap
を呼び出します。 - ここでも、
if(sizeof(void*) == 8 && (uintptr)v >= 0xffffffffU)
という条件が追加され、64ビットシステムで高位アドレス空間を扱う場合にのみ、特別な処理を行うように変更されました。これはSysReserve
と同様に、高位アドレス空間での挙動をより正確に制御し、堅牢性を高めるためのものです。
- この関数は、
これらの変更は、Goランタイムが様々なLinux環境、特にUser-Mode Linuxのような特殊な64ビット環境においても、安定して動作するための重要な改善です。
関連リンク
- Go言語のメモリ管理に関するドキュメントやブログ記事 (当時の情報源はGoの公式ドキュメントやGoブログ、またはGoのソースコードコメントから探す必要があります)
- User-Mode Linux (UML) の公式ドキュメントや解説記事
mmap
システムコールに関するLinux manページ
参考にした情報源リンク
- https://golang.org/cl/5634050 (Goのコードレビューシステムにおけるこのコミットの変更リスト)
- Go言語の公式ドキュメント (当時のバージョンに準ずる)
- Linuxカーネルのメモリ管理に関するドキュメント
- User-Mode Linuxのドキュメント
mmap(2)
man page (Linux)```markdown
[インデックス 11714] ファイルの概要
このコミットは、Go言語のランタイムにおけるメモリ管理の改善に関するものです。特に、64ビット環境、中でもUser-Mode Linux (UML)のような特定の環境で発生する、高位仮想メモリ空間の予約に関する問題を解決することを目的としています。Goランタイムが特定の高位アドレスでのメモリ予約に失敗した場合に、32ビットのメモリ割り当てメカニズムにフォールバックするよう変更が加えられています。
コミット
commit d37a8b73c504c232084666b292f20debb397bd27
Author: Paul Borman <borman@google.com>
Date: Wed Feb 8 14:39:16 2012 -0500
runtime: drop to 32 bit malloc if 64 bit will not work
On 64 bit UML it is not possible to reserve memory at 0xF8<<32.
Detect when linux cannot use these high virtual memory addresses
and drop back to the 32 bit memory allocator.
R=rsc, cw
CC=golang-dev
https://golang.org/cl/5634050
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d37a8b73c504c232084666b292f20debb397bd27
元コミット内容
このコミットは、Goランタイムが64ビットシステム上でメモリを予約する際の挙動を修正します。特に、User-Mode Linux (UML)のような環境で、0xF8<<32
のような非常に高い仮想メモリアドレスにメモリを予約できない場合に、Goランタイムがクラッシュするのではなく、32ビットのメモリ割り当てメカニズムに切り替えるように変更されています。
変更されたファイルは以下の2つです。
src/pkg/runtime/malloc.goc
: メモリ初期化ロジックとヒープ割り当てロジックが変更され、高位アドレスの予約失敗時に32ビットフォールバックを考慮するようになりました。src/pkg/runtime/mem_linux.c
: Linux固有のメモリ予約システムコール(SysReserve
)に、64ビットUML環境での高位アドレス予約失敗を検出するロジックが追加されました。
変更の背景
Go言語のランタイムは、効率的なメモリ管理のために、起動時に広大な仮想アドレス空間を予約しようとします。64ビットシステムでは、この予約は非常に高い仮想メモリ領域(例: 0xF8<<32
、約64GBの仮想アドレス空間の開始点)で行われることが一般的です。これは、ヒープの成長やガベージコレクションの効率化のために、連続した大きなアドレス空間を確保するためです。
しかし、特定の64ビットLinux環境、特にUser-Mode Linux (UML)では、この高位仮想メモリアドレスでの予約がOSによって拒否されるという問題がありました。UMLは、通常のLinuxカーネル上でユーザープロセスとして動作するLinuxカーネルであり、そのメモリ管理の挙動がネイティブなLinuxとは異なる場合があります。この予約失敗は、Goプログラムの起動失敗やクラッシュに直結していました。
このコミットの目的は、このような環境でもGoプログラムが動作できるように、高位アドレスの予約が不可能であることを検出し、より制限された32ビットのメモリ割り当て戦略に自動的にフォールバックすることです。これにより、Goプログラムの互換性と堅牢性が向上します。
前提知識の解説
仮想メモリ (Virtual Memory)
オペレーティングシステムが提供するメモリ管理の抽象化層です。各プロセスは、物理メモリとは独立した、連続した仮想アドレス空間を持っているかのように見えます。OSは、この仮想アドレスを物理メモリのアドレスにマッピングします。これにより、プログラムは物理メモリの制約から解放され、より大きなアドレス空間を利用したり、メモリ保護を実現したりできます。
mmap
と munmap
システムコール
Unix系OSにおけるメモリマッピングのためのシステムコールです。
mmap()
: ファイルやデバイス、または匿名メモリ領域をプロセスの仮想アドレス空間にマッピングするために使用されます。メモリを予約(reserve)する際にも、PROT_NONE
(アクセス不可)フラグと共に使用されることがあります。munmap()
:mmap()
でマッピングされた領域をアンマッピング(解放)します。
Goランタイムのメモリ管理
Goランタイムは独自のメモリ管理システム(Goスケジューラ、ガベージコレクタ、ヒープアロケータなど)を持っています。Goのヒープは、OSから大きな仮想アドレス空間を予約し、その中から小さなチャンクを割り当てて使用します。この予約は、将来のメモリ要求に備えて行われ、メモリの断片化を防ぎ、ガベージコレクションの効率を高めるのに役立ちます。
User-Mode Linux (UML)
UMLは、Linuxカーネルをユーザー空間のアプリケーションとして実行できるようにする技術です。これにより、通常のLinuxシステム上で別のLinuxシステムを「ゲスト」として実行できます。UMLは、カーネル開発、システムエミュレーション、サンドボックス環境の構築などに利用されます。UML環境では、ホストOSの制約や設定により、ゲストOSが利用できる仮想メモリ空間に制限がある場合があり、特に高位アドレスの予約が困難なことがあります。
32ビットと64ビットのアドレス空間
- 32ビットシステム: 仮想アドレス空間は2^32バイト(約4GB)に制限されます。プログラムが直接アクセスできるメモリは最大4GBです。
- 64ビットシステム: 仮想アドレス空間は2^64バイト(非常に広大)に拡張されます。これにより、プログラムは理論上、はるかに大きなメモリを直接アドレス指定できます。Goランタイムが
0xF8<<32
のような高位アドレスを予約しようとするのは、この64ビットアドレス空間の広さを活用するためです。0xffffffffU
は32ビット符号なし整数の最大値であり、これを超えるアドレスは64ビットアドレス空間に属します。
技術的詳細
このコミットの核心は、Goランタイムが64ビットシステムでメモリを初期化する際に、高位仮想アドレス空間の予約が成功するかどうかをより堅牢にチェックし、失敗した場合に適切なフォールバックパスを提供することです。
-
runtime·mallocinit
の変更:- Goランタイムのメモリ初期化関数である
runtime·mallocinit
は、64ビットシステムでは通常、16LL<<30
(16GB) のアリーナサイズと、そのビットマップのために0x00f8ULL<<32
という非常に高い仮想アドレスからメモリを予約しようとします。 - 変更前は、この
runtime·SysReserve
呼び出しがnil
を返した場合(予約失敗)、即座にruntime·throw("runtime: cannot reserve arena virtual address space")
でパニックを起こしていました。 - 変更後は、
if (p == nil)
ブロックが追加され、予約が失敗した場合でも即座にパニックを起こさず、その後の32ビットメモリ割り当てロジック(else
ブロックの内容)に処理が流れるように修正されました。これにより、高位アドレスの予約が不可能でも、32ビットモードで動作を継続できるようになります。
- Goランタイムのメモリ初期化関数である
-
runtime·MHeap_SysAlloc
の変更:runtime·MHeap_SysAlloc
は、GoヒープがOSからメモリを要求する際に呼び出される関数です。- 64ビットシステムにおいて、この関数が
nil
を返す条件がsizeof(void*) == 8
からsizeof(void*) == 8 && (uintptr)h->bitmap >= 0xffffffffU
に変更されました。 - これは、64ビットシステムであっても、ヒープのビットマップアドレスが32ビットアドレス空間の範囲内にある場合は、予約がまだ有効である可能性があることを意味します。
nil
を返すのは、64ビットシステムであり、かつビットマップアドレスが32ビットアドレス空間の限界を超えている(つまり、高位アドレス空間に依存している)場合に限られます。これにより、より正確な条件でメモリ予約の失敗を判断し、不必要なnil
返却を防ぎます。
-
runtime·SysReserve
(Linux固有) の変更:src/pkg/runtime/mem_linux.c
内のruntime·SysReserve
関数は、Linuxシステムコールmmap
をラップしてメモリを予約します。- 64ビットシステムで、要求されたアドレス
v
が0xffffffffU
(32ビットアドレス空間の限界)を超える場合、この関数はまず、v
から64KBの小さな領域をPROT_NONE
(アクセス不可)でmmap
しようと試みます。 - もしこの小さな
mmap
が失敗し、p != v
(つまり、要求されたアドレスにマッピングできなかった)であれば、それはその高位アドレス空間が利用できないことを意味します。この場合、runtime·SysReserve
はnil
を返し、Goランタイムに予約失敗を通知します。 - この小さな
mmap
によるテストは、特にUser-Mode Linux (UML)のような環境で、Goランタイムが予約しようとする広大なアドレス空間全体をmmap
する前に、そのアドレスがそもそも利用可能かどうかを効率的に確認するためのものです。UMLでは、広大な予約要求が拒否されることがありますが、小さなテストマッピングであれば成功することがあります。この変更は、UMLがこれらの要求を拒否する唯一の環境であるというコメントによって補強されています。
-
runtime·SysMap
(Linux固有) の変更:runtime·SysMap
は、予約された仮想メモリ領域を実際に使用可能にする(読み書き可能にする)ためにmmap
を呼び出す関数です。- ここでも、64ビットシステムで
v
が0xffffffffU
を超える場合にのみ、特別な処理を行うように条件が追加されました。これはSysReserve
と同様に、高位アドレス空間での挙動をより正確に制御するためです。
これらの変更により、Goランタイムは、特定の64ビットLinux環境(特にUML)で高位仮想メモリの予約が失敗した場合でも、堅牢に動作し、32ビットのメモリ割り当てにフォールバックすることで、プログラムの実行を可能にします。
コアとなるコードの変更箇所
src/pkg/runtime/malloc.goc
--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -289,12 +289,13 @@ runtime·mallocinit(void)
// Actually we reserve 17 GB (because the bitmap ends up being 1 GB)
// but it hardly matters: fc is not valid UTF-8 either, and we have to
// allocate 15 GB before we get that far.
+ //
+ // If this fails we fall back to the 32 bit memory mechanism
arena_size = 16LL<<30;
bitmap_size = arena_size / (sizeof(void*)*8/4);
p = runtime·SysReserve((void*)(0x00f8ULL<<32), bitmap_size + arena_size);
- if(p == nil)
- runtime·throw("runtime: cannot reserve arena virtual address space");
- } else {
+ }
+ if (p == nil) {
// On a 32-bit machine, we can't typically get away
// with a giant virtual address space reservation.
// Instead we map the memory information bitmap
@@ -359,8 +360,8 @@ runtime·MHeap_SysAlloc(MHeap *h, uintptr n)
return p;
}
- // On 64-bit, our reservation is all we have.
- if(sizeof(void*) == 8)
+ // If using 64-bit, our reservation is all we have.
+ if(sizeof(void*) == 8 && (uintptr)h->bitmap >= 0xffffffffU)
return nil;
// On 32-bit, once the reservation is gone we can
src/pkg/runtime/mem_linux.c
--- a/src/pkg/runtime/mem_linux.c
+++ b/src/pkg/runtime/mem_linux.c
@@ -73,9 +73,18 @@ runtime·SysReserve(void *v, uintptr n)
// On 64-bit, people with ulimit -v set complain if we reserve too
// much address space. Instead, assume that the reservation is okay
- // if we can reserve at least 64K and check the assumption in SysMap.
- if(sizeof(void*) == 8)
+ // if we can reserve at least 64K and check the assumption in SysMap.
+ // Only user-mode Linux (UML) rejects these requests.
+ if(sizeof(void*) == 8 && (uintptr)v >= 0xffffffffU) {
+ p = runtime·mmap(v, 64<<10, PROT_NONE, MAP_ANON|MAP_PRIVATE, -1, 0);
+ if (p != v) {
+ return nil;
+ }
+ runtime·munmap(p, 64<<10);
+
+
return v;
+ }
p = runtime·mmap(v, n, PROT_NONE, MAP_ANON|MAP_PRIVATE, -1, 0);
if((uintptr)p < 4096 || -(uintptr)p < 4096) {
@@ -92,7 +101,7 @@ runtime·SysMap(void *v, uintptr n)
mstats.sys += n;
// On 64-bit, we don't actually have v reserved, so tread carefully.
- if(sizeof(void*) == 8) {
+ if(sizeof(void*) == 8 && (uintptr)v >= 0xffffffffU) {
p = runtime·mmap(v, n, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_ANON|MAP_PRIVATE, -1, 0);
if(p != v && addrspace_free(v, n)) {
// On some systems, mmap ignores v without
コアとなるコードの解説
src/pkg/runtime/malloc.goc
の変更点
-
runtime·mallocinit
関数:- 変更前は、64ビットシステムで
runtime·SysReserve
が高位アドレスの予約に失敗すると、runtime·throw
でプログラムが強制終了していました。 - 変更後は、
if (p == nil)
ブロックが追加され、予約失敗時に即座に終了するのではなく、その後の32ビットメモリ割り当てロジック(元々else
ブロックにあった内容)に処理が流れるように修正されました。これにより、64ビット環境で高位アドレスが利用できない場合でも、32ビットのメモリ管理モードでGoランタイムが起動できるようになります。これは、Goプログラムの起動時の堅牢性を高めるための重要な変更です。
- 変更前は、64ビットシステムで
-
runtime·MHeap_SysAlloc
関数:- この関数は、GoのヒープがOSからメモリを要求する際に呼び出されます。
- 変更前は、64ビットシステムであれば無条件に
nil
を返す可能性がありました。 - 変更後は、
if(sizeof(void*) == 8 && (uintptr)h->bitmap >= 0xffffffffU)
という条件が追加されました。これは、「64ビットシステムであり、かつヒープのビットマップが32ビットアドレス空間の限界(0xffffffffU
)を超えている場合」にのみnil
を返すことを意味します。これにより、高位アドレス空間に依存している場合にのみ予約失敗と判断し、より正確なフォールバック判断が可能になります。
src/pkg/runtime/mem_linux.c
の変更点
-
runtime·SysReserve
関数:- この関数は、GoランタイムがOSに仮想メモリ空間の予約を要求する際に使用されます。
- 64ビットシステムで、要求されたアドレス
v
が32ビットアドレス空間の限界(0xffffffffU
)を超える場合、新しいロジックが追加されました。 - まず、
runtime·mmap(v, 64<<10, PROT_NONE, ...)
を呼び出して、要求された高位アドレスv
から64KBの小さな領域をPROT_NONE
(アクセス不可)でマッピングしようと試みます。 - もしこのテストマッピングが失敗し、
p != v
(つまり、要求されたアドレスにマッピングできなかった)であれば、その高位アドレス空間は利用できないと判断し、runtime·SysReserve
はnil
を返します。 - このテストは、特にUser-Mode Linux (UML)のような環境で、Goランタイムが予約しようとする広大なアドレス空間全体を
mmap
する前に、そのアドレスがそもそも利用可能かどうかを効率的に確認するためのものです。UMLでは、広大な予約要求が拒否されることがありますが、小さなテストマッピングであれば成功することがあります。この変更により、UML環境でのGoランタイムの起動失敗を防ぎます。
-
runtime·SysMap
関数:- この関数は、
SysReserve
で予約された仮想メモリ領域を実際に使用可能にする(読み書き可能にする)ためにmmap
を呼び出します。 - ここでも、
if(sizeof(void*) == 8 && (uintptr)v >= 0xffffffffU)
という条件が追加され、64ビットシステムで高位アドレス空間を扱う場合にのみ、特別な処理を行うように変更されました。これはSysReserve
と同様に、高位アドレス空間での挙動をより正確に制御し、堅牢性を高めるためのものです。
- この関数は、
これらの変更は、Goランタイムが様々なLinux環境、特にUser-Mode Linuxのような特殊な64ビット環境においても、安定して動作するための重要な改善です。
関連リンク
- Go言語のメモリ管理に関するドキュメントやブログ記事 (当時の情報源はGoの公式ドキュメントやGoブログ、またはGoのソースコードコメントから探す必要があります)
- User-Mode Linux (UML) の公式ドキュメントや解説記事
mmap
システムコールに関するLinux manページ
参考にした情報源リンク
- https://golang.org/cl/5634050 (Goのコードレビューシステムにおけるこのコミットの変更リスト)
- Go言語の公式ドキュメント (当時のバージョンに準ずる)
- Linuxカーネルのメモリ管理に関するドキュメント
- User-Mode Linuxのドキュメント
mmap(2)
man page (Linux)