【系统深入学习GO】signal 浅析

操作系统信号 (signal) 是 IPC (InterProcess Communication 进程间通信) 中唯一一种异步的通信方法,它的本质是用软件来模拟硬件的中断机制。用来通知某个进程有某个事件发生了。
在 linux 中我们可以用kill命令来查看当前系统所支持的信号,如下图:
在这里插入图片描述
可以看到,linux支持的信号有62种 (注意,没有编号32和33的信号)。其中编号从 1 到 31 的信号属于标准信号 (也称不靠谱信号),而编号从 34 到 64 的信号属于实时信号 (也称靠谱信号)。对于同一进程来说,每种标准信号只会被记录并处理一次。并且如果发送给某个进程的标准信号的种类有多个,那么它们的处理顺序也是完全不确定的。而实时信号解决了标准信号的两个问题,即多个同种类的实时信号都可以记录在案,并且他们可以按照信号的发送顺序被处理。虽然实时信号在功能上更为强大,但是已成为事实标准信号也无法被替换掉。因此,这两大类信号一直共存着。

而 Go 中信号要从 os.Signal 讲起,该类型的声明如下:

type Signal interface {
	String() string
	Signal() // to distinguish from other Stringers
}

从 so.Signal 接口的声明可知,其中 Signal 方法的声明并没有实际意义。它只是作为 os.Signal 接口类型的一个标识。因此,在 Go 标准库中,所有实现它的类型的 Signal 方法都是空方法。所以实现此接口类型的值都可以表示一个操作系统信号。

在 Go 标准库中,已经包含了与不同操作系统的信号相对应的程序实体。具体来说,标准库代码包 syscall 中有与不同操作系统所支持的每一个标准信号对应的同名常量 (简称信号常量)。这些信号常量的类型都是 syscall.Signalsyscall.Signalos.Signal 接口的一个实现类型,同时也是一个 int 类型的别名类型。

另外,如果查看 syscall.Signal 类型的 String 方法的源码,还会发现一个包级私有的、名为 signals 的变量:

func (s Signal) String() string {
	if 0 <= s && int(s) < len(signals) {
		str := signals[s]
		if str != "" {
			return str
		}
	}
	return "signal " + itoa.Itoa(int(s))
}

系统对应的文件,比如我的电脑系统:/usr/local/go/src/syscall/zerrors_darwin_arm64.go

// Signal table
var signals = [...]string{
	1:  "hangup",
	2:  "interrupt",
	3:  "quit",
	4:  "illegal instruction",
	5:  "trace/BPT trap",
	6:  "abort trap",
	7:  "EMT trap",
	...

在这个数组类型的变量中,每个索引值都代表一个标准信号的编号,而对应的元素则是该信号的一个简短描述。

有了这些前提,就来看一下 os.signal 中的 Notify 函数,

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

第一个参数是只能接受 os.Signal 类型值的通道类型,可以简称为 signal 的接收通道。这样改函数的调用方就可以从 signal 的接收通道中按顺序获取操作系统发来的信号并进行相应的处理了。
第二个参数是一个可变长的 os.Signal 类型参数,即任意多个 os.Signal 类型的参数。参数 sig 代表的参数值包含了我们希望自行处理的所有信号。接收到需要自行处理的信号后,os/signal 包中的程序,会吧它封装成syscall.Signal 类型的值并放入到 signal 接收通道中。举个例子:

sigRecv := make(chan os.Signal, 1)
sigs := []os.Signal{syscall.SIGINI, syscall.SIGQUIT}
signal.Notify(sigRecv, sigs)
for sig := range sigRecv {
	fmt.Printf("signal: %s\n", sig)
}

注意,这个例子这样做比较危险,因为其中忽略了当前进程本该处理的信号(即默认信号)。这样它会无法通过ctrl+c来打断程序。
其实就是自定义处理信号会覆盖系统默认信号的处理方法,未定义的处理信号,还是会执行系统的默认操作。

不过,还好在类 Unix 操作系统下有两种信号既不能自行处理,也不会被忽略,他们就是 SIGKILL 和 SIGSTOP,对他们的响应只能是执行系统的默认操作。

对于其他信号,除了能够自行处理他们外,还可以在之后的任意时刻恢复他们的系统默认操作。这就要用到 os/signal 包中的 Stop 函数,声明如下:

func Stop(c chan<- os.Signal)

注意:调用完 signal.Stop 函数之后,作为参数的 signal 接收通道将不会再被发送任何信号。这里还会存在一个副作用,即在之前示例中那条用于从 signal 接收的通道信号的 for 语句将会一直阻塞。为了消除这种副作用,可以在调用 signal.Shop 函数之后,使用 close 函数关闭 signal 接收通道。

signal.Stop(sigRecv)
close(sigRecv)
  • 9
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值