Go信号监听

Wednesday, May 4, 2022

1. 信号

1.1 简介

信号(signal)是一种软中断,信号机制是进程间通信的一种方式,采用异步通信方式。Linux 系统的信号分为两大类:

  • 不可靠信号:取值区间为 1-31,也称为非实时信号,不支持排队,信号可能会丢失,如果发送多次相同信号,进程只能收到1次
  • 可靠信号:取值区间为 34-64,也称为实时信号,支持排队,信号不会丢失,发多少次就会接收多少次

每个信号都有一个名字和编号,名字都由 SIG 打头,可以使用 kill -l 命令查看素有的信号,如下:

root@ubuntu:~# kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX

信号的详细定义如下:

信号 取值 默认动作 含义(发出信号的原因)
POSIX.1-1990标准信号
SIGHUP 1 Term 终端的挂断或进程死亡
SIGINT 2 Term 来自键盘的中断信号
SIGQUIT 3 Core 来自键盘的离开信号
SIGILL 4 Core 非法指令
SIGABRT 6 Core 来自 abort 的异常信号
SIGFPE 8 Core 浮点例外
SIGKILL 9 Term 杀死
SIGSEGV 11 Core 段非法错误 (内存引用无效)
SIGPIPE 13 Term 管道损坏:向一个没有读进程的管道写数据
SIGALRM 14 Term 来自 alarm 的计时器到时信号
SIGTERM 15 Term 终止
SIGUSR1 30,10,16 Term 用户自定义信号 1
SIGUSR2 31,12,17 Term 用户自定义信号 2
SIGCHLD 20,17,18 Ign 子进程停止或终止
SIGCONT 19,18,25 Cont 如果停止,继续执行
SIGSTOP 17,19,23 Stop 非来自终端的停止信号
SIGTSTP 18,20,24 Stop 来自终端的停止信号
SIGTTIN 21,21,26 Stop 后台进程读终端
SIGTTOU 22,22,27 Stop 后台进程写终端
SUSv2和POSIX.1-2001定义的信号
SIGBUS 10,7,10 Core 总线错误(内存访问错误)
SIGPOLL Term Pollable 事件发生 (Sys V),与 SIGIO 同义
SIGPROF 27,27,29 Term 统计分布图用计时器到时
SIGSYS 12,-,12 Core 非法系统调用 (SVr4)
SIGTRAP 5 Core 跟踪 / 断点自陷
SIGURG 16,23,21 Ign socket 紧急信号 (4.2BSD)
SIGVTALRM 26,26,28 Term 虚拟计时器到时 (4.2BSD)
SIGXCPU 24,24,30 Core 超过 CPU 时限 (4.2BSD)
SIGXFSZ 25,25,31 Core 超过文件长度限制 (4.2BSD)
其他常见信号
SIGIOT 6 Core IOT 自陷,与 SIGABRT 同义
SIGEMT 7,-,7 Term
SIGSTKFLT -,16,- Term 协处理器堆栈错误 (不使用)
SIGIO 23,29,22 Term 描述符上可以进行 I/O 操作
SIGCLD -,-,18 Ign 与 SIGCHLD 同义
SIGPWR 29,30,19 Term 电力故障 (System V)
SIGINFO 29,-,- 与 SIGPWR 同义
SIGLOST -,-,- Term 文件锁丢失
SIGWINCH 28,28,20 Ign 窗口大小改变 (4.3BSD, Sun)
SIGUNUSED -,31,- Term 未使用信号 (will be SIGSYS)

1.2 信号的处理

信号的处理一般有三种方式:

  1. 忽略信号,大多数信号都采用该方式处理
  2. 捕捉信号,需要告诉内核用户需要处理某一种信号,简单理解就是写一段函数,当信号发生的之后执行
  3. 系统默认动作:对每一种信号,系统都有默认的执行动作,当系统发生的时候,系统会自动执行

每一个信号都有自己的默认动作,如1.1所示,信号的默认处理方式有如下几种:

  1. Term:终止进程
  2. Ign:忽略信号
  3. Core:终止进程并转储核心
  4. Stop:停止进程
  5. Cont:如果进程当前已停止,则继续进程

需要注意的是:

  1. SIGKILL 和 SIGSTOP 信号不可捕获、阻塞或忽略
  2. SIGILL 和 SIGTRAP 信号不能恢复至默认动作
  3. SIGABRT、SIGBUS、SIGFPE、SIGILL、SIGIOT、SIGQUIT、SIGSEGV、SIGTRAP、SIGXCPU、SIGXFSZ 信号默认会导致进程流产、
  4. SIGALRM、SIGHUP、SIGINT、SIGKILL、SIGPIPE、SIGPOLL、SIGPROF、SIGSYS、SIGTERM、SIGUSR1、SIGUSR2、SIGVTALRM 信号默认会导致进程退出
  5. SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU 信号默认会导致进程停止
  6. SIGCHLD、SIGPWR、SIGURG、SIGWINCH 信号默认会被进程忽略

详细的信号说明请查看 signal(7)

1.3 Mac 与 Linux 几个信号值

信号 Mac Linux
INT 2 2
QUIT 3 3
KILL 9 9
TERM 15 15
STOP 17 19
CONT 19 18

2. Go与信号

下面是一个信号监听的示例,监听了 TERM、INT、QUIT、CONT 四个信号,在收到信号的时候打印信号内容之后再退出。

package main

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
)

func main() {
	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGCONT)
	done := make(chan bool, 1)
	go func() {
		sig := <-sigs
		fmt.Println("signal:", sig)
		done <- true
	}()
	fmt.Println("awating signal")
	<-done
	fmt.Println("exiting")
}

build 命令:

go build -o signal main.go

build 后运行该程序,之后我们尝试使用不同的信号值 kill 该进程,观察打印的内容。

本次测试再 Mac 系统下,所以 STOP 信号传的是 17,不是 19,CONT 信号传的是 19,不是 18

2.1 INT

运行该程序,并查询进程号:

➜  signal ./signal
awating signal


➜  ~ ps -ef | grep signal
  501 42114 40541   0  9:54上午 ttys006    0:00.01 ./signal

使用 INT 信号终端该进程:

➜  ~ kill -2 42114

程序打印内容:

➜  signal ./signal
awating signal
signal: interrupt
exiting

2.2 QUIT

运行该程序,并查询进程号:

➜  signal ./signal
awating signal


➜  ~ ps -ef | grep signal
  501 42185 40541   0  9:56上午 ttys006    0:00.00 ./signal

使用 QUIT 信号终端该进程:

➜  ~ kill -3 42185

程序打印内容:

➜  signal ./signal
awating signal
signal: quit
exiting

2.3 TERM

运行该程序,并查询进程号:

➜  signal ./signal
awating signal


➜  ~ ps -ef | grep signal
  501 42264 40541   0  9:57上午 ttys006    0:00.00 ./signal

使用 TERM 信号终端该进程:

➜  ~ kill -15 42264

程序打印内容:

➜  signal ./signal
awating signal
signal: terminated
exiting

2.4 KILL

运行该程序,并查询进程号:

➜  signal ./signal
awating signal


➜  ~ ps -ef | grep signal
  501 42346 40541   0  9:57上午 ttys006    0:00.00 ./signal

使用 KILL 信号终端该进程:

➜  ~ kill -9 42346

程序打印内容,可以发现程序收不到捕获不到 KILL 信号

➜  signal ./signal
awating signal
[1]    42346 killed     ./signal

2.5 STOP & CONT

运行该程序,并查询进程号:

➜  signal ./signal
awating signal


➜  ~ ps -ef | grep signal
  501 42405 40541   0  9:57上午 ttys006    0:00.00 ./signal

使用 STOP 信号终端该进程:

➜  ~ kill -17 42405

程序打印内容,可以发现程序收不到捕获不到 STOP 信号

➜  signal ./signal
awating signal
[1]  + 42405 suspended (signal)  ./signal

使用 CONT 信号恢复该进程:

➜  ~ kill -19 42405

程序打印内容,可以发现程序捕获到了 CONT 信号并进行了打印:

➜  signal signal: continued
exiting

[1]  + 42704 done       ./signal

3. signal 包

信号 SIGKILL 和 SIGSTOP 可能不会被程序捕获,因此 signal 包不会处理这两个信号.

3.1 Ignore

func Ignore(sig ... os . Signal )

忽略一个、多个或全部(不提供任何信号)信号。如果程序接收到了被忽略的信号,则什么也不做。对一个信号,如果先调用 Notify,再调用 IgnoreNotify 的效果会被取消;如果先调用 Ignore,在调用 Notify,接着调用 Reset/Stop 的话,会回到 Ingore 的效果。注意,如果 Notify 作用于多个 chan,则 Stop 需要对每个 chan 都调用才能起到该作用。

3.2 Ignored

func Ignored(sig os . Signal ) bool

判断当前是否忽略了指定的信号。

3.3 Notify

func Notify(c chan<- os . Signal , sig ... os . Signal )

使包信号将传入信号中继到 c。如果没有提供信号,所有传入的信号将被中继到 c。否则,只有提供的信号会。

包信号不会阻塞发送到 c:调用者必须确保 c 有足够的缓冲区空间来跟上预期的信号速率。对于仅用于通知一个信号值的通道,大小为 1 的缓冲区就足够了。

允许使用同一通道多次调用 Notify:每次调用都会扩展发送到该通道的信号集。从集合中移除信号的唯一方法是调用 Stop。

允许使用不同的通道和相同的信号多次调用 Notify:每个通道独立接收传入信号的副本。

3.4 Reset

func Reset(sig ... os . Signal )

撤消先前为提供的信号调用 Notify 的效果。如果未提供任何信号,则所有信号处理程序都将被重置。

3.5 Stop

func Stop(c chan<- os . Signal )

包信号停止将传入信号中继到 c。它取消了之前使用 c 调用 Notify 的所有效果。当 Stop 返回时,保证 c 不会再收到信号。

Go Signal Go

Go与ContextKafka学习笔记