Golang信号处理及如何实现进程的优雅退出详解
Linux系统中的信号类型
各操作系统的信号定义或许有些不同。下面列出了POSIX中定义的信号。
在linux中使用34-64信号用作实时系统中。
命令man7signal提供了官方的信号介绍。也可以是用kill-l来快速查看
列表中,编号为1~31的信号为传统UNIX支持的信号,是不可靠信号(非实时的),编号为32~63的信号是后来扩充的,称做可靠信号(实时信号)。不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会。
Linux支持的标准信号有以下一些,一个信号有多个值的是因为不同架构使用的值不一样,比如x86,ia64,ppc,s390,有3个值的,第一个值是slpha和sparc,中间的值是ix86,ia64,ppc,s390,arm和sh,最后一个值是对mips的,连字符-表示这个架构是缺这个信号支持的,
第1列为信号名;
第2列为对应的信号值,需要注意的是,有些信号名对应着3个信号值,这是因为这些信号值与平台相关,将man手册中对3个信号值的说明摘出如下,thefirstoneisusuallyvalidforalphaandsparc,themiddleonefori386,ppcandsh,andthelastoneformips.
第3列为操作系统收到信号后的动作,Term表明默认动作为终止进程,Ign表明默认动作为忽略该信号,Core表明默认动作为终止进程同时输出coredump,Stop表明默认动作为停止进程。
第4列为对信号作用的注释性说明。
标准信号-POSIX.1-1990定义
SignalValueActionComment ---------------------------------------------------------------------- SIGHUP1TermHangupdetectedoncontrollingterminal ordeathofcontrollingprocess SIGINT2TermInterruptfromkeyboard SIGQUIT3CoreQuitfromkeyboard SIGILL4CoreIllegalInstruction SIGABRT6CoreAbortsignalfromabort(3) SIGFPE8CoreFloatingpointexception SIGKILL9TermKillsignal SIGSEGV11CoreInvalidmemoryreference SIGPIPE13TermBrokenpipe:writetopipewithno readers SIGALRM14TermTimersignalfromalarm(2) SIGTERM15TermTerminationsignal SIGUSR130,10,16TermUser-definedsignal1 SIGUSR231,12,17TermUser-definedsignal2 SIGCHLD20,17,18IgnChildstoppedorterminated SIGCONT19,18,25ContContinueifstopped SIGSTOP17,19,23StopStopprocess SIGTSTP18,20,24StopStoptypedattty SIGTTIN21,21,26Stopttyinputforbackgroundprocess SIGTTOU22,22,27Stopttyoutputforbackgroundprocess
SIGKILL和SIGSTOP信号是不能被捕获,阻塞和忽略的。
标准信号-SUSv2andPOSIX.1-2001定义
SignalValueActionComment -------------------------------------------------------------------- SIGBUS10,7,10CoreBuserror(badmemoryaccess) SIGPOLLTermPollableevent(SysV). SynonymforSIGIO SIGPROF27,27,29TermProfilingtimerexpired SIGSYS12,-,12CoreBadargumenttoroutine(SVr4) SIGTRAP5CoreTrace/breakpointtrap SIGURG16,23,21IgnUrgentconditiononsocket(4.2BSD) SIGVTALRM26,26,28TermVirtualalarmclock(4.2BSD) SIGXCPU24,24,30CoreCPUtimelimitexceeded(4.2BSD) SIGXFSZ25,25,31CoreFilesizelimitexceeded(4.2BSD)
早在Linux2.2SIGSYS,SIGXCPU,SIGXFSZ和SIGBUS(非sparc和mips架构)的默认操作就是终止进程(但是不产生coredump)
在一些unix系统中SIGXCPU和SIGXFSZ信号是用来终止进程的,也是不产生coredunp,从Linux2.4开始这些信号会产生coredump了。
标准信号-其它信号
SignalValueActionComment -------------------------------------------------------------------- SIGIOT6CoreIOTtrap.AsynonymforSIGABRT SIGEMT7,-,7Term SIGSTKFLT-,16,-TermStackfaultoncoprocessor(unused) SIGIO23,29,22TermI/Onowpossible(4.2BSD) SIGCLD-,-,18IgnAsynonymforSIGCHLD SIGPWR29,30,19TermPowerfailure(SystemV) SIGINFO29,-,-AsynonymforSIGPWR SIGLOST-,-,-TermFilelocklost SIGWINCH28,28,20IgnWindowresizesignal(4.3BSD,Sun) SIGUNUSED-,31,-TermUnusedsignal(willbeSIGSYS)
信号29是在alpha中是SIGINFO或SIGPWR,但是在sparc中是SIGLOST。
SIGEMT没有在POSIX.1-2001中定义,但是在大多数Unix戏中是没有的,他的默认处理方式是coredump并且终止进程。
SIGPWR(没有在POSIX.1-2001中定义)他的默认处理方式是忽略。
SIGIO(没有在POSIX.1-2001中定义)在一些Unix系统中的处理方式也是忽略。
killpid的作用是向进程号为pid的进程发送SIGTERM(这是kill默认发送的信号),该信号是一个结束进程的信号且可以被应用程序捕获。若应用程序没有捕获并响应该信号的逻辑代码,则该信号的默认动作是kill掉进程。这是终止指定进程的推荐做法。
kill-9pid则是向进程号为pid的进程发送SIGKILL(该信号的编号为9),从本文上面的说明可知,SIGKILL既不能被应用程序捕获,也不能被阻塞或忽略,其动作是立即结束指定进程。通俗地说,应用程序根本无法“感知”SIGKILL信号,它在完全无准备的情况下,就被收到SIGKILL信号的操作系统给干掉了,显然,在这种“暴力”情况下,应用程序完全没有释放当前占用资源的机会。事实上,SIGKILL信号是直接发给init进程的,它收到该信号后,负责终止pid指定的进程。在某些情况下(如进程已经hang死,无法响应正常信号),就可以使用kill-9来结束进程。
若通过kill结束的进程是一个创建过子进程的父进程,则其子进程就会成为孤儿进程(OrphanProcess),这种情况下,子进程的退出状态就不能再被应用进程捕获(因为作为父进程的应用程序已经不存在了),不过应该不会对整个linux系统产生什么不利影响。
Go中的信号发送和处理
有时候我们想在Go程序中处理Signal信号,比如收到SIGTERM信号后优雅的关闭程序(参看下一节的应用)。Go信号通知机制可以通过往一个channel中发送os.Signal实现。首先我们创建一个os.Signalchannel,然后使用signal.Notify注册要接收的信号。
packagemain import( "fmt" "os" "os/signal" "syscall" ) funcmain(){ sigs:=make(chanos.Signal,1) done:=make(chanbool,1) //signal.Notify(c) signal.Notify(sigs,os.Interrupt,os.Kill,syscall.SIGUSR1,syscall.SIGUSR2,syscall.SIGINT,syscall.SIGTERM) gofunc(){ sig:=<-sigs fmt.Println(sig) done<-true }() fmt.Println("waitforsignal") <-done fmt.Println("gotsignalandexit") fmt.Println("rundone") }
如何实现进程的优雅退出
首先什么是优雅退出呢?所谓的优雅退出,其实就是避免暴力杀死进程,让进程在接收到信号之后,自动的做一些善后处理,再自己自愿的退出。
LinuxServer端的应用程序经常会长时间运行,在运行过程中,可能申请了很多系统资源,也可能保存了很多状态,在这些场景下,我们希望进程在退出前,可以释放资源或将当前状态dump到磁盘上或打印一些重要的日志,也就是希望进程优雅退出(exitgracefully)。
从上面的介绍不难看出,优雅退出可以通过捕获SIGTERM来实现。具体来讲,通常只需要两步动作:
1)注册SIGTERM信号的处理函数并在处理函数中做一些进程退出的准备。信号处理函数的注册可以通过signal()或sigaction()来实现,其中,推荐使用后者来实现信号响应函数的设置。信号处理函数的逻辑越简单越好,通常的做法是在该函数中设置一个bool型的flag变量以表明进程收到了SIGTERM信号,准备退出。
2)在主进程的main()中,通过类似于while(!bQuit)的逻辑来检测那个flag变量,一旦bQuit在signalhandlerfunction中被置为true,则主进程退出while()循环,接下来就是一些释放资源或dump进程当前状态或记录日志的动作,完成这些后,主进程退出。
这个在我前面的一篇文章中也介绍过【=[golang的httpserver优雅重启][1]】https://www.nhooo.com/article/137069.htm,里面介绍了一般我们使用的httpserver如何做到优雅重启,这里面也介绍了一些信号的使用,和优雅重启的思路。今天这里我们介绍的是如何优雅退出,其实是优雅重启的一个简化版。
packagemain import( "fmt" "os" "os/signal" "syscall" "time" ) funcmain(){ sigs:=make(chanos.Signal,1) //done:=make(chanbool,1) //signal.Notify(sigs) //signal.Notify(sigs,os.Interrupt,os.Kill,syscall.SIGUSR1,syscall.SIGUSR2,syscall.SIGINT,syscall.SIGTERM) signal.Notify(sigs,syscall.SIGUSR1,syscall.SIGUSR2,syscall.SIGINT,syscall.SIGTERM,syscall.SIGHUP,syscall.SIGQUIT) //gofunc(){ //sig:=<-sigs //fmt.Println(sig) //done<-true //}() gofunc(){ fors:=rangesigs{ switchs{ casesyscall.SIGINT,syscall.SIGTERM,syscall.SIGHUP,syscall.SIGQUIT: fmt.Println("gotsignalandtrytoexit:",s) do_exit() casesyscall.SIGUSR1: fmt.Println("usr1:",s) casesyscall.SIGUSR2: fmt.Println("usr2:",s) default: fmt.Println("other:",s) } } }() fmt.Println("waitforsignal") i:=0 for{ i++ fmt.Println("times:",i) time.Sleep(1*time.Second) } //<-done fmt.Println("gotsignalandexit") fmt.Println("rundone") } funcdo_exit(){ fmt.Println("trydosomeclearjobs") fmt.Println("rundone") os.Exit(0) }
kill-USR1pid usr1userdefinedsignal1 kill-USR2pid usr2userdefinedsignal2 kill-QUITpid gotsignalandtrytoexit:quit trydosomeclearjobs rundone
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。