什么是信号

在计算机科学中,信号是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。

当一个信号发送给一个进程,操作系统中断了进程正常的控制流程,如果进程定义了对信号的处理,此时,程序将进入捕获到的信号对应的处理函数,否则执行默认的处理函数。

Linux中信号的介绍

在Linux系统共定义了64种信号,分为两大类:实时信号非实时信号,1-31为非实时,32-64种为实时信号。

  • 非实时信号: 也称为不可靠信号,为早期Linux所支持的信号,不支持排队,信号可能会丢失, 比如发送多次相同的信号, 进程只能收到一次. 信号值取值区间为1~31;
  • 实时信号: 也称为可靠信号,支持排队, 信号不会丢失, 发多少次, 就可以收到多少次. 信号值取值区间为32~64

Linux操作系统中,在终端上执行 kill -l 便可看到系统定义的所有信号

信号表

POSIX.1-1990标准信号

此表参考自:POSIX信号

信号动作说明
SIGHUP1Term终端控制进程结束(终端连接断开)
SIGINT2Term用户发送INTR字符(Ctrl+C)触发
SIGQUIT3Core用户发送QUIT字符(Ctrl+/)触发
SIGILL4Core非法指令(程序错误、试图执行数据段、栈溢出等)
SIGABRT6Core调用abort函数触发
SIGFPE8Core算术运行错误(浮点运算错误、除数为零等)
SIGKILL9Term无条件结束程序(不能被捕获、阻塞或忽略)
SIGSEGV11Core无效内存引用(试图访问不属于自己的内存空间、对只读内存空间进行写操作)
SIGPIPE13Term消息管道损坏(FIFO/Socket通信时,管道未打开而进行写操作)
SIGALRM14Term时钟定时信号
SIGTERM15Term结束程序(可以被捕获、阻塞或忽略)
SIGUSR130,10,16Term用户保留
SIGUSR231,12,17Term用户保留
SIGCHLD20,17,18Ign子进程结束(由父进程接收)
SIGCONT19,18,25Cont继续执行已经停止的进程(不能被阻塞)
SIGSTOP17,19,23Stop停止进程(不能被捕获、阻塞或忽略)
SIGTSTP18,20,24Stop停止进程(可以被捕获、阻塞或忽略)
SIGTTIN21,21,26Stop后台程序从终端中读取数据时触发
SIGTTOU22,22,27Stop后台程序向终端中写数据时触发

更多的信号说明请查阅man7

此表的操作为每个信号的默认配置,如下所示

动作说明
Term默认操作是,终止进程。
Ign默认操作是,忽略信号。
Core默认操作是,终止该进程并核心转储
Stop默认操作是,停止进程。
Cont默认操作是,如果当前已停止,则继续该进程。

信号的产生

信号是事件发生时对进程的通知机制。信号中断与硬件中断的相似之处在于打断了程序执行的正常流程。

信号事件的来源分为软件信号和硬件信号:

  • 硬件信号: 用户输入:比如在终端上按下组合键ctrl+C,产生SIGINT信号;硬件异常:CPU检测到内存非法访问等异常,通知内核生成相应信号,并发送给发生事件的进程;
  • 软件信号: 通过系统调用: 如,发送signal信号:killraise等。

发送的信号

  • Ctrl-C 发送 INT signal (SIGINT),通常导致进程结束
  • Ctrl-Z 发送 TSTP signal (SIGTSTP); 通常导致进程挂起(suspend)
  • Ctrl-\ 发送 QUIT signal (SIGQUIT); 通常导致进程结束 和 dump core.

信号的处理

内核处理进程收到的signal是在当前进程的上下文,故进程必须是Running状态。当进程唤醒或者调度后获取CPU,则会从内核态转到用户态时检测是否有signal等待处理,处理完,进程会把相应的未决信号从链表中去掉。

signal信号处理时机: 内核 ==> 信号处理 ==> 用户

  1. 内核态:在内核态,signal信号不起作用;
  2. signal信号处理: 在用户态,signal所有未被屏蔽的信号都处理完毕;当屏蔽信号,取消屏蔽时,会在下一次内核转用户态的过程中执行;

信号处理方式

进程对信号的处理方式有3种:

  • 默认 接收到信号后按默认的行为处理该信号。 这种方式为多数应用采取的处理方式。
  • 自定义处理 用自定义的信号处理函数来执行特定的动作
  • 忽略忽略信号 接收到信号后不做任何反应。

对信号的处理动作:

  • Term: 中止进程
  • Ign: 忽略信号
  • Core: 中止进程并保存内存信息
  • Stop: 停止进程
  • Cont: 继续运行进程

Linux信号命令

kill

kill命令用来终止指定的进程, 对于一个后台进程就须用kill命令来终止,我们就需要先使用ps/pidof/pstree/top等工具获取进程PID,然后使用kill命令来杀掉该进程。

命令格式 kill[参数] [进程id]

命令参数 -l 信号,若果不加信号的编号参数,则使用“-l”参数会列出全部的信号名称 -a 当处理当前进程时,不限制命令名和进程号的对应关系 -p 指定kill 命令只打印相关进程的进程号,而不发送任何信号 -s 指定发送信号 -u 指定用户

killall

Linux系统中的killall用于杀死指定名字的进程(kill processes by name)。我们可以使用kill命令杀死指定进程PID的进程,如果要找到我们需要杀死的进程,我们还需要在之前使用ps等命令再配合grep来查找进程,而killall把这两个过程合二为一,是一个很好用的命令。

命令格式 killall[参数] [进程名]

命令参数 -I 忽略小写 -a 当处理当前进程时,不限制命令名和进程号的对应关系 -i 交互模式,杀死进程前先询问用户 -s 发送指定的信号 -w 等待进程死亡 -e 要求匹配进程名称

PKILL

pkillkillall 使用方法类似,用于杀死指定名称的进程

Go语言中的Signal的使用

在Go语言中,处理信号仅需要3个步骤即可完成对信号的处理

  • 信号的接收: signalChan := make(chan os.Signal,1)
  • 信号的监听捕获: signal.Notify(signalChan)
  • 信号的触发: signal := <-signalChan

注意事项:

  • SIGKILL kill -9SIGSTOP kill -19 信号可能不会被Notify方法捕获,因此无法处理这些信号。
  • 如果在Notify方法中没有指定信号作为参数,那么该方法将捕获所有的信号。

在Go语言中的Signal的处理

在某些场景下,如,在大量并发及,批量处理未完成时,此时需要在Go程序中处理Signal信号,比如收到SIGTERM信号后优雅的关闭程序。

实例:在一个计算场景下,有5个goroutine在处理业务,当收到 kill -15时计算完成后退出程序, kill -4不做处理。

go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package main

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

var wg sync.WaitGroup

func exitProcess() {
	fmt.Println("等待进程完成")
	wg.Wait()
	fmt.Println("进程退出")
}

func process(n int) {
	i := n
	for {
		fmt.Println("process", n, ":", i)
		if i > 100 {
			break
		}
		time.Sleep(time.Second)
		i++
	}
	fmt.Println("process", n, "finnshed")
	defer wg.Done()
}

func main() {
	signals := make(chan os.Signal, 1)
	done := make(chan bool, 1)

	signal.Notify(signals, syscall.SIGILL, syscall.SIGTERM)

	go func() {
		for signal := range signals {
			switch signal {
			case syscall.SIGTERM, syscall.SIGQUIT:
				fmt.Println("kill -15 进程退出")
				exitProcess()
			case syscall.SIGILL:
				fmt.Println("kill -4")
			}
		}
		done <- true
	}()

	wg.Add(5)
	for n := 0; n < 10; n++ {
		go process(n)
	}

	fmt.Println("waiting signal...")
	wg.Wait()
	fmt.Println("exiting")
}

收到kill -4 信号打印kill -4

收到kill -15 信号后,带程序处理完成后退出