[インデックス 14761] ファイルの概要
このコミットは、Go言語のランタイムにおけるLinux上でのmmap
システムコールがMAP_LOCKED
フラグ付きで失敗した場合のハンドリングを改善するものです。具体的には、mmap
がEAGAIN
エラーを返した場合に、より適切なエラーメッセージを出力して終了するように変更されています。これにより、以前はnil
ポインタ参照でクラッシュしていた状況が改善されます。
コミット
commit e786829e8320497a062be1b3f78646bcf9375abc
Author: Sébastien Paolacci <sebastien.paolacci@gmail.com>
Date: Sat Dec 29 14:34:06 2012 -0500
runtime: handle locked mmap failure on Linux
Used to then die on a nil pointer situation. Most Linux standard setups are rather
restrictive regarding the default amount of lockable memory.
R=minux.ma, rsc
CC=golang-dev
https://golang.org/cl/6997049
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e786829e8320497a062be1b3f78646bcf9375abc
元コミット内容
runtime: handle locked mmap failure on Linux
Used to then die on a nil pointer situation. Most Linux standard setups are rather
restrictive regarding the default amount of lockable memory.
R=minux.ma, rsc
CC=golang-dev
https://golang.org/cl/6997049
変更の背景
この変更の背景には、GoランタイムがLinuxシステム上でメモリを確保する際にmmap
システムコールを使用し、特定の状況下でMAP_LOCKED
フラグを付けてメモリをロックしようとすることがあります。しかし、多くのLinuxシステムでは、ユーザーがロックできるメモリ量にデフォルトで制限(ulimit -l
で確認できるmemlock
リソース制限)が設けられています。
この制限を超えてメモリをロックしようとすると、mmap
はEAGAIN
エラーを返します。以前のGoランタイムの実装では、このEAGAIN
エラーが適切に処理されておらず、mmap
の戻り値がnil
として扱われ、その後のnil
ポインタ参照によってランタイムがクラッシュするという問題がありました。
このコミットは、このクラッシュを防ぎ、ユーザーに対してより分かりやすいエラーメッセージを提供することで、問題の原因(ロック可能なメモリの不足)を特定しやすくすることを目的としています。
前提知識の解説
mmap
システムコール
mmap
は、Unix系OSでファイルやデバイスをプロセスのアドレス空間にマップするために使用されるシステムコールです。これにより、ファイルの内容をメモリのように直接アクセスできるようになります。メモリの確保にも使用され、特に匿名メモリ領域(ファイルに関連付けられていないメモリ)を確保する際にも利用されます。
MAP_LOCKED
フラグ
mmap
システムコールに渡されるフラグの一つで、マップされたメモリ領域を物理メモリにロック(常駐)させることをOSに要求します。これにより、そのメモリ領域がスワップアウトされるのを防ぎます。リアルタイムアプリケーションや、メモリの内容がディスクに書き出されることによるレイテンシを避けたい場合に利用されます。ただし、この操作は特権を必要とする場合があり、またシステム全体のリソースに影響を与えるため、通常はユーザーがロックできるメモリ量に制限が設けられています。
EAGAIN
エラー
EAGAIN
は、システムコールが一時的に失敗したことを示すエラーコードです。これは、操作を再試行すれば成功する可能性があることを示唆しますが、この文脈では、リソースが一時的に利用できない(この場合はロック可能なメモリが不足している)ために操作が完了できなかったことを意味します。mmap
がMAP_LOCKED
付きで呼ばれた際にEAGAIN
を返すのは、プロセスがロックしようとしたメモリ量が、システムが許可する最大ロック可能メモリ量(ulimit -l
で設定されるmemlock
)を超過した場合によく発生します。
ENOMEM
エラー
ENOMEM
は、システムが要求されたメモリを割り当てることができなかったことを示すエラーコードです。これは通常、システムに利用可能な物理メモリやスワップ領域が不足している場合に発生します。
ulimit -l
ulimit
コマンドは、ユーザーがプロセスに対して設定できるリソース制限を表示または設定するために使用されます。-l
オプションは、プロセスがロックできるメモリの最大量(memlock
)をバイト単位で表示または設定します。この値が小さいと、MAP_LOCKED
フラグを使用するmmap
呼び出しがEAGAIN
エラーで失敗する可能性が高まります。
SELinux execmem
SELinux (Security-Enhanced Linux) は、Linuxカーネルのセキュリティモジュールで、強制アクセス制御 (MAC) を提供します。execmem
は、メモリ領域が実行可能かつ書き込み可能であるという権限を制御するSELinuxのブール値です。一部のアプリケーション(特にJITコンパイラを使用するものなど)は、実行時にメモリを書き込み、それを実行可能にする必要があります。SELinuxがexecmem
を無効にしている場合、このような操作は拒否され、mmap
が失敗する可能性があります。このコミットのコードでは、mmap
が失敗した場合にSELinuxのexecmem
設定を確認するメッセージも含まれています。
技術的詳細
Goランタイムは、メモリ管理のためにOSのmmap
システムコールを利用しています。特に、Goのガベージコレクタやスケジューラは、特定のメモリ領域を物理メモリに固定する必要がある場合があります。このためにMAP_LOCKED
フラグが使用されます。
変更前のコードでは、mmap
が返すエラーコードを適切に区別していませんでした。mmap
がEAGAIN
を返した場合、これはnil
ポインタとして解釈され、その後のメモリ操作でクラッシュを引き起こしていました。
このコミットでは、src/pkg/runtime/mem_linux.c
ファイルに以下の変更が加えられています。
EAGAIN
エラーコード(値は11)がenum
に追加されました。これにより、コード内でEAGAIN
をシンボリックに参照できるようになります。runtime·SysAlloc
関数内でmmap
の戻り値をチェックするロジックが強化されました。- 既存の
p == nil
チェックに加えて、p == (void*)EAGAIN
という条件が追加されました。 - この条件が真の場合、つまり
mmap
がEAGAIN
を返した場合、"runtime: mmap: too much locked memory (check 'ulimit -l')."
というエラーメッセージを出力し、runtime·exit(2)
でプログラムを終了します。
- 既存の
これにより、MAP_LOCKED
によるmmap
呼び出しがEAGAIN
で失敗した場合に、Goランタイムはクラッシュする代わりに、ユーザーに対して具体的な原因(ロック可能なメモリの不足)と解決策(ulimit -l
の確認)を提示して終了するようになります。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/mem_linux.c
ファイルに集中しています。
--- a/src/pkg/runtime/mem_linux.c
+++ b/src/pkg/runtime/mem_linux.c
@@ -10,6 +10,7 @@
enum
{
+ EAGAIN = 11,
ENOMEM = 12,
_PAGE_SIZE = 4096,
};
@@ -63,6 +64,10 @@ runtime·SysAlloc(uintptr n)
if(p == nil) {
// On some systems, mmap with PROT_NONE does not work.
// Try again with PROT_READ|PROT_WRITE.
// This does not affect overall security because
// the memory is not yet in use. It will be
// PROT_NONE-d before being given to user.
p = runtime·mmap(nil, n, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0);
if(p == nil) {
runtime·printf("runtime: mmap: cannot allocate memory\\n");
runtime·exit(2);
}
// Check for executable stack.
// Some systems, like OpenBSD, do not allow
// non-executable stack.
// If you're running SELinux, enable execmem for this process.
runtime·printf("if you're running SELinux, enable execmem for this process.\\n");
runtime·exit(2);
}
+ if(p == (void*)EAGAIN) {
+ runtime·printf("runtime: mmap: too much locked memory (check 'ulimit -l').\\n");
+ runtime·exit(2);
+ }
return nil;
}
return p;
コアとなるコードの解説
-
EAGAIN = 11,
の追加:enum
ブロックにEAGAIN
が追加され、その値がLinuxシステムにおけるEAGAIN
エラーコードの標準的な値である11
に設定されています。これにより、マジックナンバーを避け、コードの可読性と保守性が向上します。 -
if(p == (void*)EAGAIN)
ブロックの追加:runtime·SysAlloc
関数内で、mmap
システムコールがメモリを割り当てようとした後に、その戻り値p
がnil
であるかどうかのチェックが行われています。このコミットでは、既存のif(p == nil)
ブロックの直後に、if(p == (void*)EAGAIN)
という新しい条件分岐が追加されました。mmap
システムコールは、エラーが発生した場合に-1
を返します。C言語では、ポインタ型にキャストされた-1
は通常nil
とは異なる値になります。しかし、Goのランタイムコードでは、mmap
の戻り値をvoid*
として受け取り、エラーコードを直接ポインタ値として解釈する場合があります。このため、EAGAIN
(数値の11)がポインタにキャストされた値と比較されています。- この条件が真の場合、つまり
mmap
がEAGAIN
エラーを示した場合、以下の処理が実行されます。runtime·printf("runtime: mmap: too much locked memory (check 'ulimit -l').\\n");
:ユーザーに対して、ロック可能なメモリが不足していること、そしてulimit -l
コマンドで設定を確認するよう促すエラーメッセージが出力されます。runtime·exit(2);
:プログラムが終了コード2で終了します。これは、一般的なエラーを示す終了コードです。
この変更により、GoランタイムはMAP_LOCKED
付きのmmap
呼び出しがEAGAIN
で失敗した場合に、より堅牢かつ情報量の多い方法で対応できるようになりました。
関連リンク
- Go言語のランタイムソースコード: https://github.com/golang/go/tree/master/src/runtime
mmap(2)
manページ (Linux): https://man7.org/linux/man-pages/man2/mmap.2.htmlulimit(1)
manページ (Linux): https://man7.org/linux/man-pages/man1/ulimit.1.html- SELinux
execmem
に関する情報: https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/selinux_users_and_administrators_guide/sect-security-enhanced_linux-booleans-execmem
参考にした情報源リンク
- Go CL 6997049: https://golang.org/cl/6997049 (コミットメッセージに記載されているGoのコードレビューシステムへのリンク)
mmap
andMAP_LOCKED
behavior on Linux: https://stackoverflow.com/questions/1024096/mmap-map-locked-and-eagain- Understanding
ulimit -l
: https://www.ibm.com/docs/en/aix/7.2?topic=commands-ulimit-command - Linux error codes (
errno.h
): https://www.gnu.org/software/libc/manual/html_node/Error-Codes.html (EAGAIN, ENOMEMなどの定義)