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

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

このコミットは、Go言語のsyscallパッケージにおけるPlan 9オペレーティングシステム向けの終了処理を改善するものです。具体的には、syscall.Exitが呼び出し元のGoプロセスのみを終了させ、同じプロセスグループ内の他のGoプロセスが残ってしまう問題を解決します。この変更により、syscall.Exitruntime·exitを呼び出すようになり、関連するCL (Change List) https://golang.org/cl/5617048の変更と合わせて、プロセスグループ内の全てのGoプロセスがクリーンに終了するようになります。

コミット

commit 49a7da2dd94347f0e019e832fd4584c4263bf7ff
Author: Akshat Kumar <seed@mail.nanosouffle.net>
Date:   Thu Apr 19 16:31:26 2012 -0700

    syscall: cleanly exit all Go procs on Plan 9
    
    syscall.Exit would originally kill only the calling
    Go proc, leaving behind other procs in the
    same group. This change makes syscall.Exit call
    runtime·exit, which due to CL
    https://golang.org/cl/5617048
    will cleanly exit all the Go procs in the group.
    
    R=golang-dev, rsc, rminnich, remyoudompheng, ality, john
    CC=golang-dev, mirtchovski
    https://golang.org/cl/6036051

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

https://github.com/golang/go/commit/49a7da2dd94347f0e019e832fd4584c4263bf7ff

元コミット内容

このコミットの目的は、「Plan 9上で全てのGoプロセスをクリーンに終了させる」ことです。以前のsyscall.Exitは、呼び出し元のGoプロセスのみを終了させ、同じプロセスグループ内の他のGoプロセスが残存するという問題がありました。この変更により、syscall.Exitruntime·exitを呼び出すようになり、これによりプロセスグループ内の全てのGoプロセスがクリーンに終了するようになります。これは、関連するCL https://golang.org/cl/5617048によるruntime·exitの挙動変更と連携しています。

変更の背景

Goプログラムは、複数のゴルーチン(Goの軽量スレッド)を持つことができ、これらはOSレベルでは複数のプロセス(Plan 9では「プロック」と呼ばれる)として実行されることがあります。Plan 9では、プロセスはグループ化され、特定の操作がグループ全体に影響を与えることがあります。

このコミット以前のsyscall.Exitの実装では、Goプログラムが終了しようとした際に、syscall.Exitが呼び出された特定のGoプロセス(プロック)のみが終了し、同じGoプログラムに属する他のGoプロセスが終了せずに残ってしまうという問題がありました。これは、リソースリークや予期せぬ挙動を引き起こす可能性があり、プログラムの健全な終了を妨げていました。

この問題は、特にGoプログラムが複数のOSプロセスを生成して並行処理を行う場合に顕著になります。プログラム全体が終了する際には、そのプログラムが生成した全てのOSプロセスが適切に終了することが期待されます。このコミットは、この「クリーンな終了」を実現するために導入されました。

前提知識の解説

  • Plan 9 (from Bell Labs): ベル研究所が開発した分散オペレーティングシステムです。Unixの概念をさらに推し進め、全てのリソース(ファイル、デバイス、ネットワーク接続など)をファイルシステムとして表現する「全てはファイル」という哲学を特徴としています。プロセス管理やシステムコールもUnix系OSとは異なる独自のアプローチを持っています。Plan 9では、プロセスは「プロック (proc)」と呼ばれ、プロセスグループの概念も存在します。
  • Go言語のsyscallパッケージ: Go言語の標準ライブラリの一部で、オペレーティングシステムの低レベルなプリミティブ(システムコール)へのインターフェースを提供します。OS固有の機能にアクセスするために使用されますが、通常はostimenetなどの高レベルなパッケージを使用することが推奨されます。syscallパッケージは、各OS向けに異なる実装を持っています(例: syscall_plan9.go, syscall_linux.goなど)。
  • ゴルーチン (Goroutine): Go言語における軽量な並行処理の単位です。OSのスレッドよりもはるかに軽量で、数千、数万のゴルーチンを同時に実行することが可能です。GoランタイムがゴルーチンをOSスレッドにマッピングして実行を管理します。
  • runtime·exit: Goランタイム内部の関数で、Goプログラム全体の終了処理を担当します。この関数は、プログラムの全てのゴルーチンを停止させ、リソースを解放し、最終的にOSに終了ステータスを返します。このコミットで参照されているCL https://golang.org/cl/5617048は、このruntime·exitの挙動を改善し、プロセスグループ内の全てのGoプロセスをクリーンに終了させるように変更したものです。
  • アセンブリ言語 (Assembly Language): コンピュータのプロセッサが直接実行できる機械語命令を、人間が読み書きしやすいように記号化した低レベルプログラミング言語です。Go言語のランタイムや一部のシステムコールラッパーは、パフォーマンスやOSとの直接的な連携のためにアセンブリ言語で記述されることがあります。このコミットでは、Plan 9の386アーキテクチャ向けのアセンブリコードが変更されています。
  • TEXTディレクティブ (Goアセンブリ): Go言語のアセンブリファイルで使用されるディレクティブで、関数の開始を宣言します。TEXT funcname(SB), flags, framesizeのような形式で記述され、SBは静的ベースポインタ、flagsは関数のプロパティ(例: 7NOSPLITNEEDCTXTの組み合わせ)、framesizeはスタックフレームのサイズを示します。

技術的詳細

このコミットの技術的な核心は、syscall.Exitの内部実装を、直接OSの終了システムコールを呼び出すのではなく、Goランタイムのruntime·exit関数を呼び出すように変更した点にあります。

以前のsyscall_plan9.goでは、Exit関数はExits関数を呼び出し、Exits関数が最終的にPlan 9のexitsシステムコール(メッセージを伴う終了)を呼び出していました。このexitsシステムコールは、呼び出し元のプロセスのみを終了させる挙動を持っていました。

新しい実装では、syscall_plan9.goExit関数は、アセンブリで定義されたexit関数を呼び出すように変更されます。このアセンブリのexit関数(src/pkg/syscall/asm_plan9_386.sに定義)は、引数として受け取った終了コードをスタックにプッシュし、その後CALL runtime·exit(SB)命令を使ってGoランタイムのruntime·exit関数を呼び出します。

この変更により、Goプログラムの終了処理がGoランタイムの制御下に入ります。runtime·exitは、Goランタイムが管理する全てのゴルーチンと、それらが実行されているOSプロセス(プロック)を適切に終了させるロジックを持っています。特に、このコミットで言及されているCL https://golang.org/cl/5617048によって、runtime·exitはPlan 9上でプロセスグループ内の全てのGoプロセスをクリーンに終了させるように強化されています。

これにより、Goプログラムがsyscall.Exitを呼び出した際に、そのプログラムに関連する全てのOSプロセスが確実に終了し、リソースリークやゾンビプロセスの発生を防ぐことができます。

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

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

  1. src/pkg/syscall/asm_plan9_386.s: Plan 9の386アーキテクチャ向けのアセンブリコードファイル。

    • 新しい関数TEXT ·exit(SB),7,$4が追加されました。この関数は、Goのsyscall.Exitから呼び出され、最終的にruntime·exitを呼び出すためのラッパーとして機能します。
    • MOVL code+0(FP), AXで引数codeAXレジスタにロードします。
    • MOVL AX, 0(SP)AXレジスタの値をスタックにプッシュします(runtime·exitの引数として)。
    • CALL runtime·exit(SB)runtime·exit関数を呼び出します。
  2. src/pkg/syscall/syscall_plan9.go: Plan 9オペレーティングシステム向けのGoのシステムコール実装ファイル。

    • 既存のExits関数(//sys exits(msg *byte)とそれに関連するGoコード)が完全に削除されました。
    • Exit関数が大幅に簡素化されました。以前はExitsを呼び出して終了メッセージを構築していましたが、新しい実装では単にアセンブリで定義されたexit(code int)関数を呼び出すだけになりました。
    • func exit(int)というアセンブリで実装された関数の宣言が追加されました。

コアとなるコードの解説

src/pkg/syscall/asm_plan9_386.s の変更点

+//func exit(code int)
+// Import runtime·exit for cleanly exiting.
+TEXT ·exit(SB),7,$4
+	MOVL	code+0(FP), AX
+	MOVL	AX, 0(SP)
+	CALL	runtime·exit(SB)
+	RET
  • TEXT ·exit(SB),7,$4: exitという名前のGo関数をアセンブリで定義しています。
    • ·exit: Goのリンカが認識する内部的な関数名です。
    • (SB): 静的ベースポインタからのオフセットでアドレス指定されることを示します。
    • 7: 関数のフラグです。この場合、NOSPLIT (スタックの分割を許可しない) と NEEDCTXT (コンテキストレジスタが必要) の組み合わせです。
    • $4: この関数のスタックフレームサイズが4バイトであることを示します。これは、code引数をスタックにプッシュするために必要なスペースです。
  • MOVL code+0(FP), AX: codeという引数(フレームポインタFPからのオフセット0にある)の値をAXレジスタに移動します。codesyscall.Exitに渡された終了コードです。
  • MOVL AX, 0(SP): AXレジスタの値をスタックポインタSPの指すアドレス(スタックの先頭)に移動します。これは、runtime·exit関数に渡す引数(終了コード)を準備しています。
  • CALL runtime·exit(SB): runtime·exit関数を呼び出します。この呼び出しにより、Goランタイムの終了処理が開始され、プログラムに関連する全てのGoプロセスがクリーンに終了します。
  • RET: 関数から戻ります。ただし、runtime·exitは通常、呼び出し元に戻ることなくプログラムを終了させるため、このRET命令に到達することは稀です。

src/pkg/syscall/syscall_plan9.go の変更点

// Implemented in assembly to import from runtime.
func exit(int)

func Exit(code int) { exit(code) }
  • func exit(int): これは、アセンブリファイル(asm_plan9_386.s)で実装されているexit関数をGoコードから呼び出すための宣言です。Goコンパイラは、この宣言を見て、対応するアセンブリ関数をリンクします。
  • func Exit(code int) { exit(code) }: Goのsyscall.Exit関数が、単にアセンブリで実装されたexit関数を呼び出すように変更されました。これにより、終了処理のロジックがGoランタイムに一元化され、Plan 9上でのGoプログラムの終了がより堅牢かつクリーンになります。

関連リンク

参考にした情報源リンク