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

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

このコミットは、Go言語のランタイムにおけるFreeBSD環境でのメモリ管理に関する重要な改善を導入しています。具体的には、runtime.SysUnused関数のFreeBSD実装を追加し、これに必要なmadviseシステムコールのアセンブリレベルでの実装を提供します。これにより、Goのガベージコレクタが解放したメモリをオペレーティングシステムに適切に返却できるようになり、長期間実行されるGoプロセスにおけるメモリ使用量の肥大化(OOM発生)を防ぐことを目的としています。

コミット

commit 314fd624343eaa2d110f9b5b192a0a8f354d63ed
Author: John Graham-Cumming <jgc@jgc.org>
Date:   Sat Nov 24 15:55:19 2012 +1100

    runtime: implement runtime.SysUnused on FreeBSD
    
    madvise was missing so implement it in assembler. This change
    needs to be extended to the other BSD variantes (Net and Open)
    
    Without this change the scavenger will attempt to pass memory back
    to the operating system when it has become idle, but the memory is
    not returned and for long running Go processes the total memory used
    can grow until OOM occurs.
    
    I have only been able to test the code on FreeBSD AMD64. The ARM
    platforms needs testing.
    
    R=golang-dev, mikioh.mikioh, dave, jgc, minux.ma
    CC=golang-dev
    https://golang.org/cl/6850081

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

https://github.com/golang/go/commit/314fd624343eaa2d110f9b5b192a0a8f354d63ed

元コミット内容

Goランタイムにおいて、FreeBSD上でのruntime.SysUnusedを実装する。 madviseが欠落していたため、アセンブラで実装する。この変更は他のBSD派生(NetBSD、OpenBSD)にも拡張する必要がある。 この変更がないと、スカベンジャー(ガベージコレクタの一部)はアイドル状態になったメモリをオペレーティングシステムに返却しようとするが、メモリが返却されず、長期間実行されるGoプロセスでは総メモリ使用量が増大し、最終的にOOM(Out Of Memory)が発生する可能性がある。 このコードはFreeBSD AMD64でのみテスト済み。ARMプラットフォームでのテストが必要。

変更の背景

Go言語のランタイムは、ガベージコレクタ(GC)によってメモリを管理します。GCは不要になったメモリを回収しますが、回収したメモリをすぐにオペレーティングシステム(OS)に返却するとは限りません。Goランタイムは、将来のメモリ割り当てのために回収したメモリを内部的に保持することがよくあります。しかし、長期間にわたって使用されないメモリ領域がある場合、それをOSに返却することで、システム全体のメモリ効率を向上させることができます。

このコミットが作成された2012年当時、GoのランタイムはFreeBSD環境において、ガベージコレクタが解放したメモリをOSに返却するメカニズムが不完全でした。具体的には、メモリ領域の利用状況をOSに助言するmadviseシステムコールがGoランタイムから適切に呼び出されていなかったため、Goプロセスが使用しなくなったメモリがOSに解放されず、プロセス全体のメモリフットプリントが時間とともに増大し続ける問題がありました。これは特に、長時間稼働するサーバーアプリケーションなどで深刻な問題となり、最終的にはシステム全体のメモリ不足を引き起こす可能性がありました。

この問題に対処するため、Goランタイムの「スカベンジャー」(ガベージコレクタの一部で、OSへのメモリ返却を担当するコンポーネント)が、アイドル状態のメモリをOSに返却できるように、FreeBSD固有のmadviseシステムコールをGoランタイムから呼び出せるようにする必要がありました。

前提知識の解説

1. ガベージコレクション (Garbage Collection, GC) とメモリ管理

Go言語は自動メモリ管理、すなわちガベージコレクションを採用しています。プログラマが手動でメモリを解放する必要がなく、GCが不要になったオブジェクトを自動的に検出し、そのメモリを回収します。GoのGCは、回収したメモリをすぐにOSに返却するのではなく、将来の割り当てのためにヒープ内に保持することがあります。しかし、一定期間使用されないメモリは、OSに返却することでシステム全体のメモリ効率を高めることができます。

2. madviseシステムコール

madviseは、Unix系OS(Linux, FreeBSDなど)で利用可能なシステムコールの一つです。これは、プロセスが特定のメモリ領域をどのように使用するつもりであるかをカーネルに「助言 (advise)」するために使用されます。カーネルはこの助言を参考に、メモリ管理の最適化を行います。 madviseには様々なフラグがあり、このコミットで特に重要なのはMADV_FREE(または類似の機能を持つフラグ)です。MADV_FREEは、指定されたメモリ領域がもはやプロセスによって積極的に使用されておらず、カーネルがそのページを解放してもよいことを示します。これにより、カーネルは必要に応じてそのメモリを他のプロセスに再割り当てしたり、スワップアウトしたりすることができます。

3. runtime.SysUnused

Goランタイムには、OS固有のメモリ管理機能と連携するための抽象化された関数群があります。runtime.SysUnusedはその一つで、Goのガベージコレクタが「このメモリ領域はもう使わないので、OSに返却しても構わない」と判断した際に呼び出される関数です。この関数は、各OSの実装に応じて、madviseのようなシステムコールを呼び出して実際のメモリ返却処理を行います。

4. システムコールとアセンブリ

システムコールは、ユーザー空間のプログラムがカーネルの機能を利用するためのインターフェースです。通常、C言語のライブラリ関数(例: madvise()) を通じて呼び出されますが、Goランタイムのような低レベルのコードでは、OSのシステムコールを直接アセンブリ言語で呼び出すことがあります。これは、パフォーマンスの最適化、特定のレジスタの使用、またはCgo(GoとCの相互運用機能)を介さずにOSの機能に直接アクセスする必要がある場合に用いられます。各アーキテクチャ(x86, AMD64, ARMなど)とOS(Linux, FreeBSDなど)によって、システムコールの呼び出し規約(引数の渡し方、システムコール番号など)が異なるため、それぞれに対応したアセンブリコードが必要になります。

5. FreeBSDのシステムコール規約

FreeBSDにおけるシステムコールの呼び出し規約は、アーキテクチャによって異なります。

  • i386 (32-bit): システムコール番号は%eaxレジスタに格納され、引数はスタックにプッシュされます。int $0x80命令でカーネルにトラップします。
  • AMD64 (64-bit): システムコール番号は%raxレジスタに格納され、引数は特定のレジスタ(%rdi, %rsi, %rdx, %rcx, %r8, %r9)に格納されます。syscall命令でカーネルにトラップします。
  • ARM: システムコール番号はR7レジスタに格納され、引数はR0からR6レジスタに格納されます。SWI(Software Interrupt)命令でカーネルにトラップします。

madviseシステムコールは、FreeBSDではシステムコール番号75に割り当てられています。

技術的詳細

このコミットの主要な技術的課題は、FreeBSD環境でGoランタイムがmadviseシステムコールを適切に呼び出せるようにすることでした。

  1. madviseシステムコールの欠落: 既存のFreeBSD向けGoランタイムには、madviseシステムコールを呼び出すためのラッパー関数やアセンブリコードが欠落していました。これにより、runtime.SysUnusedが実質的に何も行わない状態になっていました。

  2. アセンブリでの実装: madviseは低レベルのメモリ操作に関わるため、Goランタイムはこれを直接システムコールとして呼び出す必要があります。これは、Goの標準ライブラリが提供するCgoを介したCライブラリの呼び出しではなく、OSのシステムコールを直接アセンブリ言語で実装することを意味します。これにより、オーバーヘッドを最小限に抑え、OSとのより密接な連携を可能にします。

  3. アーキテクチャ固有の実装: システムコールの呼び出し規約はCPUアーキテクチャ(i386, AMD64, ARM)によって異なるため、それぞれのアーキテクチャに対応したアセンブリコードを記述する必要があります。このコミットでは、sys_freebsd_386.s, sys_freebsd_amd64.s, sys_freebsd_arm.sにそれぞれruntime·madvise関数が追加されています。

  4. defs_freebsd.goの更新: madviseシステムコールで使用するMADV_FREEフラグの定数(FreeBSDでは0x5)をGoランタイムが認識できるように、src/pkg/runtime/defs_freebsd.goおよび対応するヘッダファイル(defs_freebsd_386.h, defs_freebsd_amd64.h, defs_freebsd_arm.h)にMADV_FREEの定義が追加されています。これにより、GoコードからMADV_FREE定数を使用できるようになります。

  5. mem_freebsd.cの修正: runtime·SysUnused関数が、コメントアウトされていたTODO(rsc): call madvise MADV_DONTNEEDの代わりに、新しく実装されたruntime·madviseMADV_FREEフラグ付きで呼び出すように変更されました。これにより、Goのスカベンジャーが解放したメモリをOSに返却する実際の処理が実行されるようになります。

この変更により、Goプロセスが使用しなくなったメモリがFreeBSDカーネルに適切に通知され、カーネルがそのメモリを再利用できるようになるため、Goアプリケーションのメモリフットプリントがより効率的に管理されるようになります。

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

このコミットにおける主要な変更ファイルとコードスニペットは以下の通りです。

  1. src/pkg/runtime/defs_freebsd.go: MADV_FREE定数の追加。

    --- a/src/pkg/runtime/defs_freebsd.go
    +++ b/src/pkg/runtime/defs_freebsd.go
    @@ -38,6 +39,8 @@ const (
      	MAP_PRIVATE = C.MAP_PRIVATE
      	MAP_FIXED   = C.MAP_FIXED
     
    +	MADV_FREE = C.MADV_FREE
    +
      	SA_SIGINFO = C.SA_SIGINFO
      	SA_RESTART = C.SA_RESTART
      	SA_ONSTACK = C.SA_ONSTACK
    

    また、cgoコマンドの呼び出し方法がgo tool cgoに変更され、各アーキテクチャごとのヘッダファイル生成が明示されています。

  2. src/pkg/runtime/defs_freebsd_386.h, src/pkg/runtime/defs_freebsd_amd64.h, src/pkg/runtime/defs_freebsd_arm.h: 各アーキテクチャのヘッダファイルにMADV_FREEの定義(値は0x5)が追加されています。

  3. src/pkg/runtime/mem_freebsd.c: runtime·SysUnused関数がruntime·madviseを呼び出すように変更。

    --- a/src/pkg/runtime/mem_freebsd.c
    +++ b/src/pkg/runtime/mem_freebsd.c
    @@ -23,9 +23,7 @@ runtime·SysAlloc(uintptr n)
     void
     runtime·SysUnused(void *v, uintptr n)
     {
    -	USED(v);\n-	USED(n);\n-	// TODO(rsc): call madvise MADV_DONTNEED
    +	runtime·madvise(v, n, MADV_FREE);
     }
     
     void
    
  4. src/pkg/runtime/sys_freebsd_386.s: i386アーキテクチャ向けruntime·madviseのアセンブリ実装。

    --- a/src/pkg/runtime/sys_freebsd_386.s
    +++ b/src/pkg/runtime/sys_freebsd_386.s
    @@ -102,6 +102,13 @@ TEXT runtime·munmap(SB),7,$-4
      	MOVL	$0xf1, 0xf1  // crash
      	RET
      
    +TEXT runtime·madvise(SB),7,$-4
    +\tMOVL\t$75, AX\t// madvise
    +\tINT\t$0x80
    +\tJAE\t2(PC)
    +\tMOVL\t$0xf1, 0xf1  // crash
    +\tRET
    +\n
     TEXT runtime·setitimer(SB), 7, $-4
      	MOVL	$83, AX
      	INT	$0x80
    
  5. src/pkg/runtime/sys_freebsd_amd64.s: AMD64アーキテクチャ向けruntime·madviseのアセンブリ実装。

    --- a/src/pkg/runtime/sys_freebsd_amd64.s
    +++ b/src/pkg/runtime/sys_freebsd_amd64.s
    @@ -184,6 +184,17 @@ TEXT runtime·munmap(SB),7,$0
      	MOVL	$0xf1, 0xf1  // crash
      	RET
      
    +TEXT runtime·madvise(SB),7,$0
    +\tMOVQ\t8(SP), DI
    +\tMOVQ\t16(SP), SI
    +\tMOVQ\t24(SP), DX
    +\tMOVQ\t$75, AX\t// madvise
    +\tSYSCALL
    +\tCMPQ\tAX, $0xfffffffffffff001
    +\tJLS\t2(PC)
    +\tMOVL\t$0xf1, 0xf1  // crash
    +\tRET
    +\t\n
     TEXT runtime·sigaltstack(SB),7,$-8
      	MOVQ\tnew+8(SP), DI
      	MOVQ\told+16(SP), SI
    
  6. src/pkg/runtime/sys_freebsd_arm.s: ARMアーキテクチャ向けruntime·madviseのアセンブリ実装。

    --- a/src/pkg/runtime/sys_freebsd_arm.s
    +++ b/src/pkg/runtime/sys_freebsd_arm.s
    @@ -187,6 +187,15 @@ TEXT runtime·munmap(SB),7,$0
      	MOVW.CS R9, (R9)
      	RET
      
    +TEXT runtime·madvise(SB),7,$0
    +\tMOVW 0(FP), R0\t\t// arg 1 addr
    +\tMOVW 4(FP), R1\t\t// arg 2 len
    +\tMOVW 8(FP), R2\t\t// arg 3 flags
    +\tSWI $75
    +\tMOVW.CS $0, R9 // crash on syscall failure
    +\tMOVW.CS R9, (R9)
    +\tRET
    +\t\n
     TEXT runtime·sigaltstack(SB),7,$-8
      	MOVW new+0(FP), R0
      	MOVW old+4(FP), R1
    

コアとなるコードの解説

src/pkg/runtime/defs_freebsd.go およびヘッダファイル

このファイルは、GoランタイムがFreeBSD固有のシステムコールや定数を使用するための定義を含んでいます。MADV_FREE定数が追加されたことで、Goコードからmadviseシステムコールに渡すべき適切なフラグ値が利用可能になりました。これは、C言語のヘッダファイルからGoの定数として取り込むためのステップです。

src/pkg/runtime/mem_freebsd.c

このCファイルは、GoランタイムのFreeBSD固有のメモリ管理ロジックを実装しています。 runtime·SysUnused(void *v, uintptr n)関数は、Goのガベージコレクタがメモリ領域v(サイズn)がもはや使用されていないと判断したときに呼び出されます。 変更前は、この関数は単に引数を使用済みとしてマークし、// TODO(rsc): call madvise MADV_DONTNEEDというコメントがあるだけで、実際には何もメモリをOSに返却する処理を行っていませんでした。 変更後は、runtime·madvise(v, n, MADV_FREE);という行が追加されました。これにより、runtime·SysUnusedが呼び出されると、新しく実装されたruntime·madvise関数が、指定されたメモリ領域vとサイズn、そしてMADV_FREEフラグを引数として呼び出されます。これは、このメモリ領域がもはやGoランタイムによって積極的に使用されておらず、OSが自由に再利用できることをカーネルに通知する役割を果たします。

src/pkg/runtime/sys_freebsd_*.s (アセンブリファイル)

これらのファイルは、各CPUアーキテクチャ(i386, AMD64, ARM)におけるruntime·madvise関数のアセンブリ言語実装です。

  • TEXT runtime·madvise(SB),7,$-4 (i386):

    • MOVL $75, AX: システムコール番号75madvise)をAXレジスタにロードします。
    • INT $0x80: ソフトウェア割り込み0x80を発生させ、カーネルにシステムコールを要求します。
    • JAE 2(PC): システムコールが成功した場合(キャリーフラグがクリアされている場合)、次の命令をスキップします。
    • MOVL $0xf1, 0xf1 // crash: システムコールが失敗した場合、意図的にクラッシュさせます。これは、メモリ管理の失敗が致命的であることを示します。
    • RET: 関数から戻ります。
  • TEXT runtime·madvise(SB),7,$0 (AMD64):

    • MOVQ 8(SP), DI, MOVQ 16(SP), SI, MOVQ 24(SP), DX: Goの呼び出し規約に従ってスタックから引数(アドレス、長さ、フラグ)をそれぞれDI, SI, DXレジスタにロードします。これらはmadviseシステムコールの第一、第二、第三引数に対応します。
    • MOVQ $75, AX: システムコール番号75AXレジスタにロードします。
    • SYSCALL: syscall命令を実行し、カーネルにシステムコールを要求します。
    • CMPQ AX, $0xfffffffffffff001, JLS 2(PC): システムコールの戻り値(AXレジスタ)がエラーを示す値(通常は負の値)でないかチェックします。エラーでなければ次の命令をスキップします。
    • MOVL $0xf1, 0xf1 // crash: エラーの場合、クラッシュさせます。
    • RET: 関数から戻ります。
  • TEXT runtime·madvise(SB),7,$0 (ARM):

    • MOVW 0(FP), R0, MOVW 4(FP), R1, MOVW 8(FP), R2: スタックフレームポインタFPからのオフセットで引数(アドレス、長さ、フラグ)をそれぞれR0, R1, R2レジスタにロードします。これらはmadviseシステムコールの第一、第二、第三引数に対応します。
    • SWI $75: ソフトウェア割り込み75を発生させ、カーネルにシステムコールを要求します。
    • MOVW.CS $0, R9 // crash on syscall failure, MOVW.CS R9, (R9): システムコールが失敗した場合(条件コードがセットされている場合)、意図的にクラッシュさせます。
    • RET: 関数から戻ります。

これらのアセンブリコードは、GoランタイムがFreeBSD上でメモリをOSに返却するために必要な低レベルのインターフェースを提供し、Goアプリケーションのメモリ効率を大幅に改善します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • FreeBSDのmanページ
  • Go言語のソースコードリポジトリ (GitHub)
  • 一般的なUnix系OSのシステムコールに関する資料
  • アセンブリ言語(i386, AMD64, ARM)の呼び出し規約に関する資料
  • GoのIssueトラッカーやメーリングリストでの関連議論 (コミットメッセージに記載のgolang.org/cl/6850081など)

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

このコミットは、Go言語のランタイムにおけるFreeBSD環境でのメモリ管理に関する重要な改善を導入しています。具体的には、runtime.SysUnused関数のFreeBSD実装を追加し、これに必要なmadviseシステムコールのアセンブリレベルでの実装を提供します。これにより、Goのガベージコレクタが解放したメモリをオペレーティングシステムに適切に返却できるようになり、長期間実行されるGoプロセスにおけるメモリ使用量の肥大化(OOM発生)を防ぐことを目的としています。

コミット

commit 314fd624343eaa2d110f9b5b192a0a8f354d63ed
Author: John Graham-Cumming <jgc@jgc.org>
Date:   Sat Nov 24 15:55:19 2012 +1100

    runtime: implement runtime.SysUnused on FreeBSD
    
    madvise was missing so implement it in assembler. This change
    needs to be extended to the other BSD variantes (Net and Open)
    
    Without this change the scavenger will attempt to pass memory back
    to the operating system when it has become idle, but the memory is
    not returned and for long running Go processes the total memory used
    can grow until OOM occurs.
    
    I have only been able to test the code on FreeBSD AMD64. The ARM
    platforms needs testing.
    
    R=golang-dev, mikioh.mikioh, dave, jgc, minux.ma
    CC=golang-dev
    https://golang.org/cl/6850081

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

https://github.com/golang/go/commit/314fd624343eaa2d110f9b5b192a0a8f354d63ed

元コミット内容

Goランタイムにおいて、FreeBSD上でのruntime.SysUnusedを実装する。 madviseが欠落していたため、アセンブラで実装する。この変更は他のBSD派生(NetBSD、OpenBSD)にも拡張する必要がある。 この変更がないと、スカベンジャー(ガベージコレクタの一部)はアイドル状態になったメモリをオペレーティングシステムに返却しようとするが、メモリが返却されず、長期間実行されるGoプロセスでは総メモリ使用量が増大し、最終的にOOM(Out Of Memory)が発生する可能性がある。 このコードはFreeBSD AMD64でのみテスト済み。ARMプラットフォームでのテストが必要。

変更の背景

Go言語のランタイムは、ガベージコレクタ(GC)によってメモリを管理します。GCは不要になったメモリを回収しますが、回収したメモリをすぐにオペレーティングシステム(OS)に返却するとは限りません。Goランタイムは、将来のメモリ割り当てのために回収したメモリを内部的に保持することがよくあります。しかし、長期間にわたって使用されないメモリ領域がある場合、それをOSに返却することで、システム全体のメモリ効率を向上させることができます。

このコミットが作成された2012年当時、GoのランタイムはFreeBSD環境において、ガベージコレクタが解放したメモリをOSに返却するメカニズムが不完全でした。具体的には、メモリ領域の利用状況をOSに助言するmadviseシステムコールがGoランタイムから適切に呼び出されていなかったため、Goプロセスが使用しなくなったメモリがOSに解放されず、プロセス全体のメモリフットプリントが時間とともに増大し続ける問題がありました。これは特に、長時間稼働するサーバーアプリケーションなどで深刻な問題となり、最終的にはシステム全体のメモリ不足を引き起こす可能性がありました。

この問題に対処するため、Goランタイムの「スカベンジャー」(ガベージコレクタの一部で、OSへのメモリ返却を担当するコンポーネント)が、アイドル状態のメモリをOSに返却できるように、FreeBSD固有のmadviseシステムコールをGoランタイムから呼び出せるようにする必要がありました。

前提知識の解説

1. ガベージコレクション (Garbage Collection, GC) とメモリ管理

Go言語は自動メモリ管理、すなわちガベージコレクションを採用しています。プログラマが手動でメモリを解放する必要がなく、GCが不要になったオブジェクトを自動的に検出し、そのメモリを回収します。GoのGCは、回収したメモリをすぐにOSに返却するのではなく、将来の割り当てのためにヒープ内に保持することがあります。しかし、一定期間使用されないメモリは、OSに返却することでシステム全体のメモリ効率を高めることができます。

2. madviseシステムコール

madviseは、Unix系OS(Linux, FreeBSDなど)で利用可能なシステムコールの一つです。これは、プロセスが特定のメモリ領域をどのように使用するつもりであるかをカーネルに「助言 (advise)」するために使用されます。カーネルはこの助言を参考に、メモリ管理の最適化を行います。 madviseには様々なフラグがあり、このコミットで特に重要なのはMADV_FREE(または類似の機能を持つフラグ)です。MADV_FREEは、指定されたメモリ領域がもはやプロセスによって積極的に使用されておらず、カーネルがそのページを解放してもよいことを示します。これにより、カーnelは必要に応じてそのメモリを他のプロセスに再割り当てしたり、スワップアウトしたりすることができます。

3. runtime.SysUnused

Goランタイムには、OS固有のメモリ管理機能と連携するための抽象化された関数群があります。runtime.SysUnusedはその一つで、Goのガベージコレクタが「このメモリ領域はもう使わないので、OSに返却しても構わない」と判断した際に呼び出される関数です。この関数は、各OSの実装に応じて、madviseのようなシステムコールを呼び出して実際のメモリ返却処理を行います。

4. システムコールとアセンブリ

システムコールは、ユーザー空間のプログラムがカーネルの機能を利用するためのインターフェースです。通常、C言語のライブラリ関数(例: madvise()) を通じて呼び出されますが、Goランタイムのような低レベルのコードでは、OSのシステムコールを直接アセンブリ言語で呼び出すことがあります。これは、パフォーマンスの最適化、特定のレジスタの使用、またはCgo(GoとCの相互運用機能)を介さずにOSの機能に直接アクセスする必要がある場合に用いられます。各アーキテクチャ(x86, AMD64, ARMなど)とOS(Linux, FreeBSDなど)によって、システムコールの呼び出し規約(引数の渡し方、システムコール番号など)が異なるため、それぞれに対応したアセンブリコードが必要になります。

5. FreeBSDのシステムコール規約

FreeBSDにおけるシステムコールの呼び出し規約は、アーキテクチャによって異なります。

  • i386 (32-bit): システムコール番号は%eaxレジスタに格納され、引数はスタックにプッシュされます。int $0x80命令でカーネルにトラップします。
  • AMD64 (64-bit): システムコール番号は%raxレジスタに格納され、引数は特定のレジスタ(%rdi, %rsi, %rdx, %rcx, %r8, %r9)に格納されます。syscall命令でカーネルにトラップします。
  • ARM: システムコール番号はR7レジスタに格納され、引数はR0からR6レジスタに格納されます。SWI(Software Interrupt)命令でカーネルにトラップします。

madviseシステムコールは、FreeBSDではシステムコール番号75に割り当てられています。

技術的詳細

このコミットの主要な技術的課題は、FreeBSD環境でGoランタイムがmadviseシステムコールを適切に呼び出せるようにすることでした。

  1. madviseシステムコールの欠落: 既存のFreeBSD向けGoランタイムには、madviseシステムコールを呼び出すためのラッパー関数やアセンブリコードが欠落していました。これにより、runtime.SysUnusedが実質的に何も行わない状態になっていました。

  2. アセンブリでの実装: madviseは低レベルのメモリ操作に関わるため、Goランタイムはこれを直接システムコールとして呼び出す必要があります。これは、Goの標準ライブラリが提供するCgoを介したCライブラリの呼び出しではなく、OSのシステムコールを直接アセンブリ言語で実装することを意味します。これにより、オーバーヘッドを最小限に抑え、OSとのより密接な連携を可能にします。

  3. アーキテクチャ固有の実装: システムコールの呼び出し規約はCPUアーキテクチャ(i386, AMD64, ARM)によって異なるため、それぞれのアーキテクチャに対応したアセンブリコードを記述する必要があります。このコミットでは、sys_freebsd_386.s, sys_freebsd_amd64.s, sys_freebsd_arm.sにそれぞれruntime·madvise関数が追加されています。

  4. defs_freebsd.goの更新: madviseシステムコールで使用するMADV_FREEフラグの定数(FreeBSDでは0x5)をGoランタイムが認識できるように、src/pkg/runtime/defs_freebsd.goおよび対応するヘッダファイル(defs_freebsd_386.h, defs_freebsd_amd64.h, defs_freebsd_arm.h)にMADV_FREEの定義が追加されています。これにより、GoコードからMADV_FREE定数を使用できるようになります。

  5. mem_freebsd.cの修正: runtime·SysUnused関数が、コメントアウトされていたTODO(rsc): call madvise MADV_DONTNEEDの代わりに、新しく実装されたruntime·madviseMADV_FREEフラグ付きで呼び出すように変更されました。これにより、Goのスカベンジャーが解放したメモリをOSに返却する実際の処理が実行されるようになります。

この変更により、Goプロセスが使用しなくなったメモリがFreeBSDカーネルに適切に通知され、カーネルがそのメモリを再利用できるようになるため、Goアプリケーションのメモリフットプリントがより効率的に管理されるようになります。

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

このコミットにおける主要な変更ファイルとコードスニペットは以下の通りです。

  1. src/pkg/runtime/defs_freebsd.go: MADV_FREE定数の追加。

    --- a/src/pkg/runtime/defs_freebsd.go
    +++ b/src/pkg/runtime/defs_freebsd.go
    @@ -38,6 +39,8 @@ const (
      	MAP_PRIVATE = C.MAP_PRIVATE
      	MAP_FIXED   = C.MAP_FIXED
     
    +	MADV_FREE = C.MADV_FREE
    +
      	SA_SIGINFO = C.SA_SIGINFO
      	SA_RESTART = C.SA_RESTART
      	SA_ONSTACK = C.SA_ONSTACK
    

    また、cgoコマンドの呼び出し方法がgo tool cgoに変更され、各アーキテクチャごとのヘッダファイル生成が明示されています。

  2. src/pkg/runtime/defs_freebsd_386.h, src/pkg/runtime/defs_freebsd_amd64.h, src/pkg/runtime/defs_freebsd_arm.h: 各アーキテクチャのヘッダファイルにMADV_FREEの定義(値は0x5)が追加されています。

  3. src/pkg/runtime/mem_freebsd.c: runtime·SysUnused関数がruntime·madviseを呼び出すように変更。

    --- a/src/pkg/runtime/mem_freebsd.c
    +++ b/src/pkg/runtime/mem_freebsd.c
    @@ -23,9 +23,7 @@ runtime·SysAlloc(uintptr n)
     void
     runtime·SysUnused(void *v, uintptr n)
     {
    -	USED(v);\n-	USED(n);\n-	// TODO(rsc): call madvise MADV_DONTNEED
    +	runtime·madvise(v, n, MADV_FREE);
     }
     
     void
    
  4. src/pkg/runtime/sys_freebsd_386.s: i386アーキテクチャ向けruntime·madviseのアセンブリ実装。

    --- a/src/pkg/runtime/sys_freebsd_386.s
    +++ b/src/pkg/runtime/sys_freebsd_386.s
    @@ -102,6 +102,13 @@ TEXT runtime·munmap(SB),7,$-4
      	MOVL	$0xf1, 0xf1  // crash
      	RET
      
    +TEXT runtime·madvise(SB),7,$-4
    +\tMOVL\t$75, AX\t// madvise
    +\tINT\t$0x80
    +\tJAE\t2(PC)
    +\tMOVL\t$0xf1, 0xf1  // crash
    +\tRET
    +\n
     TEXT runtime·setitimer(SB), 7, $-4
      	MOVL	$83, AX
      	INT	$0x80
    
  5. src/pkg/runtime/sys_freebsd_amd64.s: AMD64アーキテクチャ向けruntime·madviseのアセンブリ実装。

    --- a/src/pkg/runtime/sys_freebsd_amd64.s
    +++ b/src/pkg/runtime/sys_freebsd_amd64.s
    @@ -184,6 +184,17 @@ TEXT runtime·munmap(SB),7,$0
      	MOVL	$0xf1, 0xf1  // crash
      	RET
      
    +TEXT runtime·madvise(SB),7,$0
    +\tMOVQ\t8(SP), DI
    +\tMOVQ\t16(SP), SI
    +\tMOVQ\t24(SP), DX
    +\tMOVQ\t$75, AX\t// madvise
    +\tSYSCALL
    +\tCMPQ\tAX, $0xfffffffffffff001
    +\tJLS\t2(PC)
    +\tMOVL\t$0xf1, 0xf1  // crash
    +\tRET
    +\t\n
     TEXT runtime·sigaltstack(SB),7,$-8
      	MOVQ\tnew+8(SP), DI
      	MOVQ\told+16(SP), SI
    
  6. src/pkg/runtime/sys_freebsd_arm.s: ARMアーキテクチャ向けruntime·madviseのアセンブリ実装。

    --- a/src/pkg/runtime/sys_freebsd_arm.s
    +++ b/src/pkg/runtime/sys_freebsd_arm.s
    @@ -187,6 +187,15 @@ TEXT runtime·munmap(SB),7,$0
      	MOVW.CS R9, (R9)
      	RET
      
    +TEXT runtime·madvise(SB),7,$0
    +\tMOVW 0(FP), R0\t\t// arg 1 addr
    +\tMOVW 4(FP), R1\t\t// arg 2 len
    +\tMOVW 8(FP), R2\t\t// arg 3 flags
    +\tSWI $75
    +\tMOVW.CS $0, R9 // crash on syscall failure
    +\tMOVW.CS R9, (R9)
    +\tRET
    +\t\n
     TEXT runtime·sigaltstack(SB),7,$-8
      	MOVW new+0(FP), R0
      	MOVW old+4(FP), R1
    

コアとなるコードの解説

src/pkg/runtime/defs_freebsd.go およびヘッダファイル

このファイルは、GoランタイムがFreeBSD固有のシステムコールや定数を使用するための定義を含んでいます。MADV_FREE定数が追加されたことで、Goコードからmadviseシステムコールに渡すべき適切なフラグ値が利用可能になりました。これは、C言語のヘッダファイルからGoの定数として取り込むためのステップです。

src/pkg/runtime/mem_freebsd.c

このCファイルは、GoランタイムのFreeBSD固有のメモリ管理ロジックを実装しています。 runtime·SysUnused(void *v, uintptr n)関数は、Goのガベージコレクタがメモリ領域v(サイズn)がもはや使用されていないと判断したときに呼び出されます。 変更前は、この関数は単に引数を使用済みとしてマークし、// TODO(rsc): call madvise MADV_DONTNEEDというコメントがあるだけで、実際には何もメモリをOSに返却する処理を行っていませんでした。 変更後は、runtime·madvise(v, n, MADV_FREE);という行が追加されました。これにより、runtime·SysUnusedが呼び出されると、新しく実装されたruntime·madvise関数が、指定されたメモリ領域vとサイズn、そしてMADV_FREEフラグを引数として呼び出されます。これは、このメモリ領域がもはやGoランタイムによって積極的に使用されておらず、OSが自由に再利用できることをカーネルに通知する役割を果たします。

src/pkg/runtime/sys_freebsd_*.s (アセンブリファイル)

これらのファイルは、各CPUアーキテクチャ(i386, AMD64, ARM)におけるruntime·madvise関数のアセンブリ言語実装です。

  • TEXT runtime·madvise(SB),7,$-4 (i386):

    • MOVL $75, AX: システムコール番号75madvise)をAXレジスタにロードします。
    • INT $0x80: ソフトウェア割り込み0x80を発生させ、カーネルにシステムコールを要求します。
    • JAE 2(PC): システムコールが成功した場合(キャリーフラグがクリアされている場合)、次の命令をスキップします。
    • MOVL $0xf1, 0xf1 // crash: システムコールが失敗した場合、意図的にクラッシュさせます。これは、メモリ管理の失敗が致命的であることを示します。
    • RET: 関数から戻ります。
  • TEXT runtime·madvise(SB),7,$0 (AMD64):

    • MOVQ 8(SP), DI, MOVQ 16(SP), SI, MOVQ 24(SP), DX: Goの呼び出し規約に従ってスタックから引数(アドレス、長さ、フラグ)をそれぞれDI, SI, DXレジスタにロードします。これらはmadviseシステムコールの第一、第二、第三引数に対応します。
    • MOVQ $75, AX: システムコール番号75AXレジスタにロードします。
    • SYSCALL: syscall命令を実行し、カーネルにシステムコールを要求します。
    • CMPQ AX, $0xfffffffffffff001, JLS 2(PC): システムコールの戻り値(AXレジスタ)がエラーを示す値(通常は負の値)でないかチェックします。エラーでなければ次の命令をスキップします。
    • MOVL $0xf1, 0xf1 // crash: エラーの場合、クラッシュさせます。
    • RET: 関数から戻ります。
  • TEXT runtime·madvise(SB),7,$0 (ARM):

    • MOVW 0(FP), R0, MOVW 4(FP), R1, MOVW 8(FP), R2: スタックフレームポインタFPからのオフセットで引数(アドレス、長さ、フラグ)をそれぞれR0, R1, R2レジスタにロードします。これらはmadviseシステムコールの第一、第二、第三引数に対応します。
    • SWI $75: ソフトウェア割り込み75を発生させ、カーネルにシステムコールを要求します。
    • MOVW.CS $0, R9 // crash on syscall failure, MOVW.CS R9, (R9): システムコールが失敗した場合(条件コードがセットされている場合)、意図的にクラッシュさせます。
    • RET: 関数から戻ります。

これらのアセンブリコードは、GoランタイムがFreeBSD上でメモリをOSに返却するために必要な低レベルのインターフェースを提供し、Goアプリケーションのメモリ効率を大幅に改善します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • FreeBSDのmanページ
  • Go言語のソースコードリポジトリ (GitHub)
  • 一般的なUnix系OSのシステムコールに関する資料
  • アセンブリ言語(i386, AMD64, ARM)の呼び出し規約に関する資料
  • GoのIssueトラッカーやメーリングリストでの関連議論 (コミットメッセージに記載のgolang.org/cl/6850081など)
  • Web検索結果: "Go runtime SysUnused FreeBSD madvise" (特に、MADV_FREEMADV_DONTNEEDの違い、Go 1.12での変更点、GODEBUG=madvdontneed=1オプションに関する情報が参考になりました。)

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

このコミットは、Go言語のランタイムにおけるFreeBSD環境でのメモリ管理に関する重要な改善を導入しています。具体的には、runtime.SysUnused関数のFreeBSD実装を追加し、これに必要なmadviseシステムコールのアセンブリレベルでの実装を提供します。これにより、Goのガベージコレクタが解放したメモリをオペレーティングシステムに適切に返却できるようになり、長期間実行されるGoプロセスにおけるメモリ使用量の肥大化(OOM発生)を防ぐことを目的としています。

コミット

commit 314fd624343eaa2d110f9b5b192a0a8f354d63ed
Author: John Graham-Cumming <jgc@jgc.org>
Date:   Sat Nov 24 15:55:19 2012 +1100

    runtime: implement runtime.SysUnused on FreeBSD
    
    madvise was missing so implement it in assembler. This change
    needs to be extended to the other BSD variantes (Net and Open)
    
    Without this change the scavenger will attempt to pass memory back
    to the operating system when it has become idle, but the memory is
    not returned and for long running Go processes the total memory used
    can grow until OOM occurs.
    
    I have only been able to test the code on FreeBSD AMD64. The ARM
    platforms needs testing.
    
    R=golang-dev, mikioh.mikioh, dave, jgc, minux.ma
    CC=golang-dev
    https://golang.org/cl/6850081

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

https://github.com/golang/go/commit/314fd624343eaa2d110f9b5b192a0a8f354d63ed

元コミット内容

Goランタイムにおいて、FreeBSD上でのruntime.SysUnusedを実装する。 madviseが欠落していたため、アセンブラで実装する。この変更は他のBSD派生(NetBSD、OpenBSD)にも拡張する必要がある。 この変更がないと、スカベンジャー(ガベージコレクタの一部)はアイドル状態になったメモリをオペレーティングシステムに返却しようとするが、メモリが返却されず、長期間実行されるGoプロセスでは総メモリ使用量が増大し、最終的にOOM(Out Of Memory)が発生する可能性がある。 このコードはFreeBSD AMD64でのみテスト済み。ARMプラットフォームでのテストが必要。

変更の背景

Go言語のランタイムは、ガベージコレクタ(GC)によってメモリを管理します。GCは不要になったメモリを回収しますが、回収したメモリをすぐにオペレーティングシステム(OS)に返却するとは限りません。Goランタイムは、将来のメモリ割り当てのために回収したメモリを内部的に保持することがよくあります。しかし、長期間にわたって使用されないメモリ領域がある場合、それをOSに返却することで、システム全体のメモリ効率を向上させることができます。

このコミットが作成された2012年当時、GoのランタイムはFreeBSD環境において、ガベージコレクタが解放したメモリをOSに返却するメカニズムが不完全でした。具体的には、メモリ領域の利用状況をOSに助言するmadviseシステムコールがGoランタイムから適切に呼び出されていなかったため、Goプロセスが使用しなくなったメモリがOSに解放されず、プロセス全体のメモリフットプリントが時間とともに増大し続ける問題がありました。これは特に、長時間稼働するサーバーアプリケーションなどで深刻な問題となり、最終的にはシステム全体のメモリ不足を引き起こす可能性がありました。

この問題に対処するため、Goランタイムの「スカベンジャー」(ガベージコレクタの一部で、OSへのメモリ返却を担当するコンポーネント)が、アイドル状態のメモリをOSに返却できるように、FreeBSD固有のmadviseシステムコールをGoランタイムから呼び出せるようにする必要がありました。

前提知識の解説

1. ガベージコレクション (Garbage Collection, GC) とメモリ管理

Go言語は自動メモリ管理、すなわちガベージコレクションを採用しています。プログラマが手動でメモリを解放する必要がなく、GCが不要になったオブジェクトを自動的に検出し、そのメモリを回収します。GoのGCは、回収したメモリをすぐにOSに返却するのではなく、将来の割り当てのためにヒープ内に保持することがあります。しかし、一定期間使用されないメモリは、OSに返却することでシステム全体のメモリ効率を高めることができます。

2. madviseシステムコール

madviseは、Unix系OS(Linux, FreeBSDなど)で利用可能なシステムコールの一つです。これは、プロセスが特定のメモリ領域をどのように使用するつもりであるかをカーネルに「助言 (advise)」するために使用されます。カーネルはこの助言を参考に、メモリ管理の最適化を行います。 madviseには様々なフラグがあり、このコミットで特に重要なのはMADV_FREEです。MADV_FREEは、指定されたメモリ領域がもはやプロセスによって積極的に使用されておらず、カーネルがそのページを解放してもよいことを示します。これにより、カーネルは必要に応じてそのメモリを他のプロセスに再割り当てしたり、スワップアウトしたりすることができます。

FreeBSDでは、Goランタイムは通常MADV_FREEフラグをmadviseシステムコールと共に使用します。これは、メモリページがもはや使用されていないが、システムがメモリ不足になったときに遅延して再利用できることをカーネルに助言します。このアプローチは、カーネルがまだメモリを再利用していない場合に、その後のアクセスで即座にページフォルトが発生するのを避けるため、一般的に効率的です。

歴史的に、LinuxではMADV_DONTNEEDが使用されていました。これはページを即座に破棄し、次にアクセスしたときにページフォルトを引き起こします。しかし、Go 1.12ではLinuxもMADV_FREEを使用するように変更され、FreeBSDのようなBSDシステムとの動作が統一されました。

3. runtime.SysUnused

Goランタイムには、OS固有のメモリ管理機能と連携するための抽象化された関数群があります。runtime.SysUnusedはその一つで、Goのガベージコレクタが「このメモリ領域はもう使わないので、OSに返却しても構わない」と判断した際に呼び出される関数です。この関数は、各OSの実装に応じて、madviseのようなシステムコールを呼び出して実際のメモリ返却処理を行います。

4. システムコールとアセンブリ

システムコールは、ユーザー空間のプログラムがカーネルの機能を利用するためのインターフェースです。通常、C言語のライブラリ関数(例: madvise()) を通じて呼び出されますが、Goランタイムのような低レベルのコードでは、OSのシステムコールを直接アセンブリ言語で呼び出すことがあります。これは、パフォーマンスの最適化、特定のレジスタの使用、またはCgo(GoとCの相互運用機能)を介さずにOSの機能に直接アクセスする必要がある場合に用いられます。各アーキテクチャ(x86, AMD64, ARMなど)とOS(Linux, FreeBSDなど)によって、システムコールの呼び出し規約(引数の渡し方、システムコール番号など)が異なるため、それぞれに対応したアセンブリコードが必要になります。

5. FreeBSDのシステムコール規約

FreeBSDにおけるシステムコールの呼び出し規約は、アーキテクチャによって異なります。

  • i386 (32-bit): システムコール番号は%eaxレジスタに格納され、引数はスタックにプッシュされます。int $0x80命令でカーネルにトラップします。
  • AMD64 (64-bit): システムコール番号は%raxレジスタに格納され、引数は特定のレジスタ(%rdi, %rsi, %rdx, %rcx, %r8, %r9)に格納されます。syscall命令でカーネルにトラップします。
  • ARM: システムコール番号はR7レジスタに格納され、引数はR0からR6レジスタに格納されます。SWI(Software Interrupt)命令でカーネルにトラップします。

madviseシステムコールは、FreeBSDではシステムコール番号75に割り当てられています。

技術的詳細

このコミットの主要な技術的課題は、FreeBSD環境でGoランタイムがmadviseシステムコールを適切に呼び出せるようにすることでした。

  1. madviseシステムコールの欠落: 既存のFreeBSD向けGoランタイムには、madviseシステムコールを呼び出すためのラッパー関数やアセンブリコードが欠落していました。これにより、runtime.SysUnusedが実質的に何も行わない状態になっていました。

  2. アセンブリでの実装: madviseは低レベルのメモリ操作に関わるため、Goランタイムはこれを直接システムコールとして呼び出す必要があります。これは、Goの標準ライブラリが提供するCgoを介したCライブラリの呼び出しではなく、OSのシステムコールを直接アセンブリ言語で実装することを意味します。これにより、オーバーヘッドを最小限に抑え、OSとのより密接な連携を可能にします。

  3. アーキテクチャ固有の実装: システムコールの呼び出し規約はCPUアーキテクチャ(i386, AMD64, ARM)によって異なるため、それぞれのアーキテクチャに対応したアセンブリコードを記述する必要があります。このコミットでは、sys_freebsd_386.s, sys_freebsd_amd64.s, sys_freebsd_arm.sにそれぞれruntime·madvise関数が追加されています。

  4. defs_freebsd.goの更新: madviseシステムコールで使用するMADV_FREEフラグの定数(FreeBSDでは0x5)をGoランタイムが認識できるように、src/pkg/runtime/defs_freebsd.goおよび対応するヘッダファイル(defs_freebsd_386.h, defs_freebsd_amd64.h, defs_freebsd_arm.h)にMADV_FREEの定義が追加されています。これにより、GoコードからMADV_FREE定数を使用できるようになります。

  5. mem_freebsd.cの修正: runtime·SysUnused関数が、コメントアウトされていたTODO(rsc): call madvise MADV_DONTNEEDの代わりに、新しく実装されたruntime·madviseMADV_FREEフラグ付きで呼び出すように変更されました。これにより、Goのスカベンジャーが解放したメモリをOSに返却する実際の処理が実行されるようになります。

この変更により、Goプロセスが使用しなくなったメモリがFreeBSDカーネルに適切に通知され、カーネルがそのメモリを再利用できるようになるため、Goアプリケーションのメモリフットプリントがより効率的に管理されるようになります。

なお、MADV_FREEはメモリがOSに即座に返却されるわけではないため、報告されるResident Set Size (RSS) が高くなる可能性があります。即座のRSS削減が重要な状況、特にコンテナ環境では、GODEBUG=madvdontneed=1という環境変数を設定することで、MADV_DONTNEEDの動作に戻すことが可能ですが、これは主にLinuxの文脈で議論されることが多いです。

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

このコミットにおける主要な変更ファイルとコードスニペットは以下の通りです。

  1. src/pkg/runtime/defs_freebsd.go: MADV_FREE定数の追加。

    --- a/src/pkg/runtime/defs_freebsd.go
    +++ b/src/pkg/runtime/defs_freebsd.go
    @@ -38,6 +39,8 @@ const (
      	MAP_PRIVATE = C.MAP_PRIVATE
      	MAP_FIXED   = C.MAP_FIXED
     
    +	MADV_FREE = C.MADV_FREE
    +
      	SA_SIGINFO = C.SA_SIGINFO
      	SA_RESTART = C.SA_RESTART
      	SA_ONSTACK = C.SA_ONSTACK
    

    また、cgoコマンドの呼び出し方法がgo tool cgoに変更され、各アーキテクチャごとのヘッダファイル生成が明示されています。

  2. src/pkg/runtime/defs_freebsd_386.h, src/pkg/runtime/defs_freebsd_amd64.h, src/pkg/runtime/defs_freebsd_arm.h: 各アーキテクチャのヘッダファイルにMADV_FREEの定義(値は0x5)が追加されています。

  3. src/pkg/runtime/mem_freebsd.c: runtime·SysUnused関数がruntime·madviseを呼び出すように変更。

    --- a/src/pkg/runtime/mem_freebsd.c
    +++ b/src/pkg/runtime/mem_freebsd.c
    @@ -23,9 +23,7 @@ runtime·SysAlloc(uintptr n)
     void
     runtime·SysUnused(void *v, uintptr n)
     {
    -	USED(v);\n-	USED(n);\n-	// TODO(rsc): call madvise MADV_DONTNEED
    +	runtime·madvise(v, n, MADV_FREE);
     }
     
     void
    
  4. src/pkg/runtime/sys_freebsd_386.s: i386アーキテクチャ向けruntime·madviseのアセンブリ実装。

    --- a/src/pkg/runtime/sys_freebsd_386.s
    +++ b/src/pkg/runtime/sys_freebsd_386.s
    @@ -102,6 +102,13 @@ TEXT runtime·munmap(SB),7,$-4
      	MOVL	$0xf1, 0xf1  // crash
      	RET
      
    +TEXT runtime·madvise(SB),7,$-4
    +\tMOVL\t$75, AX\t// madvise
    +\tINT\t$0x80
    +\tJAE\t2(PC)
    +\tMOVL\t$0xf1, 0xf1  // crash
    +\tRET
    +\n
     TEXT runtime·setitimer(SB), 7, $-4
      	MOVL	$83, AX
      	INT	$0x80
    
  5. src/pkg/runtime/sys_freebsd_amd64.s: AMD64アーキテクチャ向けruntime·madviseのアセンブリ実装。

    --- a/src/pkg/runtime/sys_freebsd_amd64.s
    +++ b/src/pkg/runtime/sys_freebsd_amd64.s
    @@ -184,6 +184,17 @@ TEXT runtime·munmap(SB),7,$0
      	MOVL	$0xf1, 0xf1  // crash
      	RET
      
    +TEXT runtime·madvise(SB),7,$0
    +\tMOVQ\t8(SP), DI
    +\tMOVQ\t16(SP), SI
    +\tMOVQ\t24(SP), DX
    +\tMOVQ\t$75, AX\t// madvise
    +\tSYSCALL
    +\tCMPQ\tAX, $0xfffffffffffff001
    +\tJLS\t2(PC)
    +\tMOVL\t$0xf1, 0xf1  // crash
    +\tRET
    +\t\n
     TEXT runtime·sigaltstack(SB),7,$-8
      	MOVQ\tnew+8(SP), DI
      	MOVQ\told+16(SP), SI
    
  6. src/pkg/runtime/sys_freebsd_arm.s: ARMアーキテクチャ向けruntime·madviseのアセンブリ実装。

    --- a/src/pkg/runtime/sys_freebsd_arm.s
    +++ b/src/pkg/runtime/sys_freebsd_arm.s
    @@ -187,6 +187,15 @@ TEXT runtime·munmap(SB),7,$0
      	MOVW.CS R9, (R9)
      	RET
      
    +TEXT runtime·madvise(SB),7,$0
    +\tMOVW 0(FP), R0\t\t// arg 1 addr
    +\tMOVW 4(FP), R1\t\t// arg 2 len
    +\tMOVW 8(FP), R2\t\t// arg 3 flags
    +\tSWI $75
    +\tMOVW.CS $0, R9 // crash on syscall failure
    +\tMOVW.CS R9, (R9)
    +\tRET
    +\t\n
     TEXT runtime·sigaltstack(SB),7,$-8
      	MOVW new+0(FP), R0
      	MOVW old+4(FP), R1
    

コアとなるコードの解説

src/pkg/runtime/defs_freebsd.go およびヘッダファイル

このファイルは、GoランタイムがFreeBSD固有のシステムコールや定数を使用するための定義を含んでいます。MADV_FREE定数が追加されたことで、Goコードからmadviseシステムコールに渡すべき適切なフラグ値が利用可能になりました。これは、C言語のヘッダファイルからGoの定数として取り込むためのステップです。

src/pkg/runtime/mem_freebsd.c

このCファイルは、GoランタイムのFreeBSD固有のメモリ管理ロジックを実装しています。 runtime·SysUnused(void *v, uintptr n)関数は、Goのガベージコレクタがメモリ領域v(サイズn)がもはや使用されていないと判断したときに呼び出されます。 変更前は、この関数は単に引数を使用済みとしてマークし、// TODO(rsc): call madvise MADV_DONTNEEDというコメントがあるだけで、実際には何もメモリをOSに返却する処理を行っていませんでした。 変更後は、runtime·madvise(v, n, MADV_FREE);という行が追加されました。これにより、runtime·SysUnusedが呼び出されると、新しく実装されたruntime·madvise関数が、指定されたメモリ領域vとサイズn、そしてMADV_FREEフラグを引数として呼び出されます。これは、このメモリ領域がもはやGoランタイムによって積極的に使用されておらず、OSが自由に再利用できることをカーネルに通知する役割を果たします。

src/pkg/runtime/sys_freebsd_*.s (アセンブリファイル)

これらのファイルは、各CPUアーキテクチャ(i386, AMD64, ARM)におけるruntime·madvise関数のアセンブリ言語実装です。

  • TEXT runtime·madvise(SB),7,$-4 (i386):

    • MOVL $75, AX: システムコール番号75madvise)をAXレジスタにロードします。
    • INT $0x80: ソフトウェア割り込み0x80を発生させ、カーネルにシステムコールを要求します。
    • JAE 2(PC): システムコールが成功した場合(キャリーフラグがクリアされている場合)、次の命令をスキップします。
    • MOVL $0xf1, 0xf1 // crash: システムコールが失敗した場合、意図的にクラッシュさせます。これは、メモリ管理の失敗が致命的であることを示します。
    • RET: 関数から戻ります。
  • TEXT runtime·madvise(SB),7,$0 (AMD64):

    • MOVQ 8(SP), DI, MOVQ 16(SP), SI, MOVQ 24(SP), DX: Goの呼び出し規約に従ってスタックから引数(アドレス、長さ、フラグ)をそれぞれDI, SI, DXレジスタにロードします。これらはmadviseシステムコールの第一、第二、第三引数に対応します。
    • MOVQ $75, AX: システムコール番号75AXレジスタにロードします。
    • SYSCALL: syscall命令を実行し、カーネルにシステムコールを要求します。
    • CMPQ AX, $0xfffffffffffff001, JLS 2(PC): システムコールの戻り値(AXレジスタ)がエラーを示す値(通常は負の値)でないかチェックします。エラーでなければ次の命令をスキップします。
    • MOVL $0xf1, 0xf1 // crash: エラーの場合、クラッシュさせます。
    • RET: 関数から戻ります。
  • TEXT runtime·madvise(SB),7,$0 (ARM):

    • MOVW 0(FP), R0, MOVW 4(FP), R1, MOVW 8(FP), R2: スタックフレームポインタFPからのオフセットで引数(アドレス、長さ、フラグ)をそれぞれR0, R1, R2レジスタにロードします。これらはmadviseシステムコールの第一、第二、第三引数に対応します。
    • SWI $75: ソフトウェア割り込み75を発生させ、カーネルにシステムコールを要求します。
    • MOVW.CS $0, R9 // crash on syscall failure, MOVW.CS R9, (R9): システムコールが失敗した場合(条件コードがセットされている場合)、意図的にクラッシュさせます。
    • RET: 関数から戻ります。

これらのアセンブリコードは、GoランタイムがFreeBSD上でメモリをOSに返却するために必要な低レベルのインターフェースを提供し、Goアプリケーションのメモリ効率を大幅に改善します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • FreeBSDのmanページ
  • Go言語のソースコードリポジトリ (GitHub)
  • 一般的なUnix系OSのシステムコールに関する資料
  • アセンブリ言語(i386, AMD64, ARM)の呼び出し規約に関する資料
  • GoのIssueトラッカーやメーリングリストでの関連議論 (コミットメッセージに記載のgolang.org/cl/6850081など)
  • Web検索結果: "Go runtime SysUnused FreeBSD madvise" (特に、MADV_FREEMADV_DONTNEEDの違い、Go 1.12での変更点、GODEBUG=madvdontneed=1オプションに関する情報が参考になりました。)