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

[インデックス 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という関数が存在し、これはプログラムが受信する全てのシグナルを受け取る単一のチャネルを返していました。この設計にはいくつかの問題点がありました。

  1. 非選択性: アプリケーションが特定のシグナル(例えばSIGTERMSIGHUP)のみを処理したい場合でも、Incomingは全てのシグナルをチャネルに送出するため、不要なシグナルも処理対象となってしまい、コードが複雑になる可能性がありました。
  2. 単一クライアント制約: Incomingが返すチャネルは一つしかなく、複数の異なるコンポーネントやライブラリがそれぞれ独立してシグナルを処理したい場合に、シグナルが単一のチャネルにしか送られないため、競合や複雑なシグナル分配ロジックが必要となる問題がありました。シグナルは通常、プロセス全体に送られるものであり、それを単一のGoチャネルに集約する設計は、OSのシグナルモデルと乖離していました。
  3. マジックな副作用: 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.cruntime·sighandler)が更新され、新しいSigTabフラグとSI_USER(ユーザーが生成したシグナル)のチェックに基づいて、シグナルをos/signalパッケージに転送するか、プロセスを終了させるか、パニックを引き起こすかを決定するようになりました。
    • 特に、info->si_code != SI_USERのチェックが追加され、カーネルが生成したシグナル(例: SIGSEGV)とユーザーがkillコマンドなどで生成したシグナルを区別して処理できるようになりました。
  • アセンブリコードの追加: src/pkg/os/signal/sig.sというアセンブリファイルが追加され、Goのos/signalパッケージからランタイムのsignal_enablesignal_recv関数を呼び出すためのブリッジを提供しています。これは、Goのコードから直接Cのランタイム関数を呼び出すためのメカニズムです。

これらの変更により、Goのシグナルハンドリングは、よりOSのシグナルモデルに近づき、アプリケーション開発者にとってより柔軟で予測可能な挙動を提供するようになりました。

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

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

  1. doc/go1.html および doc/go1.tmpl:

    • Go 1のリリースノートにos/signalパッケージの変更点が追記されています。
    • IncomingからNotifyへの移行方法、特に全てのシグナルを購読する場合と特定のシグナルを購読する場合のコード例が示されています。
  2. src/pkg/exp/signal/signal.go および src/pkg/exp/signal/signal_test.go:

    • 実験的なexp/signalパッケージが完全に削除されています。これは、新しいos/signalパッケージがその役割を引き継ぐためです。
  3. src/pkg/os/exec.go および src/pkg/os/exec_posix.go:

    • os.Signalインターフェースが定義され、os.Interruptos.Killというグローバル変数がsyscall.SIGINTsyscall.SIGKILLに初期化されています。
    • os.UnixSignal型が削除され、syscall.Signalが直接使用されるように変更されています。
    • Process.Signalメソッドの引数がos.Signalインターフェースを受け取るように変更され、内部でsyscall.Signalへの型アサーションが行われています。
  4. src/pkg/os/signal/sig.s:

    • Goのos/signalパッケージからランタイムのシグナルハンドリング関数(runtime·signal_enableruntime·signal_recv)を呼び出すためのアセンブリコードが追加されています。
  5. src/pkg/os/signal/signal.go:

    • 新しいos/signalパッケージの主要な実装ファイルです。
    • Notify関数が定義され、シグナルを購読するチャネルと対象シグナルを登録するロジックが含まれています。
    • handlersというグローバル構造体(sync.Mutexで保護されたhandlerのリスト)が導入され、複数のシグナル購読を管理します。
    • process関数は、ランタイムからシグナルを受信した際に、登録されている全てのハンドラにシグナルをディスパッチする役割を担います。
  6. src/pkg/os/signal/signal_test.go:

    • 新しいNotify APIの動作を検証するためのテストコードが追加されています。
    • 特定のシグナル(SIGHUP)の購読と、全てのシグナルの購読の両方がテストされています。
    • 複数のチャネルへのシグナル配信も検証されています。
  7. src/pkg/os/signal/signal_unix.go:

    • Unix系OS向けのos/signalパッケージの補助ファイルです。
    • loopゴルーチンがsignal_recv()を呼び出してランタイムからシグナルを受け取り、それをprocess関数に渡す役割を担います。
    • enableSignal関数は、Notifyから呼び出され、ランタイムのsignal_enable関数を介してOSに特定のシグナルハンドラを登録します。
  8. src/pkg/runtime/runtime.h:

    • SigTab構造体のフラグ定義が変更されています(SigQueueなどが削除され、SigNotify, SigKill, SigThrowが追加)。
    • runtime·initsig関数のシグネチャが変更され、引数がなくなっています。
  9. src/pkg/runtime/sig.go:

    • Sigrecv, Signame, Siginitといった古いシグナル関連のGo関数が削除されています。
  10. 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関数が導入され、シグナルハンドラの設定がより統一的に行われるようになっています。
  11. 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.alltrueであるか、または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関数から呼び出されます。
    • 引数signilの場合(全てのシグナルを購読する場合)、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言語のソースコード (上記GitHub URL)
  • Go 1リリースノート (上記URL)
  • Go Issue Tracker (上記URL)
  • Unixシグナルに関する一般的な情報 (例: man 7 signal)
  • Go言語の並行処理に関する一般的な情報 (例: Go言語の公式チュートリアル、Effective Go)
  • Goランタイムの内部構造に関する情報 (Goのソースコードや関連するブログ記事、書籍など)