什么是信號
在計算機科學中,信號是Unix、類Unix以及其他POSIX兼容的操作系統中進程間通訊的一種有限制的方式。它是一種異步的通知機制,用來提醒進程一個事件已經發生。
當一個信號發送給一個進程,操作系統中斷了進程正常的控制流程,如果進程定義了對信號的處理,此時,程序將進入捕獲到的信號對應的處理函數,否則執行默認的處理函數。
Linux中信號的介紹
在Linux系統共定義了64種信號,分為兩大類:實時信號與非實時信號,1-31為非實時,32-64種為實時信號。
- 非實時信號: 也稱為不可靠信號,為早期Linux所支持的信號,不支持排隊,信號可能會丟失, 比如發送多次相同的信號, 進程只能收到一次. 信號值取值區間為1~31;
- 實時信號: 也稱為可靠信號,支持排隊, 信號不會丟失, 發多少次, 就可以收到多少次. 信號值取值區間為32~64
Linux操作系統中,在終端上執行 kill -l
便可看到系統定義的所有信號
信號表
POSIX.1-1990標准信號
此表參考自:POSIX信號
信號 | 值 | 動作 | 說明 |
---|---|---|---|
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 | 后台程序向終端中寫數據時觸發 |
更多的信號說明請查閱man7
此表的操作為每個信號的默認配置,如下所示
動作 | 說明 |
---|---|
Term | 默認操作是,終止進程。 |
Ign | 默認操作是,忽略信號。 |
Core | 默認操作是,終止該進程並核心轉儲 |
Stop | 默認操作是,停止進程。 |
Cont | 默認操作是,如果當前已停止,則繼續該進程。 |
信號的產生
信號是事件發生時對進程的通知機制。信號中斷與硬件中斷的相似之處在於打斷了程序執行的正常流程。
信號事件的來源分為軟件信號和硬件信號:
- 硬件信號: 用戶輸入:比如在終端上按下組合鍵ctrl+C,產生SIGINT信號;硬件異常:CPU檢測到內存非法訪問等異常,通知內核生成相應信號,並發送給發生事件的進程;
- 軟件信號: 通過系統調用: 如,發送signal信號:
kill
,raise
等。
發送的信號
Ctrl-C
發送 INT signal (SIGINT),通常導致進程結束Ctrl-Z
發送 TSTP signal (SIGTSTP); 通常導致進程掛起(suspend)Ctrl-\
發送 QUIT signal (SIGQUIT); 通常導致進程結束 和 dump core.
信號的處理
內核處理進程收到的signal是在當前進程的上下文,故進程必須是Running狀態。當進程喚醒或者調度后獲取CPU,則會從內核態轉到用戶態時檢測是否有signal等待處理,處理完,進程會把相應的未決信號從鏈表中去掉。
signal信號處理時機: 內核 ==> 信號處理 ==> 用戶
- 內核態:在內核態,signal信號不起作用;
- signal信號處理: 在用戶態,signal所有未被屏蔽的信號都處理完畢;當屏蔽信號,取消屏蔽時,會在下一次內核轉用戶態的過程中執行;
信號處理方式
進程對信號的處理方式有3種:
- 默認 接收到信號后按默認的行為處理該信號。 這種方式為多數應用采取的處理方式。
- 自定義處理 用自定義的信號處理函數來執行特定的動作
- 忽略忽略信號 接收到信號后不做任何反應。
對信號的處理動作:
- Term: 中止進程
- Ign: 忽略信號
- Core: 中止進程並保存內存信息
- Stop: 停止進程
- Cont: 繼續運行進程
Linux信號命令
kill
kill命令用來終止指定的進程, 對於一個后台進程就須用kill命令來終止,我們就需要先使用ps/pidof/pstree/top等工具獲取進程PID,然后使用kill命令來殺掉該進程。
命令格式
kill[參數] [進程id]
命令參數
-l
信號,若果不加信號的編號參數,則使用“-l”參數會列出全部的信號名稱
-a
當處理當前進程時,不限制命令名和進程號的對應關系
-p
指定kill 命令只打印相關進程的進程號,而不發送任何信號
-s
指定發送信號
-u
指定用戶
killall
Linux系統中的killall
用於殺死指定名字的進程(kill processes by name)。我們可以使用kill命令殺死指定進程PID的進程,如果要找到我們需要殺死的進程,我們還需要在之前使用ps等命令再配合grep來查找進程,而killall把這兩個過程合二為一,是一個很好用的命令。
命令格式
killall[參數] [進程名]
命令參數
-I
忽略小寫
-a
當處理當前進程時,不限制命令名和進程號的對應關系
-i
交互模式,殺死進程前先詢問用戶
-s
發送指定的信號
-w
等待進程死亡
-e
要求匹配進程名稱
PKILL
pkill
與 killall
使用方法類似,用於殺死指定名稱的進程
Go語言中的Signal的使用
在Go語言中,處理信號僅需要3個步驟即可完成對信號的處理
- 信號的接收:
signalChan := make(chan os.Signal,1)
- 信號的監聽捕獲:
signal.Notify(signalChan)
- 信號的觸發:
signal := <-signalChan
注意事項:
SIGKILL kill -9
和SIGSTOP kill -19
信號可能不會被Notify方法捕獲,因此無法處理這些信號。- 如果在Notify方法中沒有指定信號作為參數,那么該方法將捕獲所有的信號。
在Go語言中的Signal的處理
在某些場景下,如,在大量並發及,批量處理未完成時,此時需要在Go程序中處理Signal信號,比如收到SIGTERM信號后優雅的關閉程序。
實例:在一個計算場景下,有5個goroutine在處理業務,當收到
kill -15
時計算完成后退出程序,kill -4
不做處理。
package main
import (
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"time"
)
var wg sync.WaitGroup
func exitProcess() {
fmt.Println("等待進程完成")
wg.Wait()
fmt.Println("進程退出")
}
func process(n int) {
i := n
for {
fmt.Println("process", n, ":", i)
if i > 100 {
break
}
time.Sleep(time.Second)
i++
}
fmt.Println("process", n, "finnshed")
defer wg.Done()
}
func main() {
signals := make(chan os.Signal, 1)
done := make(chan bool, 1)
signal.Notify(signals, syscall.SIGILL, syscall.SIGTERM)
go func() {
for signal := range signals {
switch signal {
case syscall.SIGTERM, syscall.SIGQUIT:
fmt.Println("kill -15 進程退出")
exitProcess()
case syscall.SIGILL:
fmt.Println("kill -4")
}
}
done <- true
}()
wg.Add(5)
for n := 0; n < 10; n++ {
go process(n)
}
fmt.Println("waiting signal...")
wg.Wait()
fmt.Println("exiting")
}
收到kill -4 信號打印kill -4
收到kill -15 信號后,帶程序處理完成后退出