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

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

このコミットは、Goランタイムにおけるmadviseシステムコールの挙動を修正するものです。具体的には、madviseが失敗した場合にランタイムがクラッシュするのではなく、その失敗を無視するように変更されています。これは、メモリの解放がOSによって拒否される可能性があるシナリオ(例えば、mlockall(MCL_FUTURE)が実行されている場合)に対応するための重要な変更です。

コミット

commit 295a4d8e6433e8a8b6df25375fb780b0f75ff4e6
Author: Russ Cox <rsc@golang.org>
Date:   Sat Dec 22 15:06:28 2012 -0500

    runtime: ignore failure from madvise
    
    When we release memory to the OS, if the OS doesn't want us
    to release it (for example, because the program executed
    mlockall(MCL_FUTURE)), madvise will fail. Ignore the failure
    instead of crashing.
    
    Fixes #3435.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/6998052

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

https://github.com/golang/go/commit/295a4d8e6433e8a8b6df25375fb780b0f75ff4e6

元コミット内容

GoランタイムがOSにメモリを解放しようとした際、OSがその解放を拒否した場合(例: プログラムがmlockall(MCL_FUTURE)を実行しているため)、madviseシステムコールが失敗することがあります。このコミットは、madviseの失敗を無視し、クラッシュしないように修正します。

関連するIssue: #3435

変更の背景

Goランタイムは、ガベージコレクションなどによって不要になったメモリをOSに返却する際に、madviseシステムコールを使用します。しかし、特定の状況下では、このmadviseコールが失敗することがありました。特に、プログラムがmlockall(MCL_FUTURE)を呼び出して、将来的に確保されるすべてのメモリページを物理メモリにロックするようOSに要求している場合、Goランタイムがメモリを解放しようとしても、OSはそれを拒否します。

この拒否が発生すると、以前のGoランタイムではmadviseの失敗が致命的なエラーと見なされ、プログラムがクラッシュしていました。これは、mlockallのような特定のシステム設定やセキュリティ要件を持つ環境でGoプログラムが安定して動作しない原因となっていました。

このコミットの目的は、このようなmadviseの失敗が必ずしもプログラムの実行にとって致命的ではないことを認識し、ランタイムがクラッシュする代わりにその失敗を許容するように変更することです。これにより、Goプログラムの堅牢性と互換性が向上します。

前提知識の解説

madviseシステムコール

madviseは、Unix系OS(Linux, macOS, FreeBSDなど)で利用可能なシステムコールで、プロセスが仮想メモリの特定の範囲についてOSに「アドバイス」を与えるために使用されます。このアドバイスは、OSがメモリの利用方法を最適化するのに役立ちますが、OSは必ずしもそのアドバイスに従う義務はありません。

一般的なmadviseの用途には以下のようなものがあります。

  • MADV_DONTNEED: 指定されたメモリ範囲の内容はもう必要ないため、OSはいつでもそのページを解放してよい。GoランタイムがメモリをOSに返却する際にこれを使用します。
  • MADV_WILLNEED: 指定されたメモリ範囲の内容はすぐに必要になるため、OSは事前にページをプリフェッチしてよい。
  • MADV_FREE: 指定されたメモリ範囲は解放されるが、後で再利用される可能性があるため、OSはページをスワップアウトしてもよい。

madviseはあくまで「アドバイス」であり、OSがその要求を拒否する可能性も存在します。

mlockallシステムコールと MCL_FUTURE

mlockallは、プロセスのアドレス空間全体、または将来的に確保されるメモリページを物理メモリにロックするシステムコールです。これにより、これらのメモリページがスワップアウトされるのを防ぎます。これは、リアルタイムシステムやセキュリティが非常に重要なアプリケーション(例: 暗号化キーをメモリに保持するアプリケーション)で、メモリの内容がディスクに漏洩するのを防ぐために使用されることがあります。

mlockallには以下のフラグがあります。

  • MCL_CURRENT: 現在プロセスが使用しているすべてのメモリページをロックします。
  • MCL_FUTURE: 将来的にプロセスが確保するすべてのメモリページをロックします。

このコミットの文脈では、mlockall(MCL_FUTURE)が重要です。このフラグが設定されていると、Goランタイムがmadvise(MADV_DONTNEED)を使ってメモリをOSに返却しようとしても、OSは「このメモリはロックされているため解放できない」と判断し、madviseコールが失敗する原因となります。

アセンブリ言語とシステムコール

Goランタイムは、OSとの直接的なやり取りのために、アセンブリ言語で書かれたコードを使用することがあります。これは、パフォーマンスの最適化や、特定のOS機能(システムコール)への低レベルなアクセスが必要な場合に用いられます。

このコミットで変更されているファイルは、src/pkg/runtime/sys_darwin_386.sのように、OSとアーキテクチャの組み合わせごとに存在するアセンブリファイルです。これらのファイルには、madviseのようなシステムコールを呼び出すためのアセンブリ命令が含まれています。

システムコールが成功したかどうかは、通常、レジスタの値(例: AXR0)や特定のフラグ(例: キャリーフラグ)をチェックすることで判断されます。失敗した場合、エラーコードが返されることが一般的です。

技術的詳細

このコミットの技術的な核心は、Goランタイムがmadviseシステムコールを呼び出した後のエラーハンドリングの変更にあります。

以前のバージョンでは、madviseシステムコールがエラーを返した場合、Goランタイムは意図的にクラッシュするようなアセンブリ命令を実行していました。これは、メモリ管理における予期せぬ失敗を早期に検出し、開発者に問題があることを知らせるための防御的なプログラミング手法と考えられます。

しかし、mlockall(MCL_FUTURE)のような正当な理由でmadviseが失敗するケースが判明したため、この「クラッシュ」という挙動は過剰であると判断されました。mlockallが有効な環境では、メモリの解放ができないのはOSの設計上の意図であり、Goランタイムがそのメモリを解放できないからといってクラッシュする必要はありません。単にOSがメモリを解放してくれないだけであり、プログラムの実行自体には影響がない場合が多いからです。

このコミットでは、各OS/アーキテクチャのアセンブリファイルにおいて、madviseシステムコール呼び出し後のエラーチェックとクラッシュロジックが削除されています。代わりに、システムコールが失敗した場合でも、単にRET(リターン)命令を実行して呼び出し元に戻るようになっています。これにより、madviseの失敗は無視され、プログラムの実行が継続されます。

具体的には、以下のようなアセンブリ命令が削除されています(アーキテクチャによって命令は異なりますが、概念は同じです):

  • x86/x64 (Darwin, FreeBSD, NetBSD, OpenBSD):

    • JAE 2(PC) / JCC 2(PC): システムコールが成功した場合(キャリーフラグがクリアされている場合)にジャンプする命令。これが削除されることで、成功/失敗に関わらず次の命令に進むようになります。
    • MOVL $0xf1, 0xf1 // crash: 意図的に不正なメモリアドレスに書き込もうとすることでクラッシュを引き起こす命令。
  • ARM (FreeBSD, Linux):

    • MOVW.CS $0, R9 // crash on syscall failure / MOVW.HI $0, R9 // crash on syscall failure: システムコールが失敗した場合(キャリーフラグがセットされている場合)にクラッシュを引き起こす命令。

これらの変更により、Goランタイムはmadviseの失敗をより寛容に扱い、特定の環境下での安定性が向上しました。

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

このコミットでは、Goランタイムの複数のアセンブリファイルが変更されています。これらは、異なるOS(Darwin, FreeBSD, Linux, NetBSD, OpenBSD)とCPUアーキテクチャ(386, amd64, arm)の組み合わせに対応するものです。

変更されたファイルは以下の通りです。

  • src/pkg/runtime/sys_darwin_386.s
  • src/pkg/runtime/sys_darwin_amd64.s
  • src/pkg/runtime/sys_freebsd_386.s
  • src/pkg/runtime/sys_freebsd_amd64.s
  • src/pkg/runtime/sys_freebsd_arm.s
  • src/pkg/runtime/sys_linux_386.s
  • src/pkg/runtime/sys_linux_amd64.s
  • src/pkg/runtime/sys_linux_arm.s
  • src/pkg/runtime/sys_netbsd_386.s
  • src/pkg/runtime/sys_netbsd_amd64.s
  • src/pkg/runtime/sys_openbsd_amd64.s

各ファイルにおける変更のパターンは非常に似ており、madviseシステムコールを呼び出した直後のエラーチェックと、それに続くクラッシュロジックが削除されています。

例: src/pkg/runtime/sys_darwin_386.s の変更

--- a/src/pkg/runtime/sys_darwin_386.s
+++ b/src/pkg/runtime/sys_darwin_386.s
@@ -47,8 +47,7 @@ TEXT runtime·mmap(SB),7,$0
 TEXT runtime·madvise(SB),7,$0
  	MOVL	$75, AX
  	INT	$0x80
--	JAE	2(PC)
--	MOVL	$0xf1, 0xf1  // crash
-+	// ignore failure - maybe pages are locked
  	RET
 
 TEXT runtime·munmap(SB),7,$0

コアとなるコードの解説

上記の差分は、runtime·madviseというGoランタイムの関数(アセンブリで実装されている)における変更を示しています。

  1. MOVL $75, AX: システムコール番号75(Darwin/x86におけるmadviseの番号)をAXレジスタにロードします。
  2. INT $0x80: ソフトウェア割り込み0x80を発生させ、システムコールを実行します。この命令が実行されると、OSカーネルに制御が渡り、madviseシステムコールが処理されます。
  3. JAE 2(PC): これは削除された行です。JAEは "Jump if Above or Equal" の略で、キャリーフラグがクリアされている(つまり、システムコールが成功した)場合に、現在のプログラムカウンタ(PC)から2バイト先にジャンプするという意味です。この命令が存在すると、システムコールが成功した場合はクラッシュロジックをスキップし、失敗した場合は次のクラッシュロジックに進んでいました。
  4. MOVL $0xf1, 0xf1 // crash: これも削除された行です。0xf1という値を0xf1というメモリアドレスに書き込もうとする命令です。0xf1は通常、有効なメモリアドレスではないため、この操作はセグメンテーション違反を引き起こし、プログラムをクラッシュさせます。これは、madviseが失敗した場合に意図的にクラッシュさせるためのコードでした。
  5. // ignore failure - maybe pages are locked: 新しく追加されたコメントです。このコメントは、madviseの失敗を無視する意図と、その理由(ページがロックされている可能性があるため)を明確に示しています。
  6. RET: 関数からリターンします。システムコールが成功したか失敗したかに関わらず、この命令が実行され、呼び出し元に制御が戻ります。

この変更により、madviseシステムコールがエラーを返しても、Goランタイムはクラッシュすることなく、その失敗を透過的に処理するようになりました。これは、mlockall(MCL_FUTURE)のような、メモリが意図的にロックされている環境でのGoプログラムの安定性を大幅に向上させます。

関連リンク

参考にした情報源リンク