這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
訊號(Signal)是Linux, 類Unix和其它POSIX相容的作業系統中用來進程間通訊的一種方式。一個訊號就是一個非同步通知,發送給某個進程,或者同進程的某個線程,告訴它們某個事件發生了。
當訊號發送到某個進程中時,作業系統會中斷該進程的正常流程,並進入相應的訊號處理函數執行操作,完成後再回到中斷的地方繼續執行。
如果目標進程先前註冊了某個訊號的處理常式(signal handler),則此處理常式會被調用,否則預設的處理常式被調用。
發送訊號
kill系統調用(system call)
可以用來發送一個特定的訊號給進程。
kill命令
允許使用者發送一個特定的訊號給進程。
raise庫函數
可以發送特定的訊號給當前進程。
在Linux下運行man kill可以查看此命令的介紹和用法。
The commandkill
sends the specified signal to the specified process or process group. If no signal is specified, the TERM signal is sent. The TERM signal will kill processes which do not catch this signal. For other processes, it may be necessary to use the KILL (9) signal, since this signal cannot be caught.
Most modern shells have a builtin kill function, with a usage rather similar to that of the command described here. The ‘-a’ and ‘-p’ options, and the possibility to specify pids by command name is a local extension.
If sig is 0, then no signal is sent, but error checking is still performed.
一些異常比如除以0或者 segmentation violation 相應的會產生SIGFPE和SIGSEGV訊號,預設情況下導致core dump和程式退出。
核心在某些情況下發送訊號,比如在進程往一個已經關閉的管道寫資料時會產生SIGPIPE訊號。
在進程的終端敲入特定的按鍵組合也會導致系統發送某個特定的訊號給此進程:
Ctrl-C 發送 INT signal (SIGINT),通常導致進程結束
Ctrl-Z 發送 TSTP signal (SIGTSTP); 通常導致進程掛起(suspend)
Ctrl-\ 發送 QUIT signal (SIGQUIT); 通常導致進程結束 和 dump core.
Ctrl-T (不是所有的UNIX都支援) 發送INFO signal (SIGINFO); 導致作業系統顯示此運行命令的資訊
kill -9 pid會發送SIGKILL訊號給進程。
處理訊號
Signal handler可以通過signal()系統調用進行設定。如果沒有設定,預設的handler會被調用,當然進程也可以設定忽略此訊號。
有兩種訊號不能被攔截和處理:SIGKILL和SIGSTOP。
當接收到訊號時,進程會根據訊號的響應動作執行相應的操作,訊號的響應動作有以下幾種:
- 中止進程(Term)
- 忽略訊號(Ign)
- 中止進程並儲存記憶體資訊(Core)
- 停止進程(Stop)
- 繼續運行進程(Cont)
使用者可以通過signal或sigaction函數修改訊號的響應動作(也就是常說的“註冊訊號”)。另外,在多線程中,各線程的訊號響應動作都是相同的,不能對某個線程設定獨立的響應動作。
訊號類型
個平台的訊號定義或許有些不同。下面列出了POSIX中定義的訊號。
Linux 使用34-64訊號用作即時系統中。
命令man 7 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)
Go中的Signal發送和處理
有時候我們想在Go程式中處理Signal訊號,比如收到SIGTERM訊號後優雅的關閉程式(參看下一節的應用)。
Go訊號通知機制可以通過往一個channel中發送os.Signal實現。
首先我們建立一個os.Signal channel,然後使用signal.Notify註冊要接收的訊號。
12345678910111213141516171819202122232425262728293031323334353637
package mainimport “fmt”import “os”import “os/signal”import “syscall”func main() { // Go signal notification works by sending os.Signal // values on a channel. We’ll create a channel to // receive these notifications (we’ll also make one to // notify us when the program can exit). sigs := make(chan os.Signal, 1) done := make(chan bool, 1) // signal.Notify registers the given channel to // receive notifications of the specified signals. signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) // This goroutine executes a blocking receive for // signals. When it gets one it’ll print it out // and then notify the program that it can finish. go func() { sig := <-sigs fmt.Println() fmt.Println(sig) done <- true }() // The program will wait here until it gets the // expected signal (as indicated by the goroutine // above sending a value on done) and then exit. fmt.Println(“awaiting signal”) <-done fmt.Println(“exiting”)}
go run main.go執行這個程式,敲入ctrl-C會發送SIGINT訊號。 此程式接收到這個訊號後會列印退出。
Go網路伺服器如果無縫重啟
Go很適合編寫伺服器端的網路程式。DevOps經常會遇到的一個情況是升級系統或者重新載入設定檔,在這種情況下我們需要重啟此網路程式,如果網路程式暫停時間較長,則給客戶的感覺很不好。
如何?優雅地重啟一個Go網路程式呢。主要要解決兩個問題:
進程重啟不需要關閉監聽的連接埠
既有請求應當完全處理或者逾時
@humblehack在他的文章Graceful Restart in Golang中提供了一種方式,而Florian von Bock根據此思路實現了一個架構endless。
此架構使用起來超級簡單:
1
err := endless.ListenAndServe(“localhost:4242”, mux)
只需替換http.ListenAndServe和http.ListenAndServeTLS。
它會監聽這些訊號:syscall.SIGHUP,syscall.SIGUSR1,syscall.SIGUSR2,syscall.SIGINT,syscall.SIGTERM, 和syscall.SIGTSTP。
此文章提到的思路是:
通過exec.Commandfork一個新的進程,同時繼承當前進程的開啟的檔案(輸入輸出,socket等)
1234567891011121314
file := netListener.File() // this returns a Dup()path := “/path/to/executable”args := []string{ “-graceful”}cmd := exec.Command(path, args…)cmd.Stdout = os.Stdoutcmd.Stderr = os.Stderrcmd.ExtraFiles = []*os.File{file}err := cmd.Start()if err != nil { log.Fatalf(“gracefulRestart: Failed to launch, error: %v”, err)}
子進程初始化
網路程式的啟動代碼
12345678910111213141516
server := &http.Server{Addr: “0.0.0.0:8888”} var gracefulChild bool var l net.Listever var err error flag.BoolVar(&gracefulChild, “graceful”, false, “listen on fd open 3 (internal use only)”) if gracefulChild { log.Print(“main: Listening to existing file descriptor 3.”) f := os.NewFile(3, “”) l, err = net.FileListener(f) } else { log.Print(“main: Listening on a new file descriptor.”) l, err = net.Listen(“tcp”, server.Addr) }
父進程停止
1234567
if gracefulChild { parent := syscall.Getppid() log.Printf(“main: Killing parent pid: %v”, parent) syscall.Kill(parent, syscall.SIGTERM)}server.Serve(l)
同時他還提供的如何處理已經正在處理的請求。可以查看它的文章瞭解詳細情況。
因此,處理特定的訊號可以實現程式無縫的重啟。
參考資料
https://en.wikipedia.org/wiki/Unix_signal
http://hutaow.com/blog/2013/10/19/linux-signal/
http://www.ucs.cam.ac.uk/docs/course-notes/unix-courses/Building/files/signals.pdf
https://golang.org/pkg/os/signal/
https://gobyexample.com/signals
http://grisha.org/blog/2014/06/03/graceful-restart-in-golang/
https://fitstar.github.io/falcore/hot_restart.html
https://github.com/rcrowley/goagain