【摘要】本文分析了Linux內核對於信號的實現機制和應用層的相關處理。首先介紹了軟中斷信號的本質及信號的兩種不一樣分類方法尤爲是不可靠信號的原理。接着分析了內核對於信號的處理流程包括信號的觸發/註冊/執行及註銷等。最後介紹了應用層的相關處理,主要包括信號處理函數的安裝、信號的發送、屏蔽阻塞等,最後給了幾個簡單的應用實例。html
【關鍵字】軟中斷信號,signal,sigaction,kill,sigqueue,settimer,sigmask,sigprocmask,sigset_tlinux
1 信號本質
軟中斷信號(signal,又簡稱爲信號)用來通知進程發生了異步事件。在軟件層次上是對中斷機制的一種模擬,在原理上,一個進程收到一個信號與處理器收到一箇中斷請求能夠說是同樣的。信號是進程間通訊機制中惟一的異步通訊機制,一個進程沒必要經過任何操做來等待信號的到達,事實上,進程也不知道信號到底何時到達。進程之間能夠互相經過系統調用kill發送軟中斷信號。內核也能夠由於內部事件而給進程發送信號,通知進程發生了某個事件。信號機制除了基本通知功能外,還能夠傳遞附加信息。程序員
收到信號的進程對各類信號有不一樣的處理方法。處理方法能夠分爲三類:session
第一種是相似中斷的處理程序,對於須要處理的信號,進程能夠指定處理函數,由該函數來處理。數據結構
第二種方法是,忽略某個信號,對該信號不作任何處理,就象未發生過同樣。多線程
第三種方法是,對該信號的處理保留系統的默認值,這種缺省操做,對大部分的信號的缺省操做是使得進程終止。進程經過系統調用signal來指定進程對某個信號的處理行爲。架構
2 信號的種類
能夠從兩個不一樣的分類角度對信號進行分類:異步
可靠性方面:可靠信號與不可靠信號;函數
與時間的關係上:實時信號與非實時信號。
2.1 可靠信號與不可靠信號
Linux信號機制基本上是從Unix系統中繼承過來的。早期Unix系統中的信號機制比較簡單和原始,信號值小於SIGRTMIN的信號都是不可靠信號。這就是"不可靠信號"的來源。它的主要問題是信號可能丟失。
隨着時間的發展,實踐證實了有必要對信號的原始機制加以改進和擴充。因爲原來定義的信號已有許多應用,很差再作改動,最終只好又新增長了一些信號,並在一開始就把它們定義爲可靠信號,這些信號支持排隊,不會丟失。
信號值位於SIGRTMIN和SIGRTMAX之間的信號都是可靠信號,可靠信號克服了信號可能丟失的問題。Linux在支持新版本的信號安裝函數sigation()以及信號發送函數sigqueue()的同時,仍然支持早期的signal()信號安裝函數,支持信號發送函數kill()。
信號的可靠與不可靠只與信號值有關,與信號的發送及安裝函數無關。目前linux中的signal()是經過sigation()函數實現的,所以,即便經過signal()安裝的信號,在信號處理函數的結尾也沒必要再調用一次信號安裝函數。同時,由signal()安裝的實時信號支持排隊,一樣不會丟失。
對於目前linux的兩個信號安裝函數:signal()及sigaction()來講,它們都不能把SIGRTMIN之前的信號變成可靠信號(都不支持排隊,仍有可能丟失,仍然是不可靠信號),並且對SIGRTMIN之後的信號都支持排隊。這兩個函數的最大區別在於,通過sigaction安裝的信號都能傳遞信息給信號處理函數,而通過signal安裝的信號不能向信號處理函數傳遞信息。對於信號發送函數來講也是同樣的。
2.2 實時信號與非實時信號
早期Unix系統只定義了32種信號,前32種信號已經有了預約義值,每一個信號有了肯定的用途及含義,而且每種信號都有各自的缺省動做。如按鍵盤的CTRL ^C時,會產生SIGINT信號,對該信號的默認反應就是進程終止。後32個信號表示實時信號,等同於前面闡述的可靠信號。這保證了發送的多個實時信號都被接收。
非實時信號都不支持排隊,都是不可靠信號;實時信號都支持排隊,都是可靠信號。
3 信號處理流程
對於一個完整的信號生命週期(從信號發送到相應的處理函數執行完畢)來講,能夠分爲三個階段:
信號誕生
信號在進程中註冊
信號的執行和註銷
3.1 信號誕生
信號事件的發生有兩個來源:硬件來源(好比咱們按下了鍵盤或者其它硬件故障);軟件來源,最經常使用發送信號的系統函數是kill, raise, alarm和setitimer以及sigqueue函數,軟件來源還包括一些非法運算等操做。
這裏按發出信號的緣由簡單分類,以瞭解各類信號:
(1) 與進程終止相關的信號。當進程退出,或者子進程終止時,發出這類信號。
(2) 與進程例外事件相關的信號。如進程越界,或企圖寫一個只讀的內存區域(如程序正文區),或執行一個特權指令及其餘各類硬件錯誤。
(3) 與在系統調用期間遇到不可恢復條件相關的信號。如執行系統調用exec時,原有資源已經釋放,而目前系統資源又已經耗盡。
(4) 與執行系統調用時遇到非預測錯誤條件相關的信號。如執行一個並不存在的系統調用。
(5) 在用戶態下的進程發出的信號。如進程調用系統調用kill向其餘進程發送信號。
(6) 與終端交互相關的信號。如用戶關閉一個終端,或按下break鍵等狀況。
(7) 跟蹤進程執行的信號。
Linux支持的信號列表以下。不少信號是與機器的體系結構相關的
信號值 默認處理動做 發出信號的緣由
SIGHUP 1 A 終端掛起或者控制進程終止
SIGINT 2 A 鍵盤中斷(如break鍵被按下)
SIGQUIT 3 C 鍵盤的退出鍵被按下
SIGILL 4 C 非法指令
SIGABRT 6 C 由abort(3)發出的退出指令
SIGFPE 8 C 浮點異常
SIGKILL 9 AEF Kill信號
SIGSEGV 11 C 無效的內存引用
SIGPIPE 13 A 管道破裂: 寫一個沒有讀端口的管道
SIGALRM 14 A 由alarm(2)發出的信號
SIGTERM 15 A 終止信號
SIGUSR1 30,10,16 A 用戶自定義信號1
SIGUSR2 31,12,17 A 用戶自定義信號2
SIGCHLD 20,17,18 B 子進程結束信號
SIGCONT 19,18,25 進程繼續(曾被中止的進程)
SIGSTOP 17,19,23 DEF 終止進程
SIGTSTP 18,20,24 D 控制終端(tty)上按下中止鍵
SIGTTIN 21,21,26 D 後臺進程企圖從控制終端讀
SIGTTOU 22,22,27 D 後臺進程企圖從控制終端寫
處理動做一項中的字母含義以下
A 缺省的動做是終止進程
B 缺省的動做是忽略此信號,將該信號丟棄,不作處理
C 缺省的動做是終止進程並進行內核映像轉儲(dump core),內核映像轉儲是指將進程數據在內存的映像和進程在內核結構中的部份內容以必定格式轉儲到文件系統,而且進程退出執行,這樣作的好處是爲程序員提供了方便,使得他們能夠獲得進程當時執行時的數據值,容許他們肯定轉儲的緣由,而且能夠調試他們的程序。
D 缺省的動做是中止進程,進入中止情況之後還能從新進行下去,通常是在調試的過程當中(例如ptrace系統調用)
E 信號不能被捕獲
F 信號不能被忽略
3.2 信號在目標進程中註冊
在進程表的表項中有一個軟中斷信號域,該域中每一位對應一個信號。內核給一個進程發送軟中斷信號的方法,是在進程所在的進程表項的信號域設置對應於該信號的位。若是信號發送給一個正在睡眠的進程,若是進程睡眠在可被中斷的優先級上,則喚醒進程;不然僅設置進程表中信號域相應的位,而不喚醒進程。若是發送給一個處於可運行狀態的進程,則只置相應的域便可。
進程的task_struct結構中有關於本進程中未決信號的數據成員: struct sigpending pending:
struct sigpending{
struct sigqueue *head, *tail;
sigset_t signal;
};
第三個成員是進程中全部未決信號集,第1、第二個成員分別指向一個sigqueue類型的結構鏈(稱之爲"未決信號信息鏈")的首尾,信息鏈中的每一個sigqueue結構刻畫一個特定信號所攜帶的信息,並指向下一個sigqueue結構:
struct sigqueue{
struct sigqueue *next;
siginfo_t info;
}
信號在進程中註冊指的就是信號值加入到進程的未決信號集sigset_t signal(每一個信號佔用一位)中,而且信號所攜帶的信息被保留到未決信號信息鏈的某個sigqueue結構中。只要信號在進程的未決信號集中,代表進程已經知道這些信號的存在,但還沒來得及處理,或者該信號被進程阻塞。
當一個實時信號發送給一個進程時,無論該信號是否已經在進程中註冊,都會被再註冊一次,所以,信號不會丟失,所以,實時信號又叫作"可靠信號"。這意味着同一個實時信號能夠在同一個進程的未決信號信息鏈中佔有多個sigqueue結構(進程每收到一個實時信號,都會爲它分配一個結構來登記該信號信息,並把該結構添加在未決信號鏈尾,即全部誕生的實時信號都會在目標進程中註冊)。
當一個非實時信號發送給一個進程時,若是該信號已經在進程中註冊(經過sigset_t signal指示),則該信號將被丟棄,形成信號丟失。所以,非實時信號又叫作"不可靠信號"。這意味着同一個非實時信號在進程的未決信號信息鏈中,至多佔有一個sigqueue結構。
總之信號註冊與否,與發送信號的函數(如kill()或sigqueue()等)以及信號安裝函數(signal()及sigaction())無關,只與信號值有關(信號值小於SIGRTMIN的信號最多隻註冊一次,信號值在SIGRTMIN及SIGRTMAX之間的信號,只要被進程接收到就被註冊)
3.3 信號的執行和註銷
內核處理一個進程收到的軟中斷信號是在該進程的上下文中,所以,進程必須處於運行狀態。當其因爲被信號喚醒或者正常調度從新得到CPU時,在其從內核空間返回到用戶空間時會檢測是否有信號等待處理。若是存在未決信號等待處理且該信號沒有被進程阻塞,則在運行相應的信號處理函數前,進程會把信號在未決信號鏈中佔有的結構卸掉。
對於非實時信號來講,因爲在未決信號信息鏈中最多隻佔用一個sigqueue結構,所以該結構被釋放後,應該把信號在進程未決信號集中刪除(信號註銷完畢);而對於實時信號來講,可能在未決信號信息鏈中佔用多個sigqueue結構,所以應該針對佔用sigqueue結構的數目區別對待:若是隻佔用一個sigqueue結構(進程只收到該信號一次),則執行完相應的處理函數後應該把信號在進程的未決信號集中刪除(信號註銷完畢)。不然待該信號的全部sigqueue處理完畢後再在進程的未決信號集中刪除該信號。
當全部未被屏蔽的信號都處理完畢後,便可返回用戶空間。對於被屏蔽的信號,當取消屏蔽後,在返回到用戶空間時會再次執行上述檢查處理的一套流程。
內核處理一個進程收到的信號的時機是在一個進程從內核態返回用戶態時。因此,當一個進程在內核態下運行時,軟中斷信號並不當即起做用,要等到將返回用戶態時才處理。進程只有處理完信號纔會返回用戶態,進程在用戶態下不會有未處理完的信號。
處理信號有三種類型:進程接收到信號後退出;進程忽略該信號;進程收到信號後執行用戶設定用系統調用signal的函數。當進程接收到一個它忽略的信號時,進程丟棄該信號,就象沒有收到該信號似的繼續運行。若是進程收到一個要捕捉的信號,那麼進程從內核態返回用戶態時執行用戶定義的函數。並且執行用戶定義的函數的方法很巧妙,內核是在用戶棧上建立一個新的層,該層中將返回地址的值設置成用戶定義的處理函數的地址,這樣進程從內核返回彈出棧頂時就返回到用戶定義的函數處,從函數返回再彈出棧頂時,才返回原先進入內核的地方。這樣作的緣由是用戶定義的處理函數不能且不容許在內核態下執行(若是用戶定義的函數在內核態下運行的話,用戶就能夠得到任何權限)。
4 信號的安裝
若是進程要處理某一信號,那麼就要在進程中安裝該信號。安裝信號主要用來肯定信號值及進程針對該信號值的動做之間的映射關係,即進程將要處理哪一個信號;該信號被傳遞給進程時,將執行何種操做。
linux主要有兩個函數實現信號的安裝:signal()、sigaction()。其中signal()只有兩個參數,不支持信號傳遞信息,主要是用於前32種非實時信號的安裝;而sigaction()是較新的函數(由兩個系統調用實現:sys_signal以及sys_rt_sigaction),有三個參數,支持信號傳遞信息,主要用來與 sigqueue() 系統調用配合使用,固然,sigaction()一樣支持非實時信號的安裝。sigaction()優於signal()主要體如今支持信號帶有參數。
4.1 signal()
#include <signal.h>
void (*signal(int signum, void (*handler))(int)))(int);
若是該函數原型不容易理解的話,能夠參考下面的分解方式來理解:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler));
第一個參數指定信號的值,第二個參數指定針對前面信號值的處理,能夠忽略該信號(參數設爲SIG_IGN);能夠採用系統默認方式處理信號(參數設爲SIG_DFL);也能夠本身實現處理方式(參數指定一個函數地址)。
若是signal()調用成功,返回最後一次爲安裝信號signum而調用signal()時的handler值;失敗則返回SIG_ERR。
傳遞給信號處理例程的整數參數是信號值,這樣可使得一個信號處理例程處理多個信號。
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
void sigroutine(int dunno)
{ /* 信號處理例程,其中dunno將會獲得信號的值 */
switch (dunno) {
case 1:
printf("Get a signal -- SIGHUP ");
break;
case 2:
printf("Get a signal -- SIGINT ");
break;
case 3:
printf("Get a signal -- SIGQUIT ");
break;
}
return;
}
int main() {
printf("process id is %d ",getpid());
signal(SIGHUP, sigroutine); //* 下面設置三個信號的處理方法
signal(SIGINT, sigroutine);
signal(SIGQUIT, sigroutine);
for (;;) ;
}
其中信號SIGINT由按下Ctrl-C發出,信號SIGQUIT由按下Ctrl-發出。該程序執行的結果以下:
localhost:~$ ./sig_test
process id is 463
Get a signal -SIGINT //按下Ctrl-C獲得的結果
Get a signal -SIGQUIT //按下Ctrl-獲得的結果
//按下Ctrl-z將進程置於後臺
[1]+ Stopped ./sig_test
localhost:~$ bg
[1]+ ./sig_test &
localhost:~$ kill -HUP 463 //向進程發送SIGHUP信號
localhost:~$ Get a signal – SIGHUP
kill -9 463 //向進程發送SIGKILL信號,終止進程
localhost:~$
4.2 sigaction()
#include <signal.h>
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));
sigaction函數用於改變進程接收到特定信號後的行爲。該函數的第一個參數爲信號的值,能夠爲除SIGKILL及SIGSTOP外的任何一個特定有效的信號(爲這兩個信號定義本身的處理函數,將致使信號安裝錯誤)。第二個參數是指向結構sigaction的一個實例的指針,在結構sigaction的實例中,指定了對特定信號的處理,能夠爲空,進程會以缺省方式對信號處理;第三個參數oldact指向的對象用來保存返回的原來對相應信號的處理,可指定oldact爲NULL。若是把第2、第三個參數都設爲NULL,那麼該函數可用於檢查信號的有效性。
第二個參數最爲重要,其中包含了對指定信號的處理、信號所傳遞的信息、信號處理函數執行過程當中應屏蔽掉哪些信號等等。
sigaction結構定義以下:
struct sigaction {
union{
__sighandler_t _sa_handler;
void (*_sa_sigaction)(int,struct siginfo *, void *);
}_u
sigset_t sa_mask;
unsigned long sa_flags;
}
一、聯合數據結構中的兩個元素_sa_handler以及*_sa_sigaction指定信號關聯函數,即用戶指定的信號處理函數。除了能夠是用戶自定義的處理函數外,還能夠爲SIG_DFL(採用缺省的處理方式),也能夠爲SIG_IGN(忽略信號)。
二、由_sa_sigaction是指定的信號處理函數帶有三個參數,是爲實時信號而設的(固然一樣支持非實時信號),它指定一個3參數信號處理函數。第一個參數爲信號值,第三個參數沒有使用,第二個參數是指向siginfo_t結構的指針,結構中包含信號攜帶的數據值,參數所指向的結構以下:
siginfo_t {
int si_signo; /* 信號值,對全部信號有意義*/
int si_errno; /* errno值,對全部信號有意義*/
int si_code; /* 信號產生的緣由,對全部信號有意義*/
union{ /* 聯合數據結構,不一樣成員適應不一樣信號 */
//確保分配足夠大的存儲空間
int _pad[SI_PAD_SIZE];
//對SIGKILL有意義的結構
struct{
...
}...
... ...
... ...
//對SIGILL, SIGFPE, SIGSEGV, SIGBUS有意義的結構
struct{
...
}...
... ...
}
}
前面在討論系統調用sigqueue發送信號時,sigqueue的第三個參數就是sigval聯合數據結構,當調用sigqueue時,該數據結構中的數據就將拷貝到信號處理函數的第二個參數中。這樣,在發送信號同時,就可讓信號傳遞一些附加信息。信號能夠傳遞信息對程序開發是很是有意義的。
三、sa_mask指定在信號處理程序執行過程當中,哪些信號應當被阻塞。缺省狀況下當前信號自己被阻塞,防止信號的嵌套發送,除非指定SA_NODEFER或者SA_NOMASK標誌位。
注:請注意sa_mask指定的信號阻塞的前提條件,是在由sigaction()安裝信號的處理函數執行過程當中由sa_mask指定的信號才被阻塞。
四、sa_flags中包含了許多標誌位,包括剛剛提到的SA_NODEFER及SA_NOMASK標誌位。另外一個比較重要的標誌位是SA_SIGINFO,當設定了該標誌位時,表示信號附帶的參數能夠被傳遞到信號處理函數中,所以,應該爲sigaction結構中的sa_sigaction指定處理函數,而不該該爲sa_handler指定信號處理函數,不然,設置該標誌變得毫無心義。即便爲sa_sigaction指定了信號處理函數,若是不設置SA_SIGINFO,信號處理函數一樣不能獲得信號傳遞過來的數據,在信號處理函數中對這些信息的訪問都將致使段錯誤(Segmentation fault)。
5 信號的發送
發送信號的主要函數有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。
5.1 kill()
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid,int signo)
該系統調用能夠用來向任何進程或進程組發送任何信號。參數pid的值爲信號的接收進程
pid>0 進程ID爲pid的進程
pid=0 同一個進程組的進程
pid<0 pid!=-1 進程組ID爲 -pid的全部進程
pid=-1 除發送進程自身外,全部進程ID大於1的進程
Sinno是信號值,當爲0時(即空信號),實際不發送任何信號,但照常進行錯誤檢查,所以,可用於檢查目標進程是否存在,以及當前進程是否具備向目標發送信號的權限(root權限的進程能夠向任何進程發送信號,非root權限的進程只能向屬於同一個session或者同一個用戶的進程發送信號)。
Kill()最經常使用於pid>0時的信號發送。該調用執行成功時,返回值爲0;錯誤時,返回-1,並設置相應的錯誤代碼errno。下面是一些可能返回的錯誤代碼:
EINVAL:指定的信號sig無效。
ESRCH:參數pid指定的進程或進程組不存在。注意,在進程表項中存在的進程,多是一個尚未被wait收回,但已經終止執行的僵死進程。
EPERM: 進程沒有權力將這個信號發送到指定接收信號的進程。由於,一個進程被容許將信號發送到進程pid時,必須擁有root權力,或者是發出調用的進程的UID 或EUID與指定接收的進程的UID或保存用戶ID(savedset-user-ID)相同。若是參數pid小於-1,即該信號發送給一個組,則該錯誤表示組中有成員進程不能接收該信號。
5.2 sigqueue()
#include <sys/types.h>
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval val)
調用成功返回 0;不然,返回 -1。
sigqueue()是比較新的發送信號系統調用,主要是針對實時信號提出的(固然也支持前32種),支持信號帶有參數,與函數sigaction()配合使用。
sigqueue的第一個參數是指定接收信號的進程ID,第二個參數肯定即將發送的信號,第三個參數是一個聯合數據結構union sigval,指定了信號傳遞的參數,即一般所說的4字節值。
typedef union sigval {
int sival_int;
void *sival_ptr;
}sigval_t;
sigqueue()比kill()傳遞了更多的附加信息,但sigqueue()只能向一個進程發送信號,而不能發送信號給一個進程組。若是signo=0,將會執行錯誤檢查,但實際上不發送任何信號,0值信號可用於檢查pid的有效性以及當前進程是否有權限向目標進程發送信號。
在調用sigqueue時,sigval_t指定的信息會拷貝到對應sig 註冊的3參數信號處理函數的siginfo_t結構中,這樣信號處理函數就能夠處理這些信息了。因爲sigqueue系統調用支持發送帶參數信號,因此比kill()系統調用的功能要靈活和強大得多。
5.3 alarm()
#include <unistd.h>
unsigned int alarm(unsigned int seconds)
系統調用alarm安排內核爲調用進程在指定的seconds秒後發出一個SIGALRM的信號。若是指定的參數seconds爲0,則再也不發送 SIGALRM信號。後一次設定將取消前一次的設定。該調用返回值爲上次定時調用到發送之間剩餘的時間,或者由於沒有前一次定時調用而返回0。
注意,在使用時,alarm只設定爲發送一次信號,若是要屢次發送,就要屢次使用alarm調用。
5.4 setitimer()
如今的系統中不少程序再也不使用alarm調用,而是使用setitimer調用來設置定時器,用getitimer來獲得定時器的狀態,這兩個調用的聲明格式以下:
int getitimer(int which, struct itimerval *value);
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
在使用這兩個調用的進程中加入如下頭文件:
#include <sys/time.h>
該系統調用給進程提供了三個定時器,它們各自有其獨有的計時域,當其中任何一個到達,就發送一個相應的信號給進程,並使得計時器從新開始。三個計時器由參數which指定,以下所示:
TIMER_REAL:按實際時間計時,計時到達將給進程發送SIGALRM信號。
ITIMER_VIRTUAL:僅當進程執行時才進行計時。計時到達將發送SIGVTALRM信號給進程。
ITIMER_PROF:當進程執行時和系統爲該進程執行動做時都計時。與ITIMER_VIR-TUAL是一對,該定時器常常用來統計進程在用戶態和內核態花費的時間。計時到達將發送SIGPROF信號給進程。
定時器中的參數value用來指明定時器的時間,其結構以下:
struct itimerval {
struct timeval it_interval; /* 下一次的取值 */
struct timeval it_value; /* 本次的設定值 */
};
該結構中timeval結構定義以下:
struct timeval {
long tv_sec; /* 秒 */
long tv_usec; /* 微秒,1秒 = 1000000 微秒*/
};
在setitimer 調用中,參數ovalue若是不爲空,則其中保留的是上次調用設定的值。定時器將it_value遞減到0時,產生一個信號,並將it_value的值設定爲it_interval的值,而後從新開始計時,如此往復。當it_value設定爲0時,計時器中止,或者當它計時到期,而it_interval 爲0時中止。調用成功時,返回0;錯誤時,返回-1,並設置相應的錯誤代碼errno:
EFAULT:參數value或ovalue是無效的指針。
EINVAL:參數which不是ITIMER_REAL、ITIMER_VIRT或ITIMER_PROF中的一個。
下面是關於setitimer調用的一個簡單示範,在該例子中,每隔一秒發出一個SIGALRM,每隔0.5秒發出一個SIGVTALRM信號:
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/time.h>
int sec;
void sigroutine(int signo) {
switch (signo) {
case SIGALRM:
printf("Catch a signal -- SIGALRM ");
break;
case SIGVTALRM:
printf("Catch a signal -- SIGVTALRM ");
break;
}
return;
}
int main()
{
struct itimerval value,ovalue,value2;
sec = 5;
printf("process id is %d ",getpid());
signal(SIGALRM, sigroutine);
signal(SIGVTALRM, sigroutine);
value.it_value.tv_sec = 1;
value.it_value.tv_usec = 0;
value.it_interval.tv_sec = 1;
value.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &value, &ovalue);
value2.it_value.tv_sec = 0;
value2.it_value.tv_usec = 500000;
value2.it_interval.tv_sec = 0;
value2.it_interval.tv_usec = 500000;
setitimer(ITIMER_VIRTUAL, &value2, &ovalue);
for (;;) ;
}
該例子的屏幕拷貝以下:
localhost:~$ ./timer_test
process id is 579
Catch a signal – SIGVTALRM
Catch a signal – SIGALRM
Catch a signal – SIGVTALRM
Catch a signal – SIGVTALRM
Catch a signal – SIGALRM
Catch a signal –GVTALRM
5.5 abort()
#include <stdlib.h>
void abort(void);
向進程發送SIGABORT信號,默認狀況下進程會異常退出,固然可定義本身的信號處理函數。即便SIGABORT被進程設置爲阻塞信號,調用abort()後,SIGABORT仍然能被進程接收。該函數無返回值。
5.6 raise()
#include <signal.h>
int raise(int signo)
向進程自己發送信號,參數爲即將發送的信號值。調用成功返回 0;不然,返回 -1。
6 信號集及信號集操做函數:
信號集被定義爲一種數據類型:
typedef struct {
unsigned long sig[_NSIG_WORDS];
} sigset_t
信號集用來描述信號的集合,每一個信號佔用一位。Linux所支持的全部信號能夠所有或部分的出如今信號集中,主要與信號阻塞相關函數配合使用。下面是爲信號集操做定義的相關函數:
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum)
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
sigemptyset(sigset_t *set)初始化由set指定的信號集,信號集裏面的全部信號被清空;
sigfillset(sigset_t *set)調用該函數後,set指向的信號集中將包含linux支持的64種信號;
sigaddset(sigset_t *set, int signum)在set指向的信號集中加入signum信號;
sigdelset(sigset_t *set, int signum)在set指向的信號集中刪除signum信號;
sigismember(const sigset_t *set, int signum)斷定信號signum是否在set指向的信號集中。
7 信號阻塞與信號未決:
每一個進程都有一個用來描述哪些信號遞送到進程時將被阻塞的信號集,該信號集中的全部信號在遞送到進程後都將被阻塞。下面是與信號阻塞相關的幾個函數:
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset));
int sigpending(sigset_t *set));
int sigsuspend(const sigset_t *mask));
sigprocmask()函數可以根據參數how來實現對信號集的操做,操做主要有三種:
SIG_BLOCK 在進程當前阻塞信號集中添加set指向信號集中的信號
SIG_UNBLOCK 若是進程阻塞信號集中包含set指向信號集中的信號,則解除對該信號的阻塞
SIG_SETMASK 更新進程阻塞信號集爲set指向的信號集
sigpending(sigset_t *set))得到當前已遞送到進程,卻被阻塞的全部信號,在set指向的信號集中返回結果。
sigsuspend(const sigset_t *mask))用於在接收到某個信號以前, 臨時用mask替換進程的信號掩碼, 並暫停進程執行,直到收到信號爲止。sigsuspend 返回後將恢復調用以前的信號掩碼。信號處理函數完成後,進程將繼續執行。該系統調用始終返回-1,並將errno設置爲EINTR。
8 信號應用實例
linux下的信號應用並無想象的那麼恐怖,程序員所要作的最多隻有三件事情:
安裝信號(推薦使用sigaction());
實現三參數信號處理函數,handler(int signal,struct siginfo *info, void *);
發送信號,推薦使用sigqueue()。
實際上,對有些信號來講,只要安裝信號就足夠了(信號處理方式採用缺省或忽略)。其餘可能要作的無非是與信號集相關的幾種操做。
實例一:信號發送及處理
實現一個信號接收程序sigreceive(其中信號安裝由sigaction())。
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
struct sigaction act;
int sig;
sig=atoi(argv[1]);
sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=new_op;
if(sigaction(sig,&act,NULL) < 0)
{
printf("install sigal error\n");
}
while(1)
{
sleep(2);
printf("wait for the signal\n");
}
}
void new_op(int signum,siginfo_t *info,void *myact)
{
printf("receive signal %d", signum);
sleep(5);
}
說明,命令行參數爲信號值,後臺運行sigreceive signo &,可得到該進程的ID,假設爲pid,而後再另外一終端上運行kill -s signo pid驗證信號的發送接收及處理。同時,可驗證信號的排隊問題。
實例二:信號傳遞附加信息
主要包括兩個實例:
向進程自己發送信號,並傳遞指針參數
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
struct sigaction act;
union sigval mysigval;
int i;
int sig;
pid_t pid;
char data[10];
memset(data,0,sizeof(data));
for(i=0;i < 5;i++)
data[i]='2';
mysigval.sival_ptr=data;
sig=atoi(argv[1]);
pid=getpid();
sigemptyset(&act.sa_mask);
act.sa_sigaction=new_op;//三參數信號處理函數
act.sa_flags=SA_SIGINFO;//信息傳遞開關,容許傳說參數信息給new_op
if(sigaction(sig,&act,NULL) < 0)
{
printf("install sigal error\n");
}
while(1)
{
sleep(2);
printf("wait for the signal\n");
sigqueue(pid,sig,mysigval);//向本進程發送信號,並傳遞附加信息
}
}
void new_op(int signum,siginfo_t *info,void *myact)//三參數信號處理函數的實現
{
int i;
for(i=0;i<10;i++)
{
printf("%c\n ",(*( (char*)((*info).si_ptr)+i)));
}
printf("handle signal %d over;",signum);
}
這個例子中,信號實現了附加信息的傳遞,信號究竟如何對這些信息進行處理則取決於具體的應用。
不一樣進程間傳遞整型參數:
把1中的信號發送和接收放在兩個程序中,而且在發送過程當中傳遞整型參數。
信號接收程序:
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
struct sigaction act;
int sig;
pid_t pid;
pid=getpid();
sig=atoi(argv[1]);
sigemptyset(&act.sa_mask);
act.sa_sigaction=new_op;
act.sa_flags=SA_SIGINFO;
if(sigaction(sig,&act,NULL)<0)
{
printf("install sigal error\n");
}
while(1)
{
sleep(2);
printf("wait for the signal\n");
}
}
void new_op(int signum,siginfo_t *info,void *myact)
{
printf("the int value is %d \n",info->si_int);
}
信號發送程序:
命令行第二個參數爲信號值,第三個參數爲接收進程ID。
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/types.h>
main(int argc,char**argv)
{
pid_t pid;
int signum;
union sigval mysigval;
signum=atoi(argv[1]);
pid=(pid_t)atoi(argv[2]);
mysigval.sival_int=8;//不表明具體含義,只用於說明問題
if(sigqueue(pid,signum,mysigval)==-1)
printf("send error\n");
sleep(2);
}
注:實例2的兩個例子側重點在於用信號來傳遞信息,目前關於在linux下經過信號傳遞信息的實例很是少,卻是Unix下有一些,但傳遞的基本上都是關於傳遞一個整數
實例三:信號阻塞及信號集操做
#include "signal.h"
#include "unistd.h"
static void my_op(int);
main()
{
sigset_t new_mask,old_mask,pending_mask;
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=(void*)my_op;
if(sigaction(SIGRTMIN+10,&act,NULL))
printf("install signal SIGRTMIN+10 error\n");
sigemptyset(&new_mask);
sigaddset(&new_mask,SIGRTMIN+10);
if(sigprocmask(SIG_BLOCK, &new_mask,&old_mask))
printf("block signal SIGRTMIN+10 error\n");
sleep(10);
printf("now begin to get pending mask and unblock SIGRTMIN+10\n");
if(sigpending(&pending_mask)<0)
printf("get pending mask error\n");
if(sigismember(&pending_mask,SIGRTMIN+10))
printf("signal SIGRTMIN+10 is pending\n");
if(sigprocmask(SIG_SETMASK,&old_mask,NULL)<0)
printf("unblock signal error\n");
printf("signal unblocked\n");
sleep(10);
}
static void my_op(int signum)
{
printf("receive signal %d \n",signum);
}
編譯該程序,並之後臺方式運行。在另外一終端向該進程發送信號(運行kill -s 42 pid,SIGRTMIN+10爲42),查看結果能夠看出幾個關鍵函數的運行機制,信號集相關操做比較簡單。
9 參考鳴謝:
linux信號處理機制(詳解),http://www.zxbc.cn/html/20080712/61613.html
Linux環境進程間通訊(二): 信號(上),鄭彥興 (mlinux@163.com)
signal、sigaction、kill等手冊,最直接而可靠的參考資料。
http://www.linuxjournal.com/modules.php?op=modload&name=NS-help&file=man提供了許多系統調用、庫函數等的在線指南。
http://www.opengroup.org/onlinepubs/007904975/能夠在這裏對許多關鍵函數(包括系統調用)進行查詢,很是好的一個網址
進程間通訊信號(上) http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html
進程間通訊信號(下)http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index2.html