【Linux系统编程】信号 (上)
00. 目錄
文章目錄
- 00. 目錄
- 01. 信號概述
- 02. 信號編號
- 03. 信號產(chǎn)生方式
- 04. kill發(fā)送信號
- 05. pause等待信號
- 06. 信號處理方式
- 07. 信號處理函數(shù)
- 08. 附錄
01. 信號概述
信號是 Linux 進(jìn)程間通信的最古老的方式。信號是軟件中斷,它是在軟件層次上對中斷機(jī)制的一種模擬,是一種異步通信的方式 。信號可以導(dǎo)致一個正在運行的進(jìn)程被另一個正在運行的異步進(jìn)程中斷,轉(zhuǎn)而處理某一個突發(fā)事件。
“中斷”在我們生活中經(jīng)常遇到,譬如,我正在房間里打游戲,突然送快遞的來了,把正在玩游戲的我給“中斷”了,我去簽收快遞( 處理中斷 ),處理完成后,再繼續(xù)玩我的游戲。這里我們學(xué)習(xí)的“信號”就是屬于這么一種“中斷”。我們在終端上敲“Ctrl+c”,就產(chǎn)生一個“中斷”,相當(dāng)于產(chǎn)生一個信號,接著就會處理這么一個“中斷任務(wù)”(默認(rèn)的處理方式為中斷當(dāng)前進(jìn)程)。
信號可以直接進(jìn)行用戶空間進(jìn)程和內(nèi)核空間進(jìn)程的交互,內(nèi)核進(jìn)程可以利用它來通知用戶空間進(jìn)程發(fā)生了哪些系統(tǒng)事件。
一個完整的信號周期包括三個部分:信號的產(chǎn)生,信號在進(jìn)程中的注冊,信號在進(jìn)程中的注銷,執(zhí)行信號處理函數(shù)。如下圖所示:
注意:這里信號的產(chǎn)生,注冊,注銷時信號的內(nèi)部機(jī)制,而不是信號的函數(shù)實現(xiàn)。
02. 信號編號
Linux 可使用命令:kill -l(“l(fā)” 為字母),查看相應(yīng)的信號。
deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ kill -l1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP6) 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 deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$列表中,編號為 1 ~ 31 的信號為傳統(tǒng) UNIX 支持的信號,是不可靠信號(非實時的),編號為 32 ~ 63 的信號是后來擴(kuò)充的,稱做可靠信號(實時信號)。不可靠信號和可靠信號的區(qū)別在于前者不支持排隊,可能會造成信號丟失,而后者不會。非可靠信號一般都有確定的用途及含義, 可靠信號則可以讓用戶自定義使用。
03. 信號產(chǎn)生方式
3.1 當(dāng)用戶按某些終端鍵時,將產(chǎn)生信號。
終端上按“Ctrl+c”組合鍵通常產(chǎn)生中斷信號 SIGINT,終端上按“Ctrl+\”鍵通常產(chǎn)生中斷信號 SIGQUIT,終端上按“Ctrl+z”鍵通常產(chǎn)生中斷信號 SIGSTOP 等。
3.2 硬件異常將產(chǎn)生信號。
除數(shù)為 0,無效的內(nèi)存訪問等。這些情況通常由硬件檢測到,并通知內(nèi)核,然后內(nèi)核產(chǎn)生適當(dāng)?shù)男盘柊l(fā)送給相應(yīng)的進(jìn)程。
3.3 軟件異常將產(chǎn)生信號。
當(dāng)檢測到某種軟件條件已發(fā)生,并將其通知有關(guān)進(jìn)程時,產(chǎn)生信號。
3.4 調(diào)用 kill() 函數(shù)將發(fā)送信號。
注意:接收信號進(jìn)程和發(fā)送信號進(jìn)程的所有者必須相同,或發(fā)送信號進(jìn)程的所有者必須是超級用戶。
3.5 運行 kill 命令將發(fā)送信號。
此程序?qū)嶋H上是使用 kill 函數(shù)來發(fā)送信號。也常用此命令終止一個失控的后臺進(jìn)程。
04. kill發(fā)送信號
#include <sys/types.h> #include <signal.h>int kill(pid_t pid, int sig); 功能:給指定進(jìn)程發(fā)送指定信號(不一定殺死)參數(shù):pid : 取值有 4 種情況 :pid > 0: 將信號傳送給進(jìn)程 ID 為pid的進(jìn)程。pid = 0 : 將信號傳送給當(dāng)前進(jìn)程所在進(jìn)程組中的所有進(jìn)程。pid = -1 : 將信號傳送給系統(tǒng)內(nèi)所有的進(jìn)程。pid < -1 : 將信號傳給指定進(jìn)程組的所有進(jìn)程。這個進(jìn)程組號等于 pid 的絕對值。sig : 信號的編號,這里可以填數(shù)字編號,也可以填信號的宏定義,可以通過命令 kill - l("l" 為字母)進(jìn)行相應(yīng)查看。不推薦直接使用數(shù)字,應(yīng)使用宏名,因為不同操作系統(tǒng)信號編號可能不同,但名稱一致。返回值:成功:0失敗:-1注意:使用 kill() 函數(shù)發(fā)送信號,接收信號進(jìn)程和發(fā)送信號進(jìn)程的所有者必須相同,或者發(fā)送信號進(jìn)程的所有者是超級用戶。
測試代碼:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <signal.h>int main(void) {pid_t pid = -1;pid = fork();if (-1 == pid){perror("fork"); goto err0;}else if (0 == pid){while(1){printf("child process do thing --- Hello uplooking\n"); sleep(1);}exit(0);}printf("parent process do thing\n");sleep(3);//向指定的進(jìn)程發(fā)送指定的信號//kill(pid, SIGKILL);kill(pid, 9);return 0; err0:return 1; }測試結(jié)果:
05. pause等待信號
#include <unistd.h>int pause(void); 功能:等待信號的到來(此函數(shù)會阻塞)。將調(diào)用進(jìn)程掛起直至捕捉到信號為止,此函數(shù)通常用于判斷信號是否已到。 參數(shù):無。 返回值:直到捕獲到信號才返回 -1,且 errno 被設(shè)置成 EINTR。測試代碼:
#include <unistd.h> #include <stdio.h>int main(int argc, char *argv[]) {printf("in pause function\n");pause();return 0; }測試結(jié)果:
deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ gcc 1.c deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ ./a.out in pause function ^C deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$沒有產(chǎn)生信號前,進(jìn)程一直阻塞在 pause() 不會往下執(zhí)行,假如,我們按“Ctrl+c”,pause() 會捕獲到此信號,中斷當(dāng)前進(jìn)程。
06. 信號處理方式
一個進(jìn)程收到一個信號的時候,可以用如下方法進(jìn)行處理:
1)執(zhí)行系統(tǒng)默認(rèn)動作
對大多數(shù)信號來說,系統(tǒng)默認(rèn)動作是用來終止該進(jìn)程。
2)忽略此信號
接收到此信號后沒有任何動作。
3)執(zhí)行自定義信號處理函數(shù)
用用戶定義的信號處理函數(shù)處理該信號。
注意:SIGKILL 和 SIGSTOP 不能更改信號的處理方式,因為它們向用戶提供了一種使進(jìn)程終止的可靠方法。
產(chǎn)生一個信號,我們可以讓其執(zhí)行自定義信號處理函數(shù)。假如有函數(shù) A, B, C,我們?nèi)绾未_定信號產(chǎn)生后只調(diào)用函數(shù) A,而不是函數(shù) B 或 C。這時候,我們需要一種規(guī)則規(guī)定,信號產(chǎn)生后就調(diào)用函數(shù) A,就像交通規(guī)則一樣,紅燈停綠燈行,信號注冊函數(shù) signal() 就是做這樣的事情。
07. 信號處理函數(shù)
#include <signal.h>typedef void (*sighandler_t)(int);// 回調(diào)函數(shù)的聲明 sighandler_t signal(int signum,sighandler_t handler);功能:注冊信號處理函數(shù)(不可用于 SIGKILL、SIGSTOP 信號),即確定收到信號后處理函數(shù)的入口地址。此函數(shù)不會阻塞。參數(shù):signum:信號的編號,這里可以填數(shù)字編號,也可以填信號的宏定義,可以通過命令 kill -l 進(jìn)行相應(yīng)查看。handler: 取值有 3 種情況:SIG_IGN:忽略該信號SIG_DFL:執(zhí)行系統(tǒng)默認(rèn)動作信號處理函數(shù)名:自定義信號處理函數(shù),如:fun回調(diào)函數(shù)的定義如下: void fun(int signo) {// signo 為觸發(fā)的信號,為 signal() 第一個參數(shù)的值 }注意:信號處理函數(shù)應(yīng)該為可重入函數(shù)。返回值:成功:第一次返回 NULL,下一次返回此信號上一次注冊的信號處理函數(shù)的地址。如果需要使用此返回值,必須在前面先聲明此函數(shù)指針的類型。失敗:返回 SIG_ERR測試程序一:
#include <stdio.h> #include <signal.h> #include <unistd.h>// 信號處理函數(shù) void signal_handler(int signo) {if(signo == SIGINT){printf("recv SIGINT\n");}else if(signo == SIGQUIT){printf("recv SIGQUIT\n");} }int main(int argc, char *argv[]) {printf("wait for SIGINT OR SIGQUIT\n");/* SIGINT: Ctrl+c ; SIGQUIT: Ctrl+\ */// 信號注冊函數(shù)signal(SIGINT, signal_handler);signal(SIGQUIT, signal_handler);// 等待信號pause();pause();return 0; }在終端里敲“Ctrl+c”或“Ctrl+\”,自動調(diào)用其指定好的回調(diào)函數(shù) signal_handler():
deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ gcc 1.c deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ ./a.out wait for SIGINT OR SIGQUIT ^Crecv SIGINT ^\recv SIGQUIT deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$測試程序二:
#include <stdio.h> #include <signal.h> #include <unistd.h>// 回調(diào)函數(shù)的聲明 typedef void (*sighandler_t)(int);void fun1(int signo) {printf("in fun1\n"); }void fun2(int signo) {printf("in fun2\n"); }int main(int argc, char *argv[]) {sighandler_t previous = NULL;// 第一次返回 NULLprevious = signal(SIGINT,fun1); if(previous == NULL){printf("return fun addr is NULL\n");}// 下一次返回此信號上一次注冊的信號處理函數(shù)的地址。previous = signal(SIGINT, fun2);if(previous == fun1){printf("return fun addr is fun1\n");}// 還是返回 NULL,因為處理的信號變了previous = signal(SIGQUIT,fun1);if(previous == NULL){printf("return fun addr is NULL\n");}return 0; }執(zhí)行結(jié)果:
deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ gcc 1.c deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ ./a.out return fun addr is NULL return fun addr is fun1 return fun addr is NULL deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$08. 附錄
8.1 參考博客:【linux系統(tǒng)編程】進(jìn)程間通信:信號中斷處理
總結(jié)
以上是生活随笔為你收集整理的【Linux系统编程】信号 (上)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Linux系统编程】进程间通信概述
- 下一篇: 【Linux系统编程】信号 (下)