os/signal学习笔记

591 阅读3分钟

因为在项目中中关于注册信号处理器的,所以写篇学习笔记来加深一下理解

首先基本概念为

信号是事件发生时对进程的通知机制。有时也称之为软件中断。信号与硬件中断的相似之处在于打断了程序执行的正常流程,大多数情况下,无法预测信号到达的精确时间。

因为一个具有合适权限的进程可以向另一个进程发送信号,这可以称为进程间的一种同步技术。当然,进程也可以向自身发送信号。然而,发往进程的诸多信号,通常都是源于内核。引发内核为进程产生信号的各类事件如下。

  • 硬件发生异常,即硬件检测到一个错误条件并通知内核,随即再由内核发送相应信号给相关进程。比如执行一条异常的机器语言指令(除0,引用无法访问的内存区域)。
  • 用户键入了能够产生信号的终端特殊字符。如中断字符(通常是 Control-C)、暂停字符(通常是 Control-Z)。
  • 发生了软件事件。如调整了终端窗口大小,定时器到期等。

针对每个信号,都定义了一个唯一的(小)整数,从 1 开始顺序展开。系统会用相应常量表示。Linux 中,1-31 为标准信号;32-64 为实时信号(通过 kill -l 可以查看)。

信号达到后,进程视具体信号执行如下默认操作之一。

  • 忽略信号,也就是内核将信号丢弃,信号对进程不产生任何影响。
  • 终止(杀死)进程。
  • 产生 coredump 文件,同时进程终止。
  • 暂停(Stop)进程的执行。
  • 恢复进程执行。

当然,对于有些信号,程序是可以改变默认行为的,这也就是 os/signal 包的用途。

而我们最常用的就是Notify 改变信号处理,可以改变信号的默认行为;Ignore 可以忽略信号;Reset 重置信号为默认行为;Stop 则停止接收信号,但并没有重置为默认行为。

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

类似于绑定信号处理程序。将输入信号转发到 chan c。如果没有列出要传递的信号,会将所有输入信号传递到c;否则只传递列出的输入信号。

channel c 缓存如何决定?因为 signal 包不会为了向c发送信息而阻塞(就是说如果发送时 c 阻塞了,signal包会直接放弃):调用者应该保证 c 有足够的缓存空间可以跟上期望的信号频率。对使用单一信号用于通知的channel,缓存为1就足够了。

相关源码:

// src/os/signal/signal.go process 函数
for c, h := range handlers.m {
    if h.want(n) {
        // send but do not block for it
        select {
        case c <- sig:
        default:    // 保证不会阻塞,直接丢弃
        }
    }
}

可以使用同一 channel 多次调用 Notify:每一次都会扩展该 channel 接收的信号集。唯一从信号集去除信号的方法是调用 Stop。可以使用同一信号和不同 channel 多次调用 Notify:每一个 channel 都会独立接收到该信号的一个拷贝。

目前显目上大概学习到这么多,有新的理解后面再更新