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

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

このコミットは、Go言語のPlan 9オペレーティングシステム向けランタイムおよびsyscallパッケージにおける重要な修正を導入しています。具体的には、runtime.entersyscall()モードでシステムコールが実行される際に、エラー文字列(errstr)の取得方法を変更し、アロケータが利用できない状況下でのクラッシュを防ぐことを目的としています。以前のコミット(231af8ac63aa)によってm->mcachenilに設定されるようになったため、システムコール中に新しいバッファを割り当てるsyscall.errstrが使用できなくなりました。この変更は、この問題を解決するために、M(Machine)ごとに事前に割り当てられたストレージを使用してエラー文字列を保持し、runtime.findnullでその長さを計算するように修正しています。

コミット

  • コミットハッシュ: a566deace1d96cd517e79227937e3036baca7ee2
  • Author: Akshat Kumar seed@mail.nanosouffle.net
  • Date: Fri Mar 8 00:54:44 2013 +0100

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

https://github.com/golang/go/commit/a566deace1d96cd517e79227937e3036baca7ee2

元コミット内容

    syscall: Plan 9: use lightweight errstr in entersyscall mode
    
    Change 231af8ac63aa (CL 7314062) made runtime.enteryscall()
    set m->mcache = nil, which means that we can no longer use
    syscall.errstr in syscall.Syscall and syscall.Syscall6, since it
    requires a new buffer to be allocated for holding the error string.
    Instead, we use pre-allocated per-M storage to hold error strings
    from syscalls made while in entersyscall mode, and call
    runtime.findnull to calculate the lengths.
    
    Fixes #4994.
    
    R=rsc, rminnich, ality, dvyukov, rminnich, r
    CC=golang-dev
    https://golang.org/cl/7567043

変更の背景

この変更の背景には、Goランタイムのガベージコレクタ(GC)とシステムコール(syscall)の連携に関する特定の課題があります。Goランタイムは、ガベージコレクタが安全に動作するために、GoルーチンがGoコードを実行している間はGCを停止させたり、Goルーチンを安全なポイントに移動させたりする必要があります。しかし、システムコール中はGoルーチンがOSカーネル内で実行されており、Goランタイムの制御下にはありません。

以前のコミット 231af8ac63aa (CL 7314062) は、runtime.entersyscall() 関数が呼び出された際に、現在のM(Machine、OSスレッドに相当)のm->mcachenilに設定するように変更しました。mcacheは、Goランタイムがヒープメモリを効率的に割り当てるために使用する、Mローカルなメモリキャッシュです。mcachenilに設定されると、そのMはGoランタイムのヒープアロケータを使用できなくなります。

この変更の意図は、システムコール中にGoランタイムがメモリを割り当てようとすることを防ぎ、GCとの競合やデッドロックのリスクを低減することにありました。しかし、この変更によって新たな問題が発生しました。syscall.Syscallsyscall.Syscall6といったシステムコールラッパーがエラー発生時にエラー文字列を取得するためにsyscall.errstrを呼び出していました。このsyscall.errstrは、エラー文字列を格納するための新しいバッファを動的に割り当てる必要がありました。しかし、entersyscallモードではmcachenilであるため、メモリ割り当てができなくなり、結果としてクラッシュが発生する可能性がありました。

この問題はGoのIssue #4994として報告されました。このコミットは、このクラッシュを修正し、entersyscallモードでも安全にエラー文字列を取得できるようにするためのものです。

前提知識の解説

このコミットを理解するためには、以下のGoランタイムとPlan 9に関する前提知識が必要です。

  1. Goランタイムのスケジューラ (GMPモデル):

    • G (Goroutine): Go言語の軽量スレッド。Goランタイムによって管理されます。
    • M (Machine): OSスレッドに相当します。GoランタイムはMをOSに要求し、その上でGを実行します。
    • P (Processor): 論理プロセッサ。MとGの間に位置し、Gを実行するためのコンテキストを提供します。PはMにアタッチされ、Gの実行に必要なリソース(例えば、mcache)を管理します。
    • m->mcache: 各Mが持つローカルなメモリキャッシュ。Goルーチンが小さなオブジェクトを頻繁に割り当てる際に、グローバルなヒープロックを回避し、高速なメモリ割り当てを可能にします。
    • runtime.entersyscall(): Goルーチンがシステムコールに入る直前に呼び出される関数。この関数は、Goランタイムがシステムコール中のMを追跡し、必要に応じてGCを調整するために使用されます。このコミットの背景にある問題は、この関数がm->mcache = nilを設定することに起因します。
    • runtime.exitsyscall(): システムコールから戻った直後に呼び出される関数。
  2. システムコール (Syscall):

    • GoプログラムがOSの機能(ファイルI/O、ネットワーク通信など)を利用するためのインターフェース。
    • Goのsyscallパッケージは、OS固有のシステムコールをGoから呼び出すためのラッパーを提供します。
    • syscall.Syscall / syscall.Syscall6: Goのsyscallパッケージが提供する汎用的なシステムコール呼び出し関数。引数の数に応じて使い分けられます。
    • syscall.errstr: Plan 9において、システムコールがエラーを返した場合に、そのエラーに関する文字列を取得するための関数。
  3. Plan 9オペレーティングシステム:

    • ベル研究所で開発された分散オペレーティングシステム。Go言語の開発者の一部がPlan 9の開発にも携わっていたため、Go言語はPlan 9をサポートしています。
    • Plan 9のエラー処理は、Unix系のerrnoとは異なり、エラー文字列を直接返すことが多いです。このため、GoのsyscallパッケージはPlan 9のエラー文字列の扱いに特化したロジックを持っています。
    • ERRMAX: Plan 9のエラー文字列の最大長を定義する定数。通常128バイト。
  4. アセンブリ言語 (x86/amd64):

    • Goランタイムの低レベルな部分やシステムコールラッパーは、パフォーマンスとOSとの直接的なインタラクションのためにアセンブリ言語で記述されています。
    • TEXT: Goのアセンブリ言語における関数定義のキーワード。
    • get_tls(AX): スレッドローカルストレージ(TLS)から現在のM(Machine)へのポインタを取得するマクロ。
    • m(AX) / m_errstr(BX): M構造体内のフィールドへのオフセットを示すシンボル。
    • SYSCALL / INT $64: システムコールを呼び出すためのアセンブリ命令。Plan 9ではINT $64がシステムコールエントリポイントとして使用されることがあります。
  5. runtime.findnull:

    • Goランタイム内部の関数で、与えられたバイト列の中からヌル終端文字(\0)を探し、その位置までの長さを返す関数。C言語のstrlenに相当します。

技術的詳細

このコミットの技術的詳細は、entersyscallモードにおけるメモリ割り当ての制約と、それに対する回避策に集約されます。

問題点: 以前のコミットにより、runtime.entersyscall()が呼び出されると、現在のMのm->mcachenilに設定されます。これは、システムコール中にGoランタイムのヒープアロケータが使用できないことを意味します。syscall.Syscallsyscall.Syscall6がエラーを検出した場合、syscall.errstrを呼び出してエラー文字列を取得しようとします。しかし、syscall.errstrは内部で新しいメモリバッファを割り当ててエラー文字列を格納しようとするため、mcachenilの状態ではメモリ割り当てに失敗し、ランタイムパニックやクラッシュを引き起こす可能性がありました。

解決策: このコミットは、この問題を解決するために以下の戦略を採用しています。

  1. Mごとのエラー文字列ストレージの導入:

    • runtime.hM構造体にbyte* errstr;という新しいフィールドを追加します。これは、各Mがシステムコールからのエラー文字列を格納するための専用のバッファを持つことを意味します。
    • runtime/thread_plan9.cruntime·mpreinit関数(Mの初期化時に呼び出される)で、このm->errstrバッファをERRMAX(128バイト)のサイズで事前に割り当てます。これにより、システムコール中に動的なメモリ割り当てを行う必要がなくなります。
  2. runtime.errstr関数の導入:

    • src/pkg/runtime/sys_plan9_386.ssrc/pkg/runtime/sys_plan9_amd64.sに、新しいアセンブリ関数runtime·errstrを実装します。
    • この関数は、m->errstrに格納されたエラー文字列を直接OSから取得し、そのポインタと最大長(ERRMAX)をerrstrシステムコールに渡します。
    • システムコールが完了した後、runtime.findnullを呼び出して、実際に書き込まれたエラー文字列の長さを計算します。これにより、Goの文字列として正しく扱えるようになります。
  3. syscallパッケージからの呼び出し変更:

    • src/pkg/syscall/asm_plan9_386.ssrc/pkg/syscall/asm_plan9_amd64.sにおいて、syscall.Syscallおよびsyscall.Syscall6がエラーを検出した場合に、以前はsyscall·errstrを呼び出していた箇所を、新しく導入されたruntime·errstrを呼び出すように変更します。

このアプローチにより、システムコール中にエラー文字列を取得する際に、Goランタイムのヒープアロケータに依存することなく、Mローカルな事前に割り当てられたバッファを使用できるようになります。これにより、entersyscallモードでのクラッシュが回避され、Goプログラムの堅牢性が向上します。

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

このコミットでは、以下の6つのファイルが変更されています。

  1. src/pkg/runtime/runtime.h:

    • M構造体にbyte* errstr;フィールドが追加されました。
  2. src/pkg/runtime/sys_plan9_386.s:

    • runtime·errstrアセンブリ関数が追加されました。これは386アーキテクチャ向けのPlan 9システムコールエラー文字列取得ロジックを含みます。
  3. src/pkg/runtime/sys_plan9_amd64.s:

    • runtime·errstrアセンブリ関数が追加されました。これはAMD64アーキテクチャ向けのPlan 9システムコールエラー文字列取得ロジックを含みます。
  4. src/pkg/runtime/thread_plan9.c:

    • runtime·mpreinit関数内で、新しく追加されたm->errstrバッファのメモリ割り当てが行われるようになりました。
  5. src/pkg/syscall/asm_plan9_386.s:

    • syscall·Syscallおよびsyscall·Syscall6関数内で、エラー発生時に呼び出すエラー文字列取得関数がsyscall·errstrからruntime·errstrに変更されました。
  6. src/pkg/syscall/asm_plan9_amd64.s:

    • syscall·Syscallおよびsyscall·Syscall6関数内で、エラー発生時に呼び出すエラー文字列取得関数がsyscall·errstrからruntime·errstrに変更されました。

コアとなるコードの解説

src/pkg/runtime/runtime.h

@@ -320,6 +320,7 @@ struct	M
 #endif
 #ifdef GOOS_plan9
  int8* notesig;
+ byte* errstr;
 #endif
  SEH* seh;
  uintptr end[];

M構造体にerrstrというbyteポインタが追加されました。これは、各M(OSスレッド)がシステムコールからのエラー文字列を格納するための専用のバッファを指すようになります。

src/pkg/runtime/sys_plan9_386.s および src/pkg/runtime/sys_plan9_amd64.s

これらのファイルには、runtime·errstrという新しいアセンブリ関数が追加されています。基本的なロジックは両アーキテクチャで共通しています。

#define ERRMAX 128  /* from os_plan9.h */

// func errstr() String
// Only used by package syscall.
// Grab error string due to a syscall made
// in entersyscall mode, without going
// through the allocator (issue 4994).
// See ../syscall/asm_plan9_386.s:/·Syscall/
TEXT runtime·errstr(SB),7,$0
    get_tls(AX)         // TLSから現在のMへのポインタをAXレジスタに取得
    MOVL    m(AX), BX   // AXが指すM構造体からMのベースアドレスをBXにロード
    MOVL    m_errstr(BX), CX // BXが指すM構造体からm->errstrフィールドの値をCXにロード (これがエラー文字列バッファのポインタ)
    MOVL    CX, 4(SP)   // CX (エラー文字列バッファのポインタ) をスタックにプッシュ (errstrシステムコールの第一引数)
    MOVL    $ERRMAX, 8(SP) // ERRMAX (バッファサイズ) をスタックにプッシュ (errstrシステムコールの第二引数)
    MOVL    $41, AX     // Plan 9のerrstrシステムコール番号 (386の場合)
    INT $64             // システムコール呼び出し
    CALL    runtime·findnull(SB) // ヌル終端文字を探して文字列の長さを計算
    MOVL    AX, 8(SP)   // 計算された長さをスタックに格納 (戻り値の長さ部分)
    RET                 // 呼び出し元に戻る

このアセンブリコードは、以下の手順でエラー文字列を取得します。

  1. 現在のM(OSスレッド)のコンテキストを取得し、M構造体内のerrstrフィールド(事前に割り当てられたバッファへのポインタ)を読み込みます。
  2. このバッファのポインタとERRMAX(バッファの最大サイズ)を引数として、Plan 9のerrstrシステムコールを呼び出します。これにより、OSがエラー文字列を直接このバッファに書き込みます。
  3. システムコールが完了した後、runtime.findnullを呼び出して、バッファに書き込まれた実際の文字列の長さを計算します。
  4. 最終的に、エラー文字列のポインタと長さをGoの文字列として返す準備をします。

src/pkg/runtime/thread_plan9.c

@@ -19,6 +19,10 @@ runtime·mpreinit(M *mp)
  // Initialize stack and goroutine for note handling.
  mp->gsignal = runtime·malg(32*1024);
  mp->notesig = (int8*)runtime·malloc(ERRMAX*sizeof(int8));
+
+ // Initialize stack for handling strings from the
+ // errstr system call, as used in package syscall.
+ mp->errstr = (byte*)runtime·malloc(ERRMAX*sizeof(byte));
 }
 
 // Called to initialize a new m (including the bootstrap m).

runtime·mpreinit関数は、新しいMが初期化される際に呼び出されます。ここで、mp->errstrに対してERRMAXサイズのメモリがruntime·mallocによって割り当てられます。これにより、各Mがシステムコールエラー文字列用の専用バッファを持つことが保証され、システムコール中に動的なメモリ割り当てを行う必要がなくなります。

src/pkg/syscall/asm_plan9_386.s および src/pkg/syscall/asm_plan9_amd64.s

これらのファイルでは、syscall·Syscallおよびsyscall·Syscall6関数内のエラー処理部分が変更されています。

@@ -29,7 +29,7 @@ TEXT	·Syscall(SB),7,$0
 	JNE	ok3
 
 	SUBL	$8, SP
-	CALL	syscall·errstr(SB)
+	CALL	runtime·errstr(SB)
 	MOVL	SP, SI
 	ADDL	$8, SP
 	JMP	copyresult3

変更前はsyscall·errstr(SB)を呼び出していましたが、変更後はruntime·errstr(SB)を呼び出すように修正されています。これにより、システムコールがエラーを返した場合に、Goランタイムが提供する新しい軽量なエラー文字列取得メカニズムが使用されるようになります。

これらの変更により、entersyscallモードでm->mcachenilに設定されていても、syscallパッケージが安全にエラー文字列を取得できるようになり、Goプログラムの安定性が向上しました。

関連リンク

  • Go Issue #4994: https://golang.org/issue/4994 (Web検索では直接的な情報が見つかりませんでしたが、コミットメッセージに記載されています。)
  • Go CL 7567043: https://golang.org/cl/7567043 (このコミットのChange Listへのリンク)
  • Go CL 7314062: (Web検索では直接的な情報が見つかりませんでしたが、コミットメッセージに記載されている、この変更の背景となった以前のChange List)

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Plan 9オペレーティングシステムのドキュメント
  • Goランタイムのソースコード (特にsrc/pkg/runtimeおよびsrc/pkg/syscallディレクトリ)
  • Go言語のIssueトラッカー (Issue #4994)
  • Go言語のChange List (CL 7567043, CL 7314062)

(注: Issue #4994およびCL 7314062に関する直接的なWeb検索結果は得られませんでしたが、コミットメッセージの記述に基づき解説を構成しました。)