這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Golang中的訊號處理
個平台的訊號定義或許有些不同。下面列出了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這兩個訊號既不能被應用程式捕獲,也不能被作業系統阻塞或忽略。
kill pid的作用是向進程號為pid的進程發送SIGTERM(這是kill預設發送的訊號),該訊號是一個結束進程的訊號且可以被應用程式捕獲。若應用程式沒有捕獲並響應該訊號的邏輯代碼,則該訊號的預設動作是kill掉進程。這是終止指定進程的推薦做法。
kill -9 pid則是向進程號為pid的進程發送SIGKILL(該訊號的編號為9),從本文上面的說明可知,SIGKILL既不能被應用程式捕獲,也不能被阻塞或忽略,其動作是立即結束指定進程。通俗地說,應用程式根本無法“感知”SIGKILL訊號,它在完全無準備的情況下,就被收到SIGKILL訊號的作業系統給幹掉了,顯然,在這種“暴力”情況下,應用程式完全沒有釋放當前佔用資源的機會。事實上,SIGKILL訊號是直接發給init進程的,它收到該訊號後,負責終止pid指定的進程。在某些情況下(如進程已經hang死,無法響應正常訊號),就可以使用kill -9來結束進程。
若通過kill結束的進程是一個建立過子進程的父進程,則其子進程就會成為孤兒進程(Orphan Process),這種情況下,子進程的退出狀態就不能再被應用進程捕獲(因為作為父進程的應用程式已經不存在了),不過應該不會對整個linux系統產生什麼不利影響。
Linux Server端的應用程式經常會長時間運行,在運行過程中,可能申請了很多系統資源,也可能儲存了很多狀態,在這些情境下,我們希望進程在退出前,可以釋放資源或將目前狀態dump到磁碟上或列印一些重要的日誌,也就是希望進程優雅退出(exit gracefully)。
從上面的介紹不難看出,優雅退出可以通過捕獲SIGTERM來實現。具體來講,通常只需要兩步動作:1)註冊SIGTERM訊號的處理函數並在處理函數中做一些進程退出的準備。訊號處理函數的註冊可以通過signal()或sigaction()來實現,其中,推薦使用後者來實現訊號響應函數的設定。訊號處理函數的邏輯越簡單越好,通常的做法是在該函數中設定一個bool型的flag變數以表明進程收到了SIGTERM訊號,準備退出。2)在主進程的main()中,通過類似於while(!bQuit)的邏輯來檢測那個flag變數,一旦bQuit在signal handler function中被置為true,則主進程退出while()迴圈,接下來就是一些釋放資源或dump進程目前狀態或記錄日誌的動作,完成這些後,主進程退出。
Go中的Signal發送和處理
- golang中對訊號的處理主要使用os/signal包中的兩個方法:
- notify方法用來監聽收到的訊號
- stop方法用來取消監聽
1.監聽全部訊號
package mainimport ( "fmt" "os" "os/signal")// 監聽全部訊號func main() { //合建chan c := make(chan os.Signal) //監聽所有訊號 signal.Notify(c) //阻塞直到有訊號傳入 fmt.Println("啟動") s := <-c fmt.Println("退出訊號", s)}
啟動go run example-1.go啟動ctrl+c退出,輸出退出訊號 interruptkill pid 輸出退出訊號 terminated
2.監聽指定訊號
package mainimport ( "fmt" "os" "os/signal" "syscall")// 監聽指定訊號func main() { //合建chan c := make(chan os.Signal) //監聽指定訊號 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退出,輸出退出訊號 interruptkill pid 輸出退出訊號 terminatedkill -USR1 pid 輸出退出訊號 user defined signal 1kill -USR2 pid 輸出退出訊號 user defined signal 2
3.優雅退出go守護進程
package mainimport ( "fmt" "os" "os/signal" "syscall" "time")// 優雅退出go守護進程func main() { //建立監聽退出chan c := make(chan os.Signal) //監聽指定訊號 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("執行清理...") fmt.Println("結束退出...") os.Exit(0)}
kill -USR1 pid 輸出usr1 user defined signal 1kill -USR2 pid usr2 user defined signal 2kill pid 退出 terminated開始退出...執行清理...結束退出...
執行輸出go run example-3.go進程啟動...sum: 1sum: 2sum: 3sum: 4sum: 5sum: 6sum: 7sum: 8sum: 9usr1 user defined signal 1sum: 10sum: 11sum: 12sum: 13sum: 14usr2 user defined signal 2sum: 15sum: 16sum: 17退出 terminated開始退出...執行清理...結束退出...
參考
http://www.cnblogs.com/jkkkk/p/6180016.htmlhttp://blog.csdn.net/zzhongcy/article/details/50601079https://www.douban.com/note/484935836/https://gist.github.com/reiki4040/be3705f307d3cd136e85#file-signal-go-L1