信號(Signals)是Unix、類Unix以及其餘POSIX兼容的操做系統中進程間通信的一種有限制的方式。它是一種異步的通知機制,用來提醒進程一個事件已經發生。當一個信號發送給一個進程,操做系統中斷了進程正常的控制流程,此時,任何非原子操做都將被中斷。若是進程定義了信號的處理函數,那麼它將被執行,不然就執行默認的處理函數。html
信號是進程間通訊機制中惟一的異步通訊機制,能夠看做是異步通知,通知接收信號的進程有哪些事情發生了。也能夠簡單理解爲信號是某種形式上的軟中斷。linux
通常狀況下,信號的來源可分爲如下三種:git
kill
命令向進程發送任務信號、進程調用kill
或sigqueue
函數發送信號、當檢測到某種軟件條件已經具有時發出信號,如由alarm
或settimer
設置的定時器超時時將生成SIGALRM
信號等多種情景都可產生信號。Ctrl+C
將產生一個SIGINT
信號,Ctrl+\
產生一個SIGQUIT
信號等。可運行kill -l
查看Linux支持的信號列表:github
sl@Li:~/Works$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
複製代碼
能夠看到Linux中系統一共支持64種信號,其中1到31號信號爲普通訊號(也程爲不可靠信號),34到64爲實時信號(可靠信號)。shell
可靠信號與不可靠信號的區別:微信
如下列出幾個經常使用的信號:異步
信號 | 描述 |
---|---|
SIGHUP | 當用戶退出終端時,由該終端開啓的全部進程都退接收到這個信號,默認動做爲終止進程。 |
SIGINT | 程序終止(interrupt)信號, 在用戶鍵入INTR字符(一般是Ctrl+C )時發出,用於通知前臺進程組終止進程。 |
SIGQUIT | 和SIGINT 相似, 但由QUIT字符(一般是Ctrl+\ )來控制. 進程在因收到SIGQUIT 退出時會產生core 文件, 在這個意義上相似於一個程序錯誤信號。 |
SIGKILL | 用來當即結束程序的運行. 本信號不能被阻塞、處理和忽略。 |
SIGTERM | 程序結束(terminate)信號, 與SIGKILL 不一樣的是該信號能夠被阻塞和處理。一般用來要求程序本身正常退出。 |
SIGSTOP | 中止(stopped)進程的執行. 注意它和terminate以及interrupt的區別:該進程還未結束, 只是暫停執行. 本信號不能被阻塞, 處理或忽略. |
要注意信號處理函數的可重入問題,由於信號處理函數執行過程當中有可能被其餘信號再次中斷,這時程序會跳到另外一個信號的處理函數中,處理完成後再次返回當前處理函數,編寫本身定義的信號處理函數的時候必定要注意這一點。信號能夠理解爲「軟中斷」,這樣可重入函數就很好理解了。函數
內核處理一個進程收到的信號的時機是在一個進程從內核態返回用戶態時。 因此,當一個進程在內核態下運行時,軟中斷信號並不當即起做用,要等到將返回用戶態時才處理。進程只有處理完信號纔會返回用戶態,進程在用戶態下不會有未處理完的信號。ui
內核處理一個進程收到的軟中斷信號是在該進程的上下文中,所以,進程必須處於運行狀態。當進程接收到一個它忽略的信號時,進程丟棄該信號,就像沒有收到該信號似的繼續運行。spa
若是進程收到一個要捕捉的信號,那麼進程從內核態返回用戶態時執行用戶定義的函數。並且執行用戶定義的函數的方法很巧妙,內核在用戶棧上建立一個新的層,該層中將返回地址的值設置成用戶定義的處理函數的地址,這樣進程從內核返回彈出棧頂時就返回到用戶定義的函數處,從函數返回再彈出棧頂時,才返回原先進入內核的地方。 這樣作的緣由是用戶定義的處理函數不能且不容許在內核態下執行(若是用戶定義的函數在內核態下運行的話,用戶就能夠得到任何權限)。
下面引用一個例子說明這個過程:
SIGQUIT
信號的處理函數sighandler
。main
函數,這時發生中斷或異常切換到內核態。main
函數以前檢查到有信號SIGQUIT
遞達。main
函數的上下文繼續執行,而是執行sighandler
函數,sighandler
和main
函數使用不一樣的堆棧空間,它們之間不存在調用和被調用的關係,是兩個獨立的控制流程。sighandler
函數返回後自動執行特殊的系統調用sigreturn
再次進入內核態。main
函數的上下文繼續執行了。下面的代碼收到程序退出信號後會執行用戶定義的信號處理函數來替代系統默認的處理程序。
#include<stdlib.h>
#include<stdio.h>
#include<signal.h>
#include<sys/types.h>
#include<unistd.h>
void sig_handle(int sig) {
printf("received signal: %d, quit.\n", sig);
exit(0);
}
int main () {
signal(SIGINT, sig_handle);
signal(SIGKILL, sig_handle);
signal(SIGSEGV, sig_handle);
signal(SIGTERM, sig_handle);
int i = 0;
while (1) {
printf("%d\n", ++i);
sleep(2);
}
printf("main quit.");
return 0;
}
複製代碼
運行結果:
1
2
received signal: 15, quit.
複製代碼
這段代碼功能與上面的例子差很少,信號也能夠傳參,可是更多的是通知功能,可傳遞的信息很是有限,若是須要傳遞大量的信息,能夠考慮其餘進程間通訊方式。
#include<signal.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
void new_op(int, siginfo_t*, void*);
int main() {
if (NULL == freopen("sigproc.log", "w", stdout)) {
fprintf(stderr, "error redirecting stdout\n");
}
struct sigaction act;
sigemptyset(&act.sa_mask); //sa_mask指定在信號處理程序執行過程當中,哪些信號應當被阻塞。缺省狀況下當前信號自己被阻塞,防止信號的嵌套發送
sigaddset(&act.sa_mask, SIGTERM);
sigaddset(&act.sa_mask, SIGINT);
act.sa_flags = SA_SIGINFO; //SA_SIGINFO,當設定了該標誌位時,表示信號附帶的參數能夠被傳遞到信號處理函數中
act.sa_sigaction = new_op;
if (sigaction(SIGINT, &act, NULL) < 0) {
printf("install sigal error\n");
}
if (sigaction(SIGTERM, &act, NULL) < 0) {
printf("install sigal error\n");
}
if (sigaction(SIGHUP, &act, NULL) < 0) {
printf("install sigal error\n");
}
int i = 0;
while (1) {
printf("%d\n", ++i);
sleep(1);
}
printf("end.");
return 0;
}
void new_op(int signum, siginfo_t *info, void *myact) {
printf("receive signal %d\n", signum);
for (int i = 0; i < 5; ++i) {
printf("signal processing: %d\n", i);
sleep(1);
}
printf("process quit.");
exit(0);
}
複製代碼
運行結果:
1
2
3
receive signal 15
signal processing: 0
signal processing: 1
signal processing: 2
signal processing: 3
signal processing: 4
process quit.
複製代碼
最後,歡迎關注微信公衆號,Let's go!