Signal信号处理
前言
信号(Signal)是Linux, 类Unix和其它POSIX兼容的操作系统中用来进程间通讯的一种方式。
对于Linux系统来说,信号就是软中断,用来通知进程发生了异步事件。
当信号发送到某个进程中时,操作系统会中断该进程的正常流程,并进入相应的信号处理函数执行操作,完成后再回到中断的地方继续执行。
有时候我们想在Go程序中处理Signal信号,比如收到SIGTERM(15)信号后优雅的关闭程序,以及 goroutine结束通知等。
Go 语言提供了对信号处理的包(os/signal)。
Go 中对信号的处理主要使用os/signal包中的两个方法:
- notify方法用来监听收到的信号;
- stop方法用来取消监听。
Go信号通知机制可以通过往一个channel中发送os.Signal
实现。
信号类型
每个个平台的信号定义或许有些不同。
下面列出了POSIX中定义的信号。
Linux 使用34-64信号用作实时系统中。
命令 man signal
提供了官方的信号介绍。
在POSIX.1-1990标准中定义的信号列表
信号 | 值 | 动作 | 说明 |
---|---|---|---|
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 | 后台程序向终端中写数据时触发 |
在SUSv2和POSIX.1-2001标准中的信号列表
信号 | 值 | 动作 | 说明 |
---|---|---|---|
SIGTRAP | 5 | Core | Trap指令触发(如断点,在调试器中使用) |
SIGBUS | 0,7,10 | Core | 非法地址(内存地址对齐错误) |
SIGPOLL | Term | Pollable event (Sys V). Synonym for SIGIO | |
SIGPROF | 27,27,29 | Term | 性能时钟信号(包含系统调用时间和进程占用CPU的时间) |
SIGSYS | 12,31,12 | Core | 无效的系统调用(SVr4) |
SIGURG | 16,23,21 | Ign | 有紧急数据到达Socket(4.2BSD) |
SIGVTALRM | 26,26,28 | Term | 虚拟时钟信号(进程占用CPU的时间)(4.2BSD) |
SIGXCPU | 24,24,30 Core | 超过CPU时间资源限制(4.2BSD) | |
SIGXFSZ | 25,25,31 | Core | 超过文件大小资源限制(4.2BSD) |
第1列为信号名;
第2列为对应的信号值,需要注意的是,有些信号名对应着3个信号值,这是因为这些信号值与平台相关
将man手册中对3个信号值的说明摘出如下,the first one is usually valid for alpha and sparc, the middle one for i386, ppc and sh, and the last one for mips.
第3列为操作系统收到信号后的动作,Term表明默认动作为终止进程,Ign表明默认动作为忽略该信号,Core表明默认动作为终止进程同时输出core dump,Stop表明默认动作为停止进程。
第4列为对信号作用的注释性说明。
需要特别说明的是,
SIGKILL
和SIGSTOP
这两个信号既不能被应用程序捕获,也不能被操作系统阻塞或忽略。
注册信号Notify
Notify
用于将信号注册至对应channel,系统收到对应信号时会发送至此channel。
注意:channel一定要有足够的缓存接收信号,否则会因阻塞而导致信号发送失败。
func Notify(c chan<- os.Signal, sig ...os.Signal)
Notify使包信号将输入信号转发至channel c.
如果未指定信号,则所有输入信号都会被转发至c;否则仅转发指定的信号。
包信号发送至c时不会被阻塞:调用者必须确认c拥有足够的缓存空间来跟上预期的信号速率。
对于用于一个信号通知的channel,缓存大小为1是足够的。
对于同一channel允许多次调用Notify:
每次调用都会扩展发送到channel的信号集
调用Stop将信号移除信号集的唯一方式。
允许多次调用Notify,即使是不同的channel和同样的信号
每个channel独立接收输入信号的副本
例子
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
func Ma() {
go func() {
ch := make(chan os.Signal, 1)//创建容器
signal.Notify(ch, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGHUP)//注册信号到容器中
for {
s := <-ch //循环读取管道
switch s {
case syscall.SIGINT:
fmt.Println("Ctrl+C触发")
return
case syscall.SIGQUIT:
fmt.Println("Ctrl+/触发")
return
case syscall.SIGTERM:
fmt.Println("15 优雅的退出程序")
return
case syscall.SIGHUP:
fmt.Println("终端控制进程结束(终端连接断开)")
default:
fmt.Println("default")
return
}
}
}()
time.Sleep(1 * time.Hour)
}
实用例子
收到终止的信号做一些收尾的工作
// 检测信号
func listenSignal(file *os.File) {
c := make(chan os.Signal, 1)
//监听指定信号 SIGINT和SIGTERM。按下control+c向进程发送SIGINT信号,syscall.SIGTERM
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
for {
select {
case sig := <-c: //接收到终止信息
fmt.Println("接收到信号 %d, 将退出\n", sig)
file.Close()
os.Exit(0)
}
}
}
取消监听stop
Stop
用于取消channel对信号的监听。
Stop时,会将channel对应的数据从m中删除,为了避免数据竞争,对于即将stopping的数据会存储在[]stopping中,然后等待信号发送完毕,最后才移除stop的信号。
例子
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
func Ma() {
go func() {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGHUP)
for {
s := <-ch
switch s {
case syscall.SIGINT:
fmt.Println("Ctrl+C触发")
signal.Stop(ch)//取消监听
return
case syscall.SIGQUIT:
fmt.Println("Ctrl+/触发")
signal.Stop(ch)
return
case syscall.SIGTERM:
fmt.Println("15 优雅的退出程序")
signal.Stop(ch)
return
case syscall.SIGHUP:
fmt.Println("终端控制进程结束(终端连接断开)")
signal.Stop(ch)
return
default:
fmt.Println("default")
}
}
}()
time.Sleep(1 * time.Hour)
}
上例中,如果不取消监听,当匹配到一次信号后,就会退出死循环,因为没有取消监听,信号还是会送入管道,但是因为退出了处理的死循环,就会无人无人处理信号,出现阻塞的状态。
本文来自博客园,作者:厚礼蝎,转载请注明原文链接:https://www.cnblogs.com/guangdelw/p/16891704.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律