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

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

このコミットは、Go言語のランタイムとsyscallパッケージにおける重要な変更を導入しており、特にexecシステムコール(新しいプロセスを実行する機能)のサポートに向けた準備と、シグナルハンドリングの堅牢性向上に焦点を当てています。

コミット

commit dfa5893d4f5a5724e36e1265eba4e148ca42911f
Author: Russ Cox <rsc@golang.org>
Date:   Wed Dec 3 14:21:28 2008 -0800

    preparation for exec.
    
    * syscall:
            add syscall.RawSyscall, which doesn't use sys.entersyscall/sys.exitsyscall
            add syscall.dup2
            add syscall.BytePtrPtr
            add syscall.Rusage, RusagePtr
            add syscall.F_GETFD, F_SETFD, FD_CLOEXEC
    
    * runtime:
            clean up, correct signal handling.
            can now survive (continue running after) a signal.
    
    R=r
    DELTA=394  (286 added, 51 deleted, 57 changed)
    OCL=20351
    CL=20369

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

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

元コミット内容

上記の「コミット」セクションに記載されている内容が、このコミットの元の内容です。

変更の背景

このコミットの主な背景は、Goプログラムが外部の実行可能ファイル(例えばシェルコマンドや他のプログラム)を起動できるようにするexecシステムコールの実装準備です。execは、現在のプロセスイメージを新しいプロセスイメージで置き換えるシステムコールであり、これには低レベルのシステムコールインターフェース、ファイルディスクリプタの管理、そして特に重要なシグナルハンドリングの正確な動作が不可欠です。

また、既存のGoランタイムにおけるシグナルハンドリングが不完全であり、シグナルを受信した際にプログラムがクラッシュする可能性があったため、その堅牢性を向上させる必要がありました。特に、execのようなプロセス管理を行う機能では、子プロセスの終了を通知するSIGCHLDなどのシグナルを適切に処理できることが求められます。

このコミットは、これらの要件を満たすために、syscallパッケージに新しい低レベルのプリミティブを追加し、ランタイムのシグナル処理メカニズムを根本的に見直しています。

前提知識の解説

このコミットを理解するためには、以下の概念に関する知識が役立ちます。

  • システムコール (Syscall): オペレーティングシステムが提供するサービスをプログラムが利用するためのインターフェースです。ファイル操作、プロセス管理、メモリ管理など、OSのコア機能にアクセスするために使用されます。
  • execシステムコール: Unix系OSにおけるシステムコールの一つで、現在のプロセスイメージを新しいプログラムのイメージで置き換えます。これにより、新しいプロセスが起動されますが、プロセスID (PID) は変更されません。
  • ファイルディスクリプタ (File Descriptor, FD): Unix系OSにおいて、開かれたファイルやI/Oリソース(ソケット、パイプなど)を識別するために使用される整数値です。
  • dup2システムコール: 既存のファイルディスクリプタを複製し、指定された新しいファイルディスクリプタ番号に割り当てるシステムコールです。これにより、標準入力/出力/エラーをリダイレクトする際などに利用されます。
  • FD_CLOEXECフラグ: ファイルディスクリプタに設定できるフラグの一つで、execシステムコールが実行された際に、そのファイルディスクリプタが自動的に閉じられるように指定します。これは、子プロセスに不要なファイルディスクリプタが継承されるのを防ぎ、セキュリティとリソース管理の観点から重要です。
  • シグナル (Signal): Unix系OSにおいて、プロセスに対して非同期的にイベントを通知するメカニズムです。例えば、SIGINT(Ctrl+Cによる割り込み)、SIGSEGV(セグメンテーション違反)、SIGCHLD(子プロセスの終了)などがあります。
  • シグナルハンドリング (Signal Handling): プロセスがシグナルを受信した際に、どのように応答するかを定義するメカニズムです。通常、シグナルハンドラと呼ばれる関数を登録し、特定のシグナルが配送されたときにその関数が実行されます。
  • sigaction構造体: シグナルハンドラの設定、シグナルマスク、およびシグナルハンドラの動作を制御するフラグを定義するために使用される構造体です。
  • SA_SIGINFOフラグ: sigaction構造体で設定されるフラグの一つで、シグナルハンドラに追加の情報(siginfo_t構造体)を渡すように指定します。
  • SA_ONSTACKフラグ: シグナルハンドラが代替シグナルスタックで実行されるように指定します。これにより、通常のスタックが破損している場合でもシグナルハンドラが安全に実行できます。
  • SA_RESTARTフラグ: シグナルハンドラから復帰した際に、中断されたシステムコールを自動的に再開するように指定します。
  • sigtramp (Signal Trampoline): シグナルハンドラが呼び出される前に実行される、アセンブリ言語で書かれた小さなコードスニペットです。これは、シグナルハンドラのコンテキストを設定し、ハンドラからの復帰を処理するために使用されます。
  • Rusage構造体: プロセスのリソース使用状況(CPU時間、メモリ使用量など)に関する情報を格納する構造体です。

技術的詳細

このコミットは、Goランタイムの低レベルな部分に深く関わる変更を含んでいます。

syscallパッケージの拡張

  • syscall.RawSyscallの追加: 従来のSyscall関数は、Goランタイムのsys.entersyscallsys.exitsyscallというフックを介してシステムコールを実行していました。これらは、ガベージコレクションの停止やスケジューラの調整など、ランタイムの内部状態を管理するために使用されます。しかし、execのような特定のシナリオでは、これらのランタイムフックをバイパスして、より直接的にシステムコールを実行する必要がある場合があります。RawSyscallは、これらのフックをスキップすることで、より低レベルで制御されたシステムコール実行を提供します。これは、execが新しいプロセスイメージに切り替わる際に、Goランタイムの既存の状態をクリーンに破棄するために重要です。
  • syscall.dup2の追加: dup2システムコールは、既存のファイルディスクリプタを複製し、指定された新しいファイルディスクリプタ番号に割り当てます。これは、子プロセスの標準入力、標準出力、標準エラーを親プロセスのパイプやファイルにリダイレクトする際に不可欠です。例えば、execで起動するプログラムの出力を親プロセスで捕捉するために使用されます。
  • syscall.BytePtrPtrの追加: これは、**byte型(バイトポインタへのポインタ)をシステムコールに渡すためのヘルパー関数です。Unix系システムコールでは、execveのように引数リストや環境変数リストをchar **argvchar **envpとして受け取るものがあります。Goの[]byteスライスをこれらのCスタイルのポインタ配列に変換するために必要となります。
  • syscall.Rusage, RusagePtrの追加: getrusageシステムコールなどで使用されるrusage構造体と、そのポインタを扱うための関数が追加されました。これにより、プロセスのリソース使用状況を取得できるようになります。execの前後でリソース情報を管理する際に役立つ可能性があります。
  • syscall.F_GETFD, F_SETFD, FD_CLOEXEC定数の追加: これらは、ファイルディスクリプタのフラグを操作するための定数です。特にFD_CLOEXECは、execが実行される際にファイルディスクリプタが自動的に閉じられるように設定するために重要です。これにより、子プロセスに不要なファイルディスクリプタが漏洩するのを防ぎ、セキュリティリスクを低減します。

runtimeパッケージのシグナルハンドリングの改善

  • シグナルハンドリングのクリーンアップと修正: 以前のGoランタイムでは、シグナル処理が不完全であり、シグナルを受信するとプログラムがクラッシュする可能性がありました。このコミットでは、シグナルハンドリングのロジックが大幅に改善され、シグナルを受信してもプログラムが継続して実行できるようになりました。
  • プラットフォーム固有のシグナル定義の分離: src/runtime/signals.hが削除され、代わりにsrc/runtime/signals_darwin.hsrc/runtime/signals_linux.hが新しく追加されました。これにより、OSごとのシグナル番号やそのデフォルトの動作(キャッチするか、無視するか、再開するかなど)をより明確に、かつプラットフォーム固有の特性に合わせて定義できるようになりました。
  • SigTab構造体の変更と新しいフラグ: runtime.h内のSigTab構造体が変更され、catchフィールドがflagsフィールドに置き換えられました。また、SigCatch, SigIgnore, SigRestartという新しい列挙型が導入されました。これにより、各シグナルに対して「キャッチする(Goのシグナルハンドラで処理する)」、「無視する」、「中断されたシステムコールを再開する」といった、より詳細な動作を指定できるようになりました。
  • sigaction構造体とフラグの利用: DarwinとLinuxの両方で、sigactionシステムコールを呼び出す際に、SA_SIGINFOSA_ONSTACKSA_RESTARTSA_RESTORERなどのフラグが適切に設定されるようになりました。
    • SA_SIGINFO: シグナルハンドラに詳細なシグナル情報(siginfo_t)を渡すために使用されます。
    • SA_ONSTACK: シグナルハンドラが代替スタックで実行されるようにし、スタックオーバーフローなどの問題を回避します。
    • SA_RESTART: シグナルによって中断されたシステムコールを自動的に再開します。これは、execのようなプロセス管理において、予期せぬ中断を避けるために重要です。
    • SA_RESTORER (Linux): シグナルハンドラから復帰するための特別な関数(sigreturn)を指定します。
  • sigtrampsighandlerの改善: sigtrampアセンブリルーチンとsighandler C関数が修正されました。特にLinuxでは、sigignore(シグナルを無視するだけのハンドラ)とsigreturn(シグナルハンドラから復帰するためのシステムコールを呼び出す)が導入され、より標準的なシグナル処理のパターンに沿うようになりました。sigtrampは、シグナルハンドラが呼び出される前にレジスタの状態を保存し、ハンドラからの復帰時に復元する役割を担います。
  • test/sigchld.goの追加: SIGCHLDシグナル(子プロセスの状態変化を通知するシグナル)の処理をテストするための新しいGoプログラムが追加されました。これは、execによって起動された子プロセスの管理において、SIGCHLDの適切なハンドリングが重要であることを示しています。

これらの変更は、Goがより複雑なシステムプログラミングタスク、特にプロセス管理と外部プログラムの実行を、より堅牢かつ安全に行えるようにするための基盤を築いています。

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

このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。

  • src/lib/syscall/asm_amd64_darwin.s および src/lib/syscall/asm_amd64_linux.s:
    • syscall.RawSyscallのアセンブリ実装が追加されました。これは、sys.entersyscall/sys.exitsyscallフックをバイパスして直接システムコールを呼び出すためのものです。
  • src/lib/syscall/cast_amd64.s:
    • syscall.BytePtrPtrおよびsyscall.RusagePtrのアセンブリ実装が追加されました。
  • src/lib/syscall/file_darwin.go および src/lib/syscall/file_linux.go:
    • syscall.dup2関数が追加されました。
  • src/lib/syscall/syscall.go:
    • RawSyscallおよびBytePtrPtrのGo側の宣言が追加されました。
  • src/lib/syscall/types_amd64_darwin.go および src/lib/syscall/types_amd64_linux.go:
    • Rusage構造体とそのポインタを返す関数RusagePtrが追加されました。
    • F_GETFD, F_SETFD, FD_CLOEXECなどのファイルディスクリプタ関連の定数が追加されました。
  • src/runtime/rt1_amd64_darwin.c および src/runtime/rt1_amd64_linux.c:
    • シグナルハンドリングのロジックが大幅に修正されました。sigaction構造体の定義、sighandler関数のシグネチャ、initsig関数の実装が変更され、より堅牢なシグナル処理が導入されました。
    • Linux版ではsigignoresigreturnが追加されました。
  • src/runtime/runtime.h:
    • SigTab構造体のcatchフィールドがflagsに変更され、SigCatch, SigIgnore, SigRestartという新しい列挙型が追加されました。
    • sys·sigactionおよびsys·rt_sigactionの宣言が削除されました(これらはアセンブリレベルで直接呼び出されるようになったため)。
  • src/runtime/signals.h:
    • このファイルは削除されました。
  • src/runtime/signals_darwin.h および src/runtime/signals_linux.h:
    • 新しいプラットフォーム固有のシグナル定義ファイルが追加され、各シグナルに対するSigTabのエントリが定義されました。
  • src/runtime/sys_amd64_darwin.s および src/runtime/sys_amd64_linux.s:
    • sigactionおよびrt_sigactionシステムコールの呼び出しが修正されました。
    • sigtrampアセンブリルーチンが変更され、シグナルハンドラからの復帰処理が改善されました。
    • Linux版ではsigignoresigreturnのアセンブリ実装が追加されました。
  • test/sigchld.go:
    • SIGCHLDシグナル処理をテストするための新しいGoプログラムが追加されました。

コアとなるコードの解説

syscall.RawSyscall (例: src/lib/syscall/asm_amd64_linux.s)

TEXT syscall·RawSyscall(SB),7,$0
	MOVQ	16(SP), DI
	MOVQ	24(SP), SI
	MOVQ	32(SP), DX
	MOVQ	8(SP), AX	// syscall entry
	SYSCALL
	CMPQ	AX, $0xfffffffffffff001
	JLS	ok1
	MOVQ	$-1, 40(SP)	// r1
	MOVQ	$0, 48(SP)	// r2
	NEGQ	AX
	MOVQ	AX, 56(SP)  // errno
	RET
ok1:
	MOVQ	AX, 40(SP)	// r1
	MOVQ	DX, 48(SP)	// r2
	MOVQ	$0, 56(SP)	// errno
	RET

このアセンブリコードは、syscall.RawSyscall関数のLinux/amd64向け実装です。

  1. MOVQ命令で、スタックから引数(a1, a2, a3)をレジスタDI, SI, DXに、システムコール番号(trap)をAXレジスタにロードします。
  2. SYSCALL命令を実行し、カーネルにシステムコールを要求します。
  3. システムコールからの戻り値(通常はAXレジスタ)をチェックします。Unix系システムコールでは、エラーが発生した場合、戻り値が負の値(通常は-errno)になります。
  4. CMPQ AX, $0xfffffffffffff001は、戻り値がエラーを示す範囲(-4095から-1)にあるかどうかをチェックします。
  5. エラーの場合(JLS ok1がジャンプしない場合)、AXを符号反転してerrnoとしてスタックに格納し、r1r2-10に設定して返します。
  6. 成功の場合(JLS ok1がジャンプする場合)、AXr1に、DXr2に、0errnoとしてスタックに格納して返します。 この実装は、Goランタイムのentersyscall/exitsyscallフックを呼び出さないため、より直接的なシステムコール実行を提供します。

シグナルハンドリングの初期化 (例: src/runtime/rt1_amd64_linux.cinitsig関数)

void
initsig(void)
{
	static struct sigaction sa;

	int32 i;
	sa.sa_flags = SA_ONSTACK | SA_SIGINFO | SA_RESTORER;
	sa.sa_mask = 0xFFFFFFFFFFFFFFFFULL;
	sa.sa_restorer = (void*)sigreturn;
	for(i = 0; i<NSIG; i++) {
		if(sigtab[i].flags) {
			if(sigtab[i].flags & SigCatch)
				sa.sa_handler = (void*)sigtramp;
			else
				sa.sa_handler = (void*)sigignore;
			if(sigtab[i].flags & SigRestart)
				sa.sa_flags |= SA_RESTART;
			else
				sa.sa_flags &= ~SA_RESTART;
			rt_sigaction(i, &sa, nil, 8);
		}
	}
}

このCコードは、Linux/amd64におけるシグナルハンドリングの初期化を行うinitsig関数の一部です。

  1. static struct sigaction sa;sigaction構造体を静的に宣言します。
  2. sa.sa_flagsSA_ONSTACK(代替スタックでハンドラを実行)、SA_SIGINFO(詳細なシグナル情報をハンドラに渡す)、SA_RESTORERsigreturnを復元関数として使用)を設定します。
  3. sa.sa_mask0xFFFFFFFFFFFFFFFFULLに設定し、シグナルハンドラ実行中はすべてのシグナルをブロックするようにします。
  4. sa.sa_restorersigreturn関数のアドレスを設定します。これは、シグナルハンドラから復帰する際に呼び出される特別な関数です。
  5. forループで、定義されているすべてのシグナル(NSIGまで)を反復処理します。
  6. sigtab[i].flagsをチェックし、そのシグナルに特別なフラグが設定されている場合のみ処理を行います。
  7. SigCatchフラグが設定されている場合、sa.sa_handlersigtramp(Goのシグナルハンドラを呼び出すアセンブリルーチン)に設定します。そうでない場合(例えばSigIgnoreが設定されている場合)、sa.sa_handlersigignore(シグナルを無視するだけの関数)に設定します。
  8. SigRestartフラグが設定されている場合、sa.sa_flagsSA_RESTARTを追加し、中断されたシステムコールが自動的に再開されるようにします。
  9. 最後に、rt_sigactionシステムコールを呼び出して、各シグナルに対するsigaction構造体を登録します。これにより、Goランタイムがシグナルを適切に処理できるようになります。

関連リンク

  • Go言語の公式ドキュメント (当時のものに直接アクセスするのは難しいですが、現在のドキュメントは参考になります): https://go.dev/doc/
  • Unix系システムコールに関する一般的な情報: man syscall, man dup2, man sigaction, man execve など

参考にした情報源リンク

  • Go言語のソースコード (GitHub): https://github.com/golang/go
  • Unix系OSのシステムコールに関するmanページ (例: man 2 sigaction, man 2 execve)
  • Go言語の初期の設計に関する議論やメーリングリストのアーカイブ (Goの歴史的な背景を理解する上で役立つ場合があります)
  • Goのsyscallパッケージに関するドキュメントや解説記事 (当時のものに直接アクセスするのは難しいですが、現在のGoのsyscallパッケージの動作を理解する上で参考になります)
  • Goのランタイムとスケジューラに関する技術記事やブログポスト