[インデックス 11847] ファイルの概要
このコミットは、Go言語のos/signal
パッケージにおけるシグナルハンドリングのメカニズムを大幅に刷新し、より柔軟で堅牢な設計へと変更するものです。具体的には、従来のIncoming
関数による一元的なシグナル受信から、Notify
関数を用いた選択的かつ複数クライアント対応のシグナル通知へと移行しています。また、Unix系OSにおける共通のシグナルハンドリングコードをruntime/signal_unix.c
に集約することで、ランタイムのコードベースの整理も行われています。
コミット
commit 35586f718cc5d808de1c7d9a367f55c54864326a
Author: Russ Cox <rsc@golang.org>
Date: Mon Feb 13 13:52:37 2012 -0500
os/signal: selective signal handling
Restore package os/signal, with new API:
Notify replaces Incoming, allowing clients
to ask for certain signals only. Also, signals
go to everyone who asks, not just one client.
This could plausibly move into package os now
that there are no magic side effects as a result
of the import.
Update runtime for new API: move common Unix
signal handling code into signal_unix.c.
(It's so easy to do this now that we don't have
to edit Makefiles!)
Tested on darwin,linux 386,amd64.
Fixes #1266.
R=r, dsymonds, bradfitz, iant, borman
CC=golang-dev
https://golang.org/cl/3749041
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/35586f718cc5d808de1c7d9a367f55c54864326a
元コミット内容
os/signal: selective signal handling
Restore package os/signal, with new API:
Notify replaces Incoming, allowing clients
to ask for certain signals only. Also, signals
go to everyone who asks, not just one client.
This could plausibly move into package os now
that there are no magic side effects as a result
of the import.
Update runtime for new API: move common Unix
signal handling code into signal_unix.c.
(It's so easy to do this now that we don't have
to edit Makefiles!)
Tested on darwin,linux 386,amd64.
Fixes #1266.
R=r, dsymonds, bradfitz, iant, borman
CC=golang-dev
https://golang.org/cl/3749041
変更の背景
このコミットの背景には、Go言語におけるシグナルハンドリングの初期設計が抱えていた課題と、それに対する改善の必要性がありました。
初期のos/signal
パッケージにはIncoming
という関数が存在し、これはプログラムが受信する全てのシグナルを受け取る単一のチャネルを返していました。この設計にはいくつかの問題点がありました。
- 非選択性: アプリケーションが特定のシグナル(例えば
SIGTERM
やSIGHUP
)のみを処理したい場合でも、Incoming
は全てのシグナルをチャネルに送出するため、不要なシグナルも処理対象となってしまい、コードが複雑になる可能性がありました。 - 単一クライアント制約:
Incoming
が返すチャネルは一つしかなく、複数の異なるコンポーネントやライブラリがそれぞれ独立してシグナルを処理したい場合に、シグナルが単一のチャネルにしか送られないため、競合や複雑なシグナル分配ロジックが必要となる問題がありました。シグナルは通常、プロセス全体に送られるものであり、それを単一のGoチャネルに集約する設計は、OSのシグナルモデルと乖離していました。 - マジックな副作用:
os/signal
パッケージをインポートするだけで、ランタイムがシグナルハンドリングを開始するという「マジックな副作用」がありました。これは、開発者が意図しない挙動を引き起こす可能性があり、コードの透明性を損ねていました。
これらの課題を解決するため、このコミットでは以下の目標が掲げられました。
- 選択的シグナルハンドリング: アプリケーションが必要なシグナルのみを購読できるようにする。
- 複数クライアント対応: 複数のGoルーチンやパッケージがそれぞれ独立してシグナルを購読できるようにする。
- 副作用の排除: パッケージのインポートによる暗黙的なシグナルハンドリングの開始をなくし、明示的なAPI呼び出しによってシグナルハンドリングを制御できるようにする。
- ランタイムコードの整理: 各OS固有のシグナルハンドリングロジックが散在していたのを、共通のUnixシグナルハンドリングコードとして
runtime/signal_unix.c
に集約し、コードの保守性と可読性を向上させる。
特に、Fixes #1266
という記述から、この変更が特定のバグや問題報告に対応するものであることがわかります。GoのIssue #1266は、os/signal
パッケージのIncoming
関数が、シグナルを一度に一つしか処理できないという問題点を指摘しており、このコミットはその問題に対する直接的な解決策を提供しています。
前提知識の解説
このコミットを理解するためには、以下の概念について基本的な知識が必要です。
1. オペレーティングシステム (OS) シグナル
OSシグナルは、Unix系OS(Linux, macOS, FreeBSDなど)において、プロセス間通信やプロセス制御のために使用されるソフトウェア割り込みの一種です。特定のイベントが発生した際に、OSがプロセスに対してシグナルを送信し、プロセスはそのシグナルを受信して定義された動作を実行します。
-
一般的なシグナル:
SIGINT
(Interrupt): 通常、Ctrl+Cによって送信され、プロセスに終了を要求します。SIGTERM
(Terminate): プロセスに正常な終了を要求します。kill
コマンドのデフォルトシグナルです。SIGHUP
(Hangup): 端末が切断された際に送信されることがありますが、デーモンプロセスでは設定ファイルの再読み込みなどの用途で利用されることが多いです。SIGKILL
(Kill): プロセスを強制終了させます。このシグナルは捕捉(キャッチ)したり無視したりすることはできません。SIGWINCH
(Window Change): 端末のウィンドウサイズが変更された際に送信されます。SIGSEGV
(Segmentation Fault): 無効なメモリアクセスが発生した際に送信されます。通常、プログラムのクラッシュを引き起こします。SIGFPE
(Floating-Point Exception): 浮動小数点演算例外が発生した際に送信されます(例: ゼロ除算)。
-
シグナルハンドリング: プロセスは、特定のシグナルを受信した際に実行する「シグナルハンドラ」と呼ばれる関数を登録できます。これにより、デフォルトの動作(プロセスの終了など)を上書きし、カスタムの処理(リソースの解放、設定の再読み込みなど)を行うことができます。
2. Go言語の並行処理とチャネル
Go言語は、並行処理をサポートするためにゴルーチン(goroutine)とチャネル(channel)という強力なプリミティブを提供しています。
- ゴルーチン: 軽量なスレッドのようなもので、Goランタイムによって管理されます。数千、数万のゴルーチンを同時に実行してもオーバーヘッドが少ないのが特徴です。
- チャネル: ゴルーチン間で値を安全に送受信するための通信メカニズムです。チャネルは型付けされており、データの競合を避けるために設計されています。シグナルハンドリングにおいては、OSから受信したシグナルをGoのアプリケーションロジックに伝えるための主要な手段としてチャネルが利用されます。
3. Goランタイム
Goランタイムは、Goプログラムの実行を管理するシステムです。これには、ガベージコレクタ、スケジューラ(ゴルーチンをOSスレッドにマッピングする)、そしてOSとのインタフェース(システムコール、シグナルハンドリングなど)が含まれます。OSシグナルは低レベルなOS機能であるため、Goプログラムがシグナルを処理するためには、ランタイムがOSからのシグナルを捕捉し、それをGoのチャネルに変換してアプリケーションに通知する役割を担う必要があります。
4. syscall
パッケージ
Goのsyscall
パッケージは、OSのシステムコールへの低レベルなインタフェースを提供します。シグナルに関連する定数(例: syscall.SIGHUP
, syscall.SIGQUIT
)や、シグナルをプロセスに送信するための関数(例: syscall.Kill
)などが含まれます。
技術的詳細
このコミットの技術的詳細は、主にos/signal
パッケージのAPI変更と、Goランタイムにおけるシグナルハンドリングの実装変更の2点に集約されます。
1. os/signal
パッケージのAPI変更
Incoming
関数の廃止: 従来のIncoming
関数は削除されました。これにより、全てのシグナルを単一のチャネルで受け取るという非選択的なモデルが廃止されます。Notify
関数の導入: 新たにNotify
関数が導入されました。
この関数は、以下の機能を提供します。func Notify(c chan<- os.Signal, sig ...os.Signal)
c chan<- os.Signal
: シグナルを受信するチャネルを指定します。このチャネルは、呼び出し側が作成し、十分なバッファサイズを持つ必要があります。Goランタイムは、このチャネルへの送信をブロックしないため、シグナルレートに追いつくためには適切なバッファリングが重要です。sig ...os.Signal
: 可変引数として、購読したい特定のシグナルを指定できます。- もし
sig
が何も指定されない場合(Notify(c)
)、全ての受信シグナルがc
にリレーされます。これは従来のIncoming
の挙動に近いですが、後述するように複数クライアントに対応しています。 - 特定のシグナルが指定された場合(例:
Notify(c, syscall.SIGHUP, syscall.SIGQUIT)
)、指定されたシグナルのみがc
にリレーされます。
- もし
- 複数クライアント対応:
Notify
関数は複数回呼び出すことができます。これにより、異なるGoルーチンやパッケージがそれぞれ独自のチャネルでシグナルを購読できるようになります。例えば、SIGTERM
を処理するGoルーチンと、SIGHUP
を処理する別のGoルーチンが同時に存在し、それぞれが独立してシグナルを受け取ることが可能です。シグナルが到着すると、そのシグナルを購読している全てのチャネルに通知が送られます。 os.Signal
インターフェース: シグナルはos.Signal
インターフェースとして表現されます。これは、syscall.Signal
型がこのインターフェースを満たすように変更されています。
2. Goランタイムにおけるシグナルハンドリングの実装変更
このコミットの最も重要な変更点の一つは、GoランタイムがOSシグナルをどのように処理するかという内部実装の変更です。
src/pkg/exp/signal
の削除とsrc/pkg/os/signal
の復活: 以前は実験的なexp/signal
パッケージにシグナルハンドリングのロジックがありましたが、これが削除され、正式なos/signal
パッケージが復活しました。runtime/signal_unix.c
への集約: 各Unix系OS(Darwin, Linux, FreeBSD, NetBSD, OpenBSD)に散在していたシグナルハンドリングの共通ロジックが、新たに作成されたsrc/pkg/runtime/signal_unix.c
に集約されました。これにより、コードの重複が減り、保守性が向上しました。- このファイルには、OSからのシグナルを捕捉し、Goランタイムの内部シグナルキューに投入する低レベルなCコードが含まれています。
signal_enable
関数(アセンブリ経由で呼び出される)は、特定のシグナルを有効化(OSにシグナルハンドラを登録)するために使用されます。signal_recv
関数(アセンブリ経由で呼び出される)は、ランタイムの内部シグナルキューからシグナルを受信するために使用されます。
SigTab
構造体の変更:src/pkg/runtime/runtime.h
で定義されているSigTab
構造体(各シグナルの特性を定義するテーブル)のフラグが変更されました。- 旧:
SigCatch
,SigIgnore
,SigRestart
,SigQueue
,SigPanic
- 新:
SigNotify
,SigKill
,SigThrow
,SigPanic
SigNotify
:os/signal
パッケージにシグナルを通知すべきかどうかを示します。SigKill
:os/signal
パッケージがシグナルを処理しない場合に、プロセスを静かに終了させるべきかどうかを示します。SigThrow
:os/signal
パッケージがシグナルを処理しない場合に、パニックを引き起こしてプロセスを終了させるべきかどうかを示します。SigPanic
: シグナルがカーネルから来た場合にパニックを引き起こすべきかどうかを示します(例:SIGSEGV
)。
- 旧:
- シグナルハンドラの変更:
runtime
パッケージ内の各OS固有のシグナルハンドラ(例:signal_darwin_386.c
のruntime·sighandler
)が更新され、新しいSigTab
フラグとSI_USER
(ユーザーが生成したシグナル)のチェックに基づいて、シグナルをos/signal
パッケージに転送するか、プロセスを終了させるか、パニックを引き起こすかを決定するようになりました。- 特に、
info->si_code != SI_USER
のチェックが追加され、カーネルが生成したシグナル(例:SIGSEGV
)とユーザーがkill
コマンドなどで生成したシグナルを区別して処理できるようになりました。
- 特に、
- アセンブリコードの追加:
src/pkg/os/signal/sig.s
というアセンブリファイルが追加され、Goのos/signal
パッケージからランタイムのsignal_enable
とsignal_recv
関数を呼び出すためのブリッジを提供しています。これは、Goのコードから直接Cのランタイム関数を呼び出すためのメカニズムです。
これらの変更により、Goのシグナルハンドリングは、よりOSのシグナルモデルに近づき、アプリケーション開発者にとってより柔軟で予測可能な挙動を提供するようになりました。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下のファイルに集中しています。
-
doc/go1.html
およびdoc/go1.tmpl
:- Go 1のリリースノートに
os/signal
パッケージの変更点が追記されています。 Incoming
からNotify
への移行方法、特に全てのシグナルを購読する場合と特定のシグナルを購読する場合のコード例が示されています。
- Go 1のリリースノートに
-
src/pkg/exp/signal/signal.go
およびsrc/pkg/exp/signal/signal_test.go
:- 実験的な
exp/signal
パッケージが完全に削除されています。これは、新しいos/signal
パッケージがその役割を引き継ぐためです。
- 実験的な
-
src/pkg/os/exec.go
およびsrc/pkg/os/exec_posix.go
:os.Signal
インターフェースが定義され、os.Interrupt
とos.Kill
というグローバル変数がsyscall.SIGINT
とsyscall.SIGKILL
に初期化されています。os.UnixSignal
型が削除され、syscall.Signal
が直接使用されるように変更されています。Process.Signal
メソッドの引数がos.Signal
インターフェースを受け取るように変更され、内部でsyscall.Signal
への型アサーションが行われています。
-
src/pkg/os/signal/sig.s
:- Goの
os/signal
パッケージからランタイムのシグナルハンドリング関数(runtime·signal_enable
とruntime·signal_recv
)を呼び出すためのアセンブリコードが追加されています。
- Goの
-
src/pkg/os/signal/signal.go
:- 新しい
os/signal
パッケージの主要な実装ファイルです。 Notify
関数が定義され、シグナルを購読するチャネルと対象シグナルを登録するロジックが含まれています。handlers
というグローバル構造体(sync.Mutex
で保護されたhandler
のリスト)が導入され、複数のシグナル購読を管理します。process
関数は、ランタイムからシグナルを受信した際に、登録されている全てのハンドラにシグナルをディスパッチする役割を担います。
- 新しい
-
src/pkg/os/signal/signal_test.go
:- 新しい
Notify
APIの動作を検証するためのテストコードが追加されています。 - 特定のシグナル(
SIGHUP
)の購読と、全てのシグナルの購読の両方がテストされています。 - 複数のチャネルへのシグナル配信も検証されています。
- 新しい
-
src/pkg/os/signal/signal_unix.go
:- Unix系OS向けの
os/signal
パッケージの補助ファイルです。 loop
ゴルーチンがsignal_recv()
を呼び出してランタイムからシグナルを受け取り、それをprocess
関数に渡す役割を担います。enableSignal
関数は、Notify
から呼び出され、ランタイムのsignal_enable
関数を介してOSに特定のシグナルハンドラを登録します。
- Unix系OS向けの
-
src/pkg/runtime/runtime.h
:SigTab
構造体のフラグ定義が変更されています(SigQueue
などが削除され、SigNotify
,SigKill
,SigThrow
が追加)。runtime·initsig
関数のシグネチャが変更され、引数がなくなっています。
-
src/pkg/runtime/sig.go
:Sigrecv
,Signame
,Siginit
といった古いシグナル関連のGo関数が削除されています。
-
src/pkg/runtime/signal_darwin_386.c
,signal_darwin_amd64.c
,signal_freebsd_386.c
,signal_freebsd_amd64.c
,signal_linux_386.c
,signal_linux_amd64.c
,signal_linux_arm.c
,signal_netbsd_386.c
,signal_netbsd_amd64.c
,signal_openbsd_386.c
,signal_openbsd_amd64.c
:- 各OS/アーキテクチャ固有のシグナルハンドラ(
runtime·sighandler
)が大幅に修正されています。 runtime·sigignore
関数が削除されています。runtime·initsig
関数が削除され、そのロジックがsignal_unix.c
や新しいランタイムの初期化フローに統合されています。SigTab
の新しいフラグ(SigNotify
,SigKill
,SigThrow
,SigPanic
)に基づいて、シグナルの処理ロジックが変更されています。特に、info->si_code != SI_USER
のチェックが追加され、カーネルが生成したシグナルとユーザーが生成したシグナルを区別して処理するようになっています。runtime·setsig
関数が導入され、シグナルハンドラの設定がより統一的に行われるようになっています。
- 各OS/アーキテクチャ固有のシグナルハンドラ(
-
src/pkg/runtime/signal_unix.c
:- Unix系OSにおける共通のシグナルハンドリングロジックがここに集約されています。
sigtramp
(シグナルハンドラのエントリポイント)から呼び出されるsighandler
が、Goランタイムの内部シグナルキューにシグナルを投入する役割を担います。sigqueue
(シグナルキュー)の管理ロジックが含まれています。
これらの変更は、Goのシグナルハンドリングのアーキテクチャを根本的に変更し、より現代的で柔軟な設計へと進化させています。
コアとなるコードの解説
ここでは、このコミットにおける最も重要なコード変更とその意味について詳しく解説します。
1. src/pkg/os/signal/signal.go
- Notify
関数とシグナルディスパッチ
// src/pkg/os/signal/signal.go
package signal
import (
"os"
"sync"
)
var handlers struct {
sync.Mutex
list []handler
}
type handler struct {
c chan<- os.Signal
sig os.Signal
all bool
}
// Notify causes package signal to relay incoming signals to c.
// If no signals are listed, all incoming signals will be relayed to c.
// Otherwise, just the listed signals will.
//
// Package signal will not block sending to c: the caller must ensure
// that c has sufficient buffer space to keep up with the expected
// signal rate. For a channel used for notification of just one signal value,
// a buffer of size 1 is sufficient.
//
func Notify(c chan<- os.Signal, sig ...os.Signal) {
if c == nil {
panic("os/signal: Notify using nil channel")
}
handlers.Lock()
defer handlers.Unlock()
if len(sig) == 0 {
enableSignal(nil) // ask for all signals
handlers.list = append(handlers.list, handler{c: c, all: true})
} else {
for _, s := range sig {
// We use nil as a special wildcard value for enableSignal,
// so filter it out of the list of arguments. This is safe because
// we will never get an incoming nil signal, so discarding the
// registration cannot affect the observed behavior.
if s != nil {
enableSignal(s) // ask for specific signal
handlers.list = append(handlers.list, handler{c: c, sig: s})
}
}
}
}
func process(sig os.Signal) {
handlers.Lock()
defer handlers.Unlock()
for _, h := range handlers.list {
if h.all || h.sig == sig {
// send but do not block for it
select {
case h.c <- sig:
default:
// If the channel is full, we drop the signal.
// This is why the caller must ensure sufficient buffer space.
}
}
}
}
handlers
構造体:Notify
によって登録された全てのシグナルハンドラ(handler
型)を管理します。sync.Mutex
によって保護されており、複数のゴルーチンからの同時アクセスを安全に処理します。handler
型: シグナルを受信するチャネル(c
)、購読対象の特定のシグナル(sig
)、または全てのシグナルを購読するかどうかを示すフラグ(all
)を保持します。Notify
関数:- ユーザーがシグナルを受信するチャネル
c
と、購読したいシグナルsig
(可変引数)を渡します。 len(sig) == 0
の場合、enableSignal(nil)
を呼び出し、全てのシグナルを有効化するようランタイムに要求します。そして、all: true
のハンドラを登録します。- 特定のシグナルが指定された場合、それぞれのシグナルに対して
enableSignal(s)
を呼び出し、そのシグナルを有効化するようランタイムに要求します。そして、sig: s
のハンドラを登録します。 enableSignal
は、Goランタイムの低レベルな関数を呼び出し、OSレベルでそのシグナルに対するハンドラを登録する役割を担います。
- ユーザーがシグナルを受信するチャネル
process
関数:- この関数は、Goランタイムからシグナルが到着した際に呼び出されます。
handlers.list
に登録されている全てのハンドラをイテレートします。- 各ハンドラについて、
h.all
がtrue
であるか、またはh.sig
が受信したシグナルsig
と一致する場合、そのハンドラのチャネルh.c
にシグナルを送信します。 select
文のdefault
ケースを使用することで、チャネルが満杯でブロックされることを防いでいます。これは、Notify
のドキュメントにある「Package signal will not block sending to c」という保証を実現するための重要な実装です。もしチャネルが満杯であれば、シグナルは破棄されます。
2. src/pkg/os/signal/signal_unix.go
- ランタイムとの連携
// src/pkg/os/signal/signal_unix.go
package signal
import (
"os"
"syscall"
)
// In assembly.
func signal_enable(uint32)
func signal_recv() uint32
func loop() {
for {
process(syscall.Signal(signal_recv()))
}
}
func init() {
signal_enable(0) // first call - initialize
go loop()
}
func enableSignal(sig os.Signal) {
switch sig := sig.(type) {
case nil:
signal_enable(^uint32(0)) // enable all signals
case syscall.Signal:
signal_enable(uint32(sig)) // enable specific signal
default:
// Can ignore: this signal (whatever it is) will never come in.
}
}
signal_enable(uint32)
とsignal_recv() uint32
: これらはアセンブリで実装された関数であり、Goランタイムの低レベルなシグナルハンドリング機能へのGo側からのインタフェースです。signal_enable
: OSレベルで特定のシグナルに対するハンドラを有効化します。引数としてシグナル番号のビットマスクを受け取ります。^uint32(0)
は全てのビットが1のマスクであり、全てのシグナルを有効化することを意味します。signal_recv
: Goランタイムの内部シグナルキューから、次に到着したシグナル番号をブロックして受け取ります。
loop()
関数:- 無限ループで
signal_recv()
を呼び出し、Goランタイムからシグナルを受信します。 - 受信したシグナル(
syscall.Signal
型にキャスト)を、os/signal
パッケージのprocess
関数に渡します。これにより、process
関数が登録されたハンドラにシグナルをディスパッチします。
- 無限ループで
init()
関数:- パッケージが初期化される際に一度だけ実行されます。
signal_enable(0)
を呼び出し、ランタイムのシグナルハンドリング機構を初期化します。go loop()
によって、loop
関数を新しいゴルーチンとして起動します。これにより、シグナル受信がバックグラウンドで非同期に行われ、メインのアプリケーションロジックをブロックしません。
enableSignal
関数:os/signal
パッケージのNotify
関数から呼び出されます。- 引数
sig
がnil
の場合(全てのシグナルを購読する場合)、signal_enable(^uint32(0))
を呼び出して全てのシグナルを有効化します。 syscall.Signal
型の場合、そのシグナル番号をuint32
にキャストしてsignal_enable
に渡し、特定のシグナルを有効化します。
3. src/pkg/runtime/signal_unix.c
- 低レベルなシグナルハンドリング
// src/pkg/runtime/signal_unix.c (抜粋)
// ...
// sigtramp is the signal handler called from the kernel.
// It is written in assembly and calls sighandler.
extern void sigtramp(void);
// sighandler is the C function called by sigtramp.
// It receives the signal and puts it into the Go signal queue.
void
sighandler(int32 sig, Siginfo *info, void *context, G *gp)
{
// ... (context saving, stack switching, etc.)
// Put the signal into the Go signal queue.
// This is a non-blocking operation.
if (sigsend(sig)) {
return;
}
// If sigsend failed (queue full), and it's a signal that
// should cause a panic or exit, do so.
SigTab *t = &sigtab[sig];
if (info->si_code != SI_USER && (t->flags & SigPanic)) {
// ... (handle panic)
}
if (t->flags & SigKill) {
exit(2); // exit quietly
}
if (!(t->flags & SigThrow)) {
return; // ignore
}
// ... (throw a panic)
}
// sigsend puts a signal into the Go signal queue.
// Returns true if the signal was successfully queued.
bool
sigsend(int32 sig)
{
// ... (queue management logic)
// This function adds 'sig' to a circular buffer (queue)
// and wakes up any goroutines waiting on signal_recv.
// If the queue is full, it returns false.
}
// signal_enable enables or disables a signal.
// This is called from Go via assembly.
void
signal_enable(uint32 sig)
{
// ... (logic to set OS signal handler using sigaction)
// If sig is 0xFFFFFFFF (all bits set), it enables all signals.
// Otherwise, it enables the specific signal 'sig'.
// It sets the signal handler to sigtramp.
}
// signal_recv receives a signal from the Go signal queue.
// This is called from Go via assembly.
uint32
signal_recv(void)
{
// ... (logic to read from circular buffer and block if empty)
// This function waits for a signal to be available in the queue
// and returns its number.
}
sigtramp
: OSがシグナルを送信した際に最初に呼び出されるアセンブリで書かれたエントリポイントです。これは、Goランタイムのスタックやレジスタの状態を適切に保存し、C言語で書かれたsighandler
関数を呼び出す役割を担います。sighandler
:sigtramp
から呼び出されるC関数で、OSから受け取ったシグナル(sig
)とシグナル情報(info
)を処理します。sigsend(sig)
を呼び出し、受信したシグナルをGoランタイム内部のシグナルキューに投入しようとします。sigsend
は非ブロッキング操作であり、キューが満杯の場合はfalse
を返します。SigTab
のフラグ(SigPanic
,SigKill
,SigThrow
)とinfo->si_code
(シグナルの発生源、SI_USER
はユーザーが生成したシグナル)をチェックし、シグナルがキューに投入できなかった場合のフォールバック動作を決定します。これにより、カーネルが生成した致命的なシグナル(例:SIGSEGV
)がos/signal
パッケージで処理されなかった場合に、適切にパニックや終了を引き起こすことができます。
sigsend
: Goランタイム内部のシグナルキュー(通常はリングバッファ)にシグナルを書き込む関数です。このキューは、OSからGoランタイムへのシグナル伝達のバッファとして機能します。signal_enable
: OSのsigaction
システムコールを呼び出し、特定のシグナルに対するシグナルハンドラとしてsigtramp
を登録します。これにより、OSがそのシグナルをプロセスに送信した際に、Goランタイムがそれを捕捉できるようになります。signal_recv
:sigsend
によって書き込まれたシグナルを、Goのloop
ゴルーチンが読み出すための関数です。キューが空の場合、この関数はブロックし、シグナルが到着するまで待機します。
これらの低レベルなCコードとアセンブリコードが、OSとGoランタイムの間のシグナル伝達の橋渡しを行い、最終的にGoのチャネルを通じてアプリケーションにシグナルが通知される仕組みを構築しています。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- Go 1リリースノート: https://go.dev/doc/go1 (このコミットで更新された
os/signal
に関する記述が含まれています) - Go Issue #1266: https://go.dev/issue/1266 (このコミットが修正した問題)
- Go CL 3749041: https://golang.org/cl/3749041 (このコミットのコードレビューページ)
参考にした情報源リンク
- Go言語のソースコード (上記GitHub URL)
- Go 1リリースノート (上記URL)
- Go Issue Tracker (上記URL)
- Unixシグナルに関する一般的な情報 (例:
man 7 signal
) - Go言語の並行処理に関する一般的な情報 (例: Go言語の公式チュートリアル、Effective Go)
- Goランタイムの内部構造に関する情報 (Goのソースコードや関連するブログ記事、書籍など)