Golang中的信號處理
個平臺的信號定義或許有些不同嫉入。下面列出了POSIX中定義的信號。
Linux 使用34-64信號用作實時系統(tǒng)中爱葵。
命令 man signal 提供了官方的信號介紹惜浅。
在POSIX.1-1990標準中定義的信號列表
信號 |
值 |
動作 |
說明 |
SIGHUP |
1 |
Term |
終端控制進程結(jié)束(終端連接斷開) |
SIGINT |
2 |
Term |
用戶發(fā)送INTR字符(Ctrl+C)觸發(fā) |
SIGQUIT |
3 |
Core |
用戶發(fā)送QUIT字符(Ctrl+/)觸發(fā) |
SIGILL |
4 |
Core |
非法指令(程序錯誤酣溃、試圖執(zhí)行數(shù)據(jù)段、棧溢出等) |
SIGABRT |
6 |
Core |
調(diào)用abort函數(shù)觸發(fā) |
SIGFPE |
8 |
Core |
算術(shù)運行錯誤(浮點運算錯誤、除數(shù)為零等) |
SIGKILL |
9 |
Term |
無條件結(jié)束程序(不能被捕獲窍侧、阻塞或忽略) |
SIGSEGV |
11 |
Core |
無效內(nèi)存引用(試圖訪問不屬于自己的內(nèi)存空間停撞、對只讀內(nèi)存空間進行寫操作) |
SIGPIPE |
13 |
Term |
消息管道損壞(FIFO/Socket通信時瓷蛙,管道未打開而進行寫操作) |
SIGALRM |
14 |
Term |
時鐘定時信號 |
SIGTERM |
15 |
Term |
結(jié)束程序(可以被捕獲悼瓮、阻塞或忽略) |
SIGUSR1 |
30,10,16 |
Term |
用戶保留 |
SIGUSR2 |
31,12,17 |
Term |
用戶保留 |
SIGCHLD |
20,17,18 |
Ign |
子進程結(jié)束(由父進程接收) |
SIGCONT |
19,18,25 |
Cont |
繼續(xù)執(zhí)行已經(jīng)停止的進程(不能被阻塞) |
SIGSTOP |
17,19,23 |
Stop |
停止進程(不能被捕獲、阻塞或忽略) |
SIGTSTP |
18,20,24 |
Stop |
停止進程(可以被捕獲艰猬、阻塞或忽略) |
SIGTTIN |
21,21,26 |
Stop |
后臺程序從終端中讀取數(shù)據(jù)時觸發(fā) |
SIGTTOU |
22,22,27 |
Stop |
后臺程序向終端中寫數(shù)據(jù)時觸發(fā) |
- 在SUSv2和POSIX.1-2001標準中的信號列表:
信號 |
值 |
動作 |
說明 |
SIGTRAP |
5 |
Core |
Trap指令觸發(fā)(如斷點横堡,在調(diào)試器中使用) |
SIGBUS |
0,7,10 |
Core |
非法地址(內(nèi)存地址對齊錯誤) |
SIGPOLL |
|
Term |
Pollable event (Sys V). Synonym for SIGIO |
SIGPROF |
27,27,29 |
Term |
性能時鐘信號(包含系統(tǒng)調(diào)用時間和進程占用CPU的時間) |
SIGSYS |
12,31,12 |
Core |
無效的系統(tǒng)調(diào)用(SVr4) |
SIGURG |
16,23,21 |
Ign |
有緊急數(shù)據(jù)到達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個信號值,這是因為這些信號值與平臺相關(guān)食听,將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列為操作系統(tǒng)收到信號后的動作,Term表明默認動作為終止進程樱报,Ign表明默認動作為忽略該信號葬项,Core表明默認動作為終止進程同時輸出core dump,Stop表明默認動作為停止進程迹蛤。
第4列為對信號作用的注釋性說明玷室,淺顯易懂,這里不再贅述笤受。
需要特別說明的是穷缤,SIGKILL和SIGSTOP這兩個信號既不能被應用程序捕獲,也不能被操作系統(tǒng)阻塞或忽略箩兽。
- kill pid與kill -9 pid的區(qū)別
kill pid的作用是向進程號為pid的進程發(fā)送SIGTERM(這是kill默認發(fā)送的信號)津肛,該信號是一個結(jié)束進程的信號且可以被應用程序捕獲。若應用程序沒有捕獲并響應該信號的邏輯代碼汗贫,則該信號的默認動作是kill掉進程身坐。這是終止指定進程的推薦做法。
kill -9 pid則是向進程號為pid的進程發(fā)送SIGKILL(該信號的編號為9)落包,從本文上面的說明可知部蛇,SIGKILL既不能被應用程序捕獲,也不能被阻塞或忽略咐蝇,其動作是立即結(jié)束指定進程觅闽。通俗地說侈净,應用程序根本無法“感知”SIGKILL信號黍氮,它在完全無準備的情況下晕鹊,就被收到SIGKILL信號的操作系統(tǒng)給干掉了,顯然旭寿,在這種“暴力”情況下警绩,應用程序完全沒有釋放當前占用資源的機會。事實上盅称,SIGKILL信號是直接發(fā)給init進程的肩祥,它收到該信號后后室,負責終止pid指定的進程。在某些情況下(如進程已經(jīng)hang死混狠,無法響應正常信號)岸霹,就可以使用kill -9來結(jié)束進程。
若通過kill結(jié)束的進程是一個創(chuàng)建過子進程的父進程檀蹋,則其子進程就會成為孤兒進程(Orphan Process),這種情況下云芦,子進程的退出狀態(tài)就不能再被應用進程捕獲(因為作為父進程的應用程序已經(jīng)不存在了)俯逾,不過應該不會對整個linux系統(tǒng)產(chǎn)生什么不利影響。
Linux Server端的應用程序經(jīng)常會長時間運行舅逸,在運行過程中桌肴,可能申請了很多系統(tǒng)資源,也可能保存了很多狀態(tài)琉历,在這些場景下坠七,我們希望進程在退出前,可以釋放資源或?qū)斍盃顟B(tài)dump到磁盤上或打印一些重要的日志旗笔,也就是希望進程優(yōu)雅退出(exit gracefully)彪置。
從上面的介紹不難看出,優(yōu)雅退出可以通過捕獲SIGTERM來實現(xiàn)蝇恶。具體來講拳魁,通常只需要兩步動作:
1)注冊SIGTERM信號的處理函數(shù)并在處理函數(shù)中做一些進程退出的準備。信號處理函數(shù)的注冊可以通過signal()或sigaction()來實現(xiàn)撮弧,其中潘懊,推薦使用后者來實現(xiàn)信號響應函數(shù)的設(shè)置。信號處理函數(shù)的邏輯越簡單越好贿衍,通常的做法是在該函數(shù)中設(shè)置一個bool型的flag變量以表明進程收到了SIGTERM信號授舟,準備退出。
2)在主進程的main()中贸辈,通過類似于while(!bQuit)的邏輯來檢測那個flag變量释树,一旦bQuit在signal handler function中被置為true,則主進程退出while()循環(huán)擎淤,接下來就是一些釋放資源或dump進程當前狀態(tài)或記錄日志的動作躏哩,完成這些后,主進程退出揉燃。
Go中的Signal發(fā)送和處理
- golang中對信號的處理主要使用os/signal包中的兩個方法:
- notify方法用來監(jiān)聽收到的信號
- stop方法用來取消監(jiān)聽
1.監(jiān)聽全部信號
package main
import (
"fmt"
"os"
"os/signal"
)
// 監(jiān)聽全部信號
func main() {
//合建chan
c := make(chan os.Signal)
//監(jiān)聽所有信號
signal.Notify(c)
//阻塞直到有信號傳入
fmt.Println("啟動")
s := <-c
fmt.Println("退出信號", s)
}
啟動
go run example-1.go
啟動
ctrl+c退出,輸出
退出信號 interrupt
kill pid 輸出
退出信號 terminated
2.監(jiān)聽指定信號
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
// 監(jiān)聽指定信號
func main() {
//合建chan
c := make(chan os.Signal)
//監(jiān)聽指定信號 ctrl+c kill
signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGUSR1, syscall.SIGUSR2)
//阻塞直到有信號傳入
fmt.Println("啟動")
//阻塞直至有信號傳入
s := <-c
fmt.Println("退出信號", s)
}
啟動
go run example-2.go
啟動
ctrl+c退出,輸出
退出信號 interrupt
kill pid 輸出
退出信號 terminated
kill -USR1 pid 輸出
退出信號 user defined signal 1
kill -USR2 pid 輸出
退出信號 user defined signal 2
3.優(yōu)雅退出go守護進程
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
// 優(yōu)雅退出go守護進程
func main() {
//創(chuàng)建監(jiān)聽退出chan
c := make(chan os.Signal)
//監(jiān)聽指定信號 ctrl+c kill
signal.Notify(c, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2)
go func() {
for s := range c {
switch s {
case syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT:
fmt.Println("退出", s)
ExitFunc()
case syscall.SIGUSR1:
fmt.Println("usr1", s)
case syscall.SIGUSR2:
fmt.Println("usr2", s)
default:
fmt.Println("other", s)
}
}
}()
fmt.Println("進程啟動...")
sum := 0
for {
sum++
fmt.Println("sum:", sum)
time.Sleep(time.Second)
}
}
func ExitFunc() {
fmt.Println("開始退出...")
fmt.Println("執(zhí)行清理...")
fmt.Println("結(jié)束退出...")
os.Exit(0)
}
kill -USR1 pid 輸出
usr1 user defined signal 1
kill -USR2 pid
usr2 user defined signal 2
kill pid
退出 terminated
開始退出...
執(zhí)行清理...
結(jié)束退出...
執(zhí)行輸出
go run example-3.go
進程啟動...
sum: 1
sum: 2
sum: 3
sum: 4
sum: 5
sum: 6
sum: 7
sum: 8
sum: 9
usr1 user defined signal 1
sum: 10
sum: 11
sum: 12
sum: 13
sum: 14
usr2 user defined signal 2
sum: 15
sum: 16
sum: 17
退出 terminated
開始退出...
執(zhí)行清理...
結(jié)束退出...
參考
http://www.cnblogs.com/jkkkk/p/6180016.html
http://blog.csdn.net/zzhongcy/article/details/50601079
https://www.douban.com/note/484935836/
https://gist.github.com/reiki4040/be3705f307d3cd136e85#file-signal-go-L1