原理
對于 Linux來說,實際信號是軟中斷,許多重要的程序都需要處理信號。
信號,為 Linux 提供了一種處理異步事件的方法。比如,終端用戶輸入了ctrl+c來中斷程序,會通過信號機制停止一個程序。
概述
信號的名字和編號
每個信號都有一個名字和編號,這些名字都以“SIG”開頭,例如“SIGUP(掛起) ”、“SIGINT(中斷)、SIGQUIT(退出)”等等。
信號定義在signal.h頭文件中,信號名都定義為正整數。
具體的信號名稱可以 使用kill -l來查看信號的名字以及序號,
信號是從1開始編號的,不存在0號信號。kill對于信號0有特殊的應用。
信號的處理
信號的處理方式有三種,分別是忽略、捕捉和默認動作。
忽略信號
大多數信號可以使用這個方式來處理,但是有兩種信號不能被忽略(分別是 SIGKILL和SIGSTOP)。因為他們向內核和超級用戶提供了進程終止和停止的可靠方法,如果忽略了,那么這個進程就變成了沒人能管理的的進程,顯然是內核設計者不希望看到的場景。
系統自帶的忽略宏函數
signal(SIGINT,SIG_IGN);//將SIGINT信號(ctrl+C、2)忽略
捕捉信號
需要告訴內核,用戶希望如何處理某一種信號,說白了就是寫一個信號處理函數,然后將這個函數告訴內核。當該信號產生時,由內核來調用用戶自定義的函數,以此來實現某種信號的處理。(?在main函數外定義一個函數,用signal函數中的參數調用該函數并執行函數中的功能)
系統默認動作
對于每個信號來說,系統都對應由默認的處理動作,當發生了該信號,系統會自動執行。不過,對系統來說,大部分的處理方式都比較粗暴,就是直接殺死該進程。
具體的信號默認動作可以使用man 7 signal來查看系統的具體定義。在此,我就不詳細展開了,需要查看的,可以自行查看。也可以參考 《UNIX 環境高級編程(第三部)》的 P251——P256中間對于每個信號有詳細的說明。
例子如下:
其實對于常用的 kill 命令就是一個發送信號的工具,kill 9 PID 來殺死進程。比如,我在后臺運行了一個 a.out?工具,通過 ps 命令可以查看他的 PID,通過 kill 9來發送了一個終止進程的信號來結束了 a.out?進程。如果查看信號編號和名稱,可以發現9對應的是 SIGKILL,正是殺死該進程的信號。而以下的執行過程實際也就是執行了9號信號的默認動作——殺死進程。
kill -9 進程PID
kill -SIGKILL 進程PID
可見,兩者的執行結果相同。說明kill命令是發送信號的工具。
signal函數
功能
設置某一信號的對應動作
頭文件
#include <signal.h>
原型
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
函數解讀
第一行是真實處理信號的函數:中斷函數的原型中,有一個參數是 int 類型,顯然也是信號產生的類型,方便使用一個函數來處理多個信號,即注冊函數的第二個參數可以調用信號處理函數并執行其中的功能。
第二行是信號處理注冊的函數:
signum | 信號的編號,如SIGKILL的編號是9 |
handler | 中斷函數的指針,寫入后可以調用編寫的真實處理信號函數并執行功能 |
signal()會依參數signum指定的信號編號來設置該信號的處理函數。當指定的信號到達時就會跳轉到參數handler指定的函數執行。
返回值
成功則返回先前的信號處理函數指針,如果有錯誤則返回SIG_ERR(-1)。
代碼示例
信號處理函數的注冊
signal1.c
#include <stdio.h>
#include <signal.h>void handler(int signum)
{printf("get signum is %d\n",signum);printf("not quit\n");switch(signum){case 2:printf("SIGINT\n");break;case 9:printf("SIGKILL\n");break;case 10:printf("SIGUSR1\n");break;}
}int main()
{signal(SIGINT,handler);signal(SIGUSR1,handler);while(1);return 0;
}
代碼編譯后查看運行a.out工具,通過ps查看其編號
運用kill指令分別對信號進行處理
注:第一種按下crtl+c執行結果相同。
可見調用signal函數后匹配的正確編號后會執行handler中的功能(將函數編號打印出來)。第三個與前兩個結果不一樣是因為SIGKILL指令無法被忽略,這里的kill -9發出的是指令,由于代碼為死循環,若SIGKILL被忽視,則會導致代碼無法終止循環,所以一旦SIGKILL指令發出,程序立刻停止(被殺死)。
發送信號處理函數
signal2.c
#include <stdio.h>
#include <signal.h>int main(int argc,char **argv)//由于需要此代碼發送指令另一部分代碼才會執行,所以需要進行傳參,參數為kill參數,格式為./a.out pid signum
{int signum;int pid;signum = atoi(argv[1]);pid = atoi(argv[2]);kill(pid,signum);//調用kill函數,將信號處理編號和工具的pid值輸入即可printf("send signal success\n");return 0;
}
?先編譯signal1.c(上一模塊的代碼)并運行
調用ps指令查看該程序的信號值
編譯運行signal2.c中的代碼傳參即可運行signal1.c代碼中的功能
將signum與pid輸入后即可實現signal1.c中的功能,實現信號捕捉處理。
功能與signal2.c一樣的代碼:
#include <stdio.h>
#include <signal.h>int main(int argc,char **argv)
{int signum;int pid;char cmd[128] = {0};signum = atoi(argv[1]);pid = atoi(argv[2]);sprintf(cmd,"kill -%d %d",pid,signum);//cmd的指令格式為“”里的格式,即調用kill指令system(cmd);//調用cmd指令printf("send signal success\n");return 0;
}
注:
1、atoi (表示 ascii to integer)是把字符串轉換成整型數的一個函數。
2、sprintf指的是字符串格式化命令,函數原型為
int sprintf(char *string, char *format [,argument,…]);
主要功能是把格式化的數據寫入某個字符串中,即發送格式化輸出到 string 所指向的字符串。
信號忽略函數的補充
代碼展示
#include<stdio.h>
#include <signal.h>
void handler(int signum)
{printf("get signum=%d\n",signum);switch(signum){case 2:printf("SIGINT\n");break;case 10:printf("SIGUSR1\n");break;}
}
int main()
{signal(SIGINT,SIG_IGN);//將SIGINT信號(ctrl+C、2)忽略signal(SIGUSR1,SIG_IGN);//將SIGUSR1信號(10)忽略while(1);return 0;
}
可見crtl+c和kill -10和kill -2都被忽略了,只有kill -9才能使該程序終止,印證的信號處理中的忽略部分不能忽略SIGKILL。