http://my.oschina.net/chenliang165/blog/125825.
最近同事的程序設計過程中用到了Linux的signal機制,從而引發了我對Linux中signal機制的思考。Signal機制在Linux中是一個非常常用的進程間通信機制,很多人在使用的時候不會考慮該機制是具體如何實現的。signal機制可以被理解成進程的軟中斷,因此,在實時性方面還是相對比較高的。Linux中signal機制的模型可以采用下圖進行描述。
每個進程都會采用一個進程控制塊對其進行描述,進程控制塊中設計了一個signal的位圖信息,其中的每位與具體的signal相對應,這與中斷機制是保持一致的。當系統中一個進程A通過signal系統調用向進程B發送signal時,設置進程B的對應signal位圖,類似於觸發了signal對應中斷。發送signal只是“中斷”觸發的一個過程,具體執行會在兩個階段發生:
1、 system call返回。進程B由於調用了system call后,從內核返回用戶態時需要檢查他擁有的signal位圖信息表,此時是一個執行點。
2、 中斷返回。進程被系統中斷打斷之后,系統將CPU交給進程時,需要檢查即將執行進程所擁有的signal位圖信息表,此時也是一個執行點。
綜上所述,signal的執行點可以理解成從內核態返回用戶態時,在返回時,如果發現待執行進程存在被觸發的signal,那么在離開內核態之后(也就是將CPU切換到用戶模式),執行用戶進程為該signal綁定的signal處理函數,從這一點上看,signal處理函數是在用戶進程上下文中執行的。當執行完signal處理函數之后,再返回到用戶進程被中斷或者system call(軟中斷或者指令陷阱)打斷的地方。
Signal機制實現的比較靈活,用戶進程由於中斷或者system call陷入內核之后,將斷點信息都保存到了堆棧中,在內核返回用戶態時,如果存在被觸發的signal,那么直接將待執行的signal處理函數push到堆棧中,在CPU切換到用戶模式之后,直接pop堆棧就可以執行signal處理函數並且返回到用戶進程了。Signal處理函數應用了進程上下文,並且應用實際的中斷模擬了進程的軟中斷過程。
最近寫程序,各種bug各種錯,有一回程序莫名退出,沒報錯,也沒產生日志和core文件,貌似正常退出一樣。
但又不是在程序全部走完后退出,中途莫名退出,這就叫我想到了signal,應該是某些函數錯誤后發送kill信號給主進程,然后退出。
現在總結下signal各種類型:
Signal
|
Description
|
SIGABRT
|
由調用abort函數產生,進程非正常退出
|
SIGALRM
|
用alarm函數設置的timer超時或setitimer函數設置的interval timer超時
|
SIGBUS
|
某種特定的硬件異常,通常由內存訪問引起
|
SIGCANCEL
|
由Solaris Thread Library內部使用,通常不會使用
|
SIGCHLD
|
進程Terminate或Stop的時候,SIGCHLD會發送給它的父進程。缺省情況下該Signal會被忽略
|
SIGCONT
|
當被stop的進程恢復運行的時候,自動發送
|
SIGEMT
|
和實現相關的硬件異常
|
SIGFPE
|
數學相關的異常,如被0除,浮點溢出,等等
|
SIGFREEZE
|
Solaris專用,Hiberate或者Suspended時候發送
|
SIGHUP
|
發送給具有Terminal的Controlling Process,當terminal被disconnect時候發送
|
SIGILL
|
非法指令異常
|
SIGINFO
|
BSD signal。由Status Key產生,通常是CTRL+T。發送給所有Foreground Group的進程
|
SIGINT
|
由Interrupt Key產生,通常是CTRL+C或者DELETE。發送給所有ForeGround Group的進程
|
SIGIO
|
異步IO事件
|
SIGIOT
|
實現相關的硬件異常,一般對應SIGABRT
|
SIGKILL
|
無法處理和忽略。中止某個進程
|
SIGLWP
|
由Solaris Thread Libray內部使用
|
SIGPIPE
|
在reader中止之后寫Pipe的時候發送
|
SIGPOLL
|
當某個事件發送給Pollable Device的時候發送
|
SIGPROF
|
Setitimer指定的Profiling Interval Timer所產生
|
SIGPWR
|
和系統相關。和UPS相關。
|
SIGQUIT
|
輸入Quit Key的時候(CTRL+\)發送給所有Foreground Group的進程
|
SIGSEGV
|
非法內存訪問
|
SIGSTKFLT
|
Linux專用,數學協處理器的棧異常
|
SIGSTOP
|
中止進程。無法處理和忽略。
|
SIGSYS
|
非法系統調用
|
SIGTERM
|
請求中止進程,kill命令缺省發送
|
SIGTHAW
|
Solaris專用,從Suspend恢復時候發送
|
SIGTRAP
|
實現相關的硬件異常。一般是調試異常
|
SIGTSTP
|
Suspend Key,一般是Ctrl+Z。發送給所有Foreground Group的進程
|
SIGTTIN
|
當Background Group的進程嘗試讀取Terminal的時候發送
|
SIGTTOU
|
當Background Group的進程嘗試寫Terminal的時候發送
|
SIGURG
|
當out-of-band data接收的時候可能發送
|
SIGUSR1
|
用戶自定義signal 1
|
SIGUSR2
|
用戶自定義signal 2
|
SIGVTALRM
|
setitimer函數設置的Virtual Interval Timer超時的時候
|
SIGWAITING
|
Solaris Thread Library內部實現專用
|
SIGWINCH
|
當Terminal的窗口大小改變的時候,發送給Foreground Group的所有進程
|
SIGXCPU
|
當CPU時間限制超時的時候
|
SIGXFSZ
|
進程超過文件大小限制
|
SIGXRES
|
Solaris專用,進程超過資源限制的時候發送
|
signal對應的值:
POSIX.1中列出的信號:
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 后台進程企圖從控制終端寫
沒在POSIX.1中列出,而在SUSv2列出
SIGBUS 10,7,10 C 總線錯誤(錯誤的內存訪問)
SIGPOLL A Sys V定義的Pollable事件,與SIGIO同義
SIGPROF 27,27,29 A Profiling定時器到
SIGSYS 12,-,12 C 無效的系統調用 (SVID)
SIGTRAP 5 C 跟蹤/斷點捕獲
SIGURG 16,23,21 B Socket出現緊急條件(4.2 BSD)
SIGVTALRM 26,26,28 A 實際時間報警時鍾信號(4.2 BSD)
SIGXCPU 24,24,30 C 超出設定的CPU時間限制(4.2 BSD)
SIGXFSZ 25,25,31 C 超出設定的文件大小限制(4.2 BSD)
(對於SIGSYS,SIGXCPU,SIGXFSZ,以及某些機器體系結構下的SIGBUS,Linux缺省的動作是A (terminate),SUSv2 是C (terminate and dump core))。
下面是其它的一些信號
信號 值 處理動作 發出信號的原因
----------------------------------------------------------------------
SIGIOT 6 C IO捕獲指令,與SIGABRT同義
SIGEMT 7,-,7
SIGSTKFLT -,16,- A 協處理器堆棧錯誤
SIGIO 23,29,22 A 某I/O操作現在可以進行了(4.2 BSD)
SIGCLD -,-,18 A 與SIGCHLD同義
SIGPWR 29,30,19 A 電源故障(System V)
SIGINFO 29,-,- A 與SIGPWR同義
SIGLOST -,-,- A 文件鎖丟失
SIGWINCH 28,28,20 B 窗口大小改變(4.3 BSD, Sun)
SIGUNUSED -,31,- A 未使用的信號(will be SIGSYS)
(在這里,- 表示信號沒有實現;有三個值給出的含義為,第一個值通常在Alpha和Sparc上有效,中間的值對應i386和ppc以及sh,最后一個值對應mips。信號29在Alpha上為SIGINFO / SIGPWR ,在Sparc上為SIGLOST。)
處理動作一項中的字母含義如下
A 缺省的動作是終止進程
B 缺省的動作是忽略此信號
C 缺省的動作是終止進程並進行內核映像轉儲(dump core)
D 缺省的動作是停止進程
E 信號不能被捕獲
F 信號不能被忽略
測試代碼:
01 |
#include<stdio.h> |
02 |
#include<signal.h> |
03 |
#include<unistd.h> |
04 |
#include<stdlib.h> |
05 |
void when_alarm(); |
06 |
void when_sigint(); |
07 |
void when_sigchld( int ); |
08 |
void when_sigusr1(); |
09 |
void when_sigio(); |
10 |
int main() |
11 |
{ |
12 |
int childpid; //子程序進程ID號 |
13 |
printf ( "程序已經開始運行,5秒鍾后將接收到時鍾信號。/n" ); |
14 |
if ((childpid=fork())>0) //父進程 |
15 |
{ |
16 |
signal (SIGALRM,when_alarm); //當接收到SIGALRM信號時,調用when_alarm函數 |
17 |
signal (SIGINT,when_sigint); //當接收到SIGINT信號時,調用when_sigint函數 |
18 |
signal (SIGCHLD,when_sigchld); //當接收到SIGCHLD信號時,調用when_sigchld函數 |
19 |
signal (SIGUSR1,when_sigusr1); //當接收到SIGUSR1信號時,調用when_sigusr1函數 |
20 |
signal (SIGIO,when_sigio); //當接收到SIGIO信號時,調用when_sigio函數 |
21 |
alarm(5); //5秒鍾之后產生SIGALRM信號 |
22 |
raise (SIGIO); //向自己發送一個SIGIO信號 |
23 |
pause(); //將父進程暫停下來,等待SIGALRM信號到來 |
24 |
pause(); //將父進程暫停下來,等待SIGUSR1信號到來 |
25 |
pause(); //將父進程暫停下來,等待SIGCHLD信號到來 |
26 |
printf ( "------此時程序會停下來等待,請按下ctrl+c送出SIGINT信號-------/n" ); |
27 |
pause(); //將父進程暫停下來,等待SIGINT信號到來 |
28 |
} |
29 |
else if (childpid==0) //子進程 |
30 |
{ |
31 |
int timer; |
32 |
for (timer=7;timer>=0;timer--) //時鍾計時5秒產生SIGALRM信號,再過2秒子進程退出,產生SIGCHLD信號 |
33 |
{ |
34 |
if (timer>2) |
35 |
printf ( "距離SIGALRM信號到來還有%d秒。/n" ,timer-2); |
36 |
if (timer==4) |
37 |
kill(getppid(),SIGUSR1); //向父進程發送一個SIGUSR1信號 |
38 |
if ((timer<=2)&&(timer>0)) |
39 |
printf ( "子進程還剩%d秒退出,屆時會產生SIGCHLD信號。/n" ,timer); |
40 |
if (timer==0) //子進程退出,產生SIGCHLD信號 |
41 |
raise (SIGKILL); //子進程給自己發一個結束信號 |
42 |
sleep(1); //每個循環延時1秒鍾 |
43 |
} |
44 |
} |
45 |
else |
46 |
printf ( "fork()函數調用出現錯誤!/n" ); |
47 |
return 0; |
48 |
} |
49 |
void when_alarm() |
50 |
{ |
51 |
printf ( "5秒鍾時間已到,系統接收到了SIGALRM信號!/n" ); |
52 |
} |
53 |
void when_sigint() |
54 |
{ |
55 |
printf ( "已經接收到了SIGINT信號,程序將退出!/n" ); |
56 |
exit (0); |
57 |
} |
58 |
void when_sigchld( int SIGCHLD_num) |
59 |
{ |
60 |
printf ( "收到SIGCHLD信號,表明我的子進程已經中止,SIGCHLD信號的數值是:%d。/n" ,SIGCHLD_num); |
61 |
} |
62 |
void when_sigusr1() |
63 |
{ |
64 |
printf ( "系統接收到了用戶自定義信號SIGUSR1。/n" ); |
65 |
} |
66 |
void when_sigio() |
67 |
{ |
68 |
printf ( "系統接收到了SIGIO信號。/n" ); |
69 |
} |