什么是信号

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

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

Linux中信号的介绍

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

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

Linux信号与golang中的捕获处理

信号表

POSIX.1-1990标准信号

此表参考自:POSIX信号

信号 动作 说明
SIGHUP 1 Term 终端控制进程结束(终端连接断开)
SIGINT 2 Term 用户发送INTR字符(Ctrl+C)触发
SIGQUIT 3 Core 用户发送QUIT字符(Ctrl+/)触发
SIGILL 4 Core 非法指令(程序错误、试图执行数据段、栈溢出等)
SIGABRT 6 Core 调用abort函数触发
SIGFPE 8 Core 算术运行错误(浮点运算错误、除数为零等)
SIGKILL 9 Term 无条件结束程序(不能被捕获、阻塞或忽略)
SIGSEGV 11 Core 无效内存引用(试图访问不属于自己的内存空间、对只读内存空间进行写操作)
SIGPIPE 13 Term 消息管道损坏(FIFO/Socket通信时,管道未打开而进行写操作)
SIGALRM 14 Term 时钟定时信号
SIGTERM 15 Term 结束程序(可以被捕获、阻塞或忽略)
SIGUSR1 30,10,16 Term 用户保留
SIGUSR2 31,12,17 Term 用户保留
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 后台程序向终端中写数据时触发

更多的信号说明请查阅man7

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

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

信号的产生

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

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

killraise

发送的信号

Ctrl-CCtrl-ZCtrl-\

信号的处理

内核处理进程收到的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-a-p-s-u

killall

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

在Go语言中的Signal的处理

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

kill -15kill -4
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

Linux信号与golang中的捕获处理

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

Linux信号与golang中的捕获处理

Linux信号与golang中的捕获处理

Go进程间通讯