[インデックス 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.entersyscall
とsys.exitsyscall
というフックを介してシステムコールを実行していました。これらは、ガベージコレクションの停止やスケジューラの調整など、ランタイムの内部状態を管理するために使用されます。しかし、exec
のような特定のシナリオでは、これらのランタイムフックをバイパスして、より直接的にシステムコールを実行する必要がある場合があります。RawSyscall
は、これらのフックをスキップすることで、より低レベルで制御されたシステムコール実行を提供します。これは、exec
が新しいプロセスイメージに切り替わる際に、Goランタイムの既存の状態をクリーンに破棄するために重要です。syscall.dup2
の追加:dup2
システムコールは、既存のファイルディスクリプタを複製し、指定された新しいファイルディスクリプタ番号に割り当てます。これは、子プロセスの標準入力、標準出力、標準エラーを親プロセスのパイプやファイルにリダイレクトする際に不可欠です。例えば、exec
で起動するプログラムの出力を親プロセスで捕捉するために使用されます。syscall.BytePtrPtr
の追加: これは、**byte
型(バイトポインタへのポインタ)をシステムコールに渡すためのヘルパー関数です。Unix系システムコールでは、execve
のように引数リストや環境変数リストをchar **argv
やchar **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.h
とsrc/runtime/signals_linux.h
が新しく追加されました。これにより、OSごとのシグナル番号やそのデフォルトの動作(キャッチするか、無視するか、再開するかなど)をより明確に、かつプラットフォーム固有の特性に合わせて定義できるようになりました。 SigTab
構造体の変更と新しいフラグ:runtime.h
内のSigTab
構造体が変更され、catch
フィールドがflags
フィールドに置き換えられました。また、SigCatch
,SigIgnore
,SigRestart
という新しい列挙型が導入されました。これにより、各シグナルに対して「キャッチする(Goのシグナルハンドラで処理する)」、「無視する」、「中断されたシステムコールを再開する」といった、より詳細な動作を指定できるようになりました。sigaction
構造体とフラグの利用: DarwinとLinuxの両方で、sigaction
システムコールを呼び出す際に、SA_SIGINFO
、SA_ONSTACK
、SA_RESTART
、SA_RESTORER
などのフラグが適切に設定されるようになりました。SA_SIGINFO
: シグナルハンドラに詳細なシグナル情報(siginfo_t
)を渡すために使用されます。SA_ONSTACK
: シグナルハンドラが代替スタックで実行されるようにし、スタックオーバーフローなどの問題を回避します。SA_RESTART
: シグナルによって中断されたシステムコールを自動的に再開します。これは、exec
のようなプロセス管理において、予期せぬ中断を避けるために重要です。SA_RESTORER
(Linux): シグナルハンドラから復帰するための特別な関数(sigreturn
)を指定します。
sigtramp
とsighandler
の改善: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版では
sigignore
とsigreturn
が追加されました。
- シグナルハンドリングのロジックが大幅に修正されました。
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版では
sigignore
とsigreturn
のアセンブリ実装が追加されました。
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向け実装です。
MOVQ
命令で、スタックから引数(a1
,a2
,a3
)をレジスタDI
,SI
,DX
に、システムコール番号(trap
)をAX
レジスタにロードします。SYSCALL
命令を実行し、カーネルにシステムコールを要求します。- システムコールからの戻り値(通常は
AX
レジスタ)をチェックします。Unix系システムコールでは、エラーが発生した場合、戻り値が負の値(通常は-errno
)になります。 CMPQ AX, $0xfffffffffffff001
は、戻り値がエラーを示す範囲(-4095
から-1
)にあるかどうかをチェックします。- エラーの場合(
JLS ok1
がジャンプしない場合)、AX
を符号反転してerrno
としてスタックに格納し、r1
とr2
を-1
と0
に設定して返します。 - 成功の場合(
JLS ok1
がジャンプする場合)、AX
をr1
に、DX
をr2
に、0
をerrno
としてスタックに格納して返します。 この実装は、Goランタイムのentersyscall
/exitsyscall
フックを呼び出さないため、より直接的なシステムコール実行を提供します。
シグナルハンドリングの初期化 (例: src/runtime/rt1_amd64_linux.c
のinitsig
関数)
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
関数の一部です。
static struct sigaction sa;
でsigaction
構造体を静的に宣言します。sa.sa_flags
にSA_ONSTACK
(代替スタックでハンドラを実行)、SA_SIGINFO
(詳細なシグナル情報をハンドラに渡す)、SA_RESTORER
(sigreturn
を復元関数として使用)を設定します。sa.sa_mask
を0xFFFFFFFFFFFFFFFFULL
に設定し、シグナルハンドラ実行中はすべてのシグナルをブロックするようにします。sa.sa_restorer
にsigreturn
関数のアドレスを設定します。これは、シグナルハンドラから復帰する際に呼び出される特別な関数です。for
ループで、定義されているすべてのシグナル(NSIG
まで)を反復処理します。sigtab[i].flags
をチェックし、そのシグナルに特別なフラグが設定されている場合のみ処理を行います。SigCatch
フラグが設定されている場合、sa.sa_handler
をsigtramp
(Goのシグナルハンドラを呼び出すアセンブリルーチン)に設定します。そうでない場合(例えばSigIgnore
が設定されている場合)、sa.sa_handler
をsigignore
(シグナルを無視するだけの関数)に設定します。SigRestart
フラグが設定されている場合、sa.sa_flags
にSA_RESTART
を追加し、中断されたシステムコールが自動的に再開されるようにします。- 最後に、
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のランタイムとスケジューラに関する技術記事やブログポスト