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.1所示,信号的默认处理方式有如下几种:
- Term:终止进程
- Ign:忽略信号
- Core:终止进程并转储核心
- Stop:停止进程
- Cont:如果进程当前已停止,则继续进程
需要注意的是:
- SIGKILL 和 SIGSTOP 信号不可捕获、阻塞或忽略
- SIGILL 和 SIGTRAP 信号不能恢复至默认动作
- SIGABRT、SIGBUS、SIGFPE、SIGILL、SIGIOT、SIGQUIT、SIGSEGV、SIGTRAP、SIGXCPU、SIGXFSZ 信号默认会导致进程流产、
- SIGALRM、SIGHUP、SIGINT、SIGKILL、SIGPIPE、SIGPOLL、SIGPROF、SIGSYS、SIGTERM、SIGUSR1、SIGUSR2、SIGVTALRM 信号默认会导致进程退出
- SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU 信号默认会导致进程停止
- 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
,再调用 Ignore
,Notify
的效果会被取消;如果先调用 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 不会再收到信号。