linux c之信號(hào)signal處理機(jī)制
最近同事的程序設(shè)計(jì)過(guò)程中用到了Linux的signal機(jī)制,從而引發(fā)了我對(duì)Linux中signal機(jī)制的思考。Signal機(jī)制在Linux中是一個(gè)非常常用的進(jìn)程間通信機(jī)制,很多人在使用的時(shí)候不會(huì)考慮該機(jī)制是具體如何實(shí)現(xiàn)的。signal機(jī)制可以被理解成進(jìn)程的軟中斷,因此,在實(shí)時(shí)性方面還是相對(duì)比較高的。Linux中signal機(jī)制的模型可以采用下圖進(jìn)行描述。
個(gè)進(jìn)程都會(huì)采用一個(gè)進(jìn)程控制塊對(duì)其進(jìn)行描述,進(jìn)程控制塊中設(shè)計(jì)了一個(gè)signal的位圖信息,其中的每位與具體的signal相對(duì)應(yīng),這與中斷機(jī)制是保持一致的。當(dāng)系統(tǒng)中一個(gè)進(jìn)程A通過(guò)signal系統(tǒng)調(diào)用向進(jìn)程B發(fā)送signal時(shí),設(shè)置進(jìn)程B的對(duì)應(yīng)signal位圖,類似于觸發(fā)了signal對(duì)應(yīng)中斷。發(fā)送signal只是“中斷”觸發(fā)的一個(gè)過(guò)程,具體執(zhí)行會(huì)在兩個(gè)階段發(fā)生:
1、? system call返回。進(jìn)程B由于調(diào)用了system call后,從內(nèi)核返回用戶態(tài)時(shí)需要檢查他擁有的signal位圖信息表,此時(shí)是一個(gè)執(zhí)行點(diǎn)。
2、? 中斷返回。進(jìn)程被系統(tǒng)中斷打斷之后,系統(tǒng)將CPU交給進(jìn)程時(shí),需要檢查即將執(zhí)行進(jìn)程所擁有的signal位圖信息表,此時(shí)也是一個(gè)執(zhí)行點(diǎn)。
綜上所述,signal的執(zhí)行點(diǎn)可以理解成從內(nèi)核態(tài)返回用戶態(tài)時(shí),在返回時(shí),如果發(fā)現(xiàn)待執(zhí)行進(jìn)程存在被觸發(fā)的signal,那么在離開(kāi)內(nèi)核態(tài)之后(也就是將CPU切換到用戶模式),執(zhí)行用戶進(jìn)程為該signal綁定的signal處理函數(shù),從這一點(diǎn)上看,signal處理函數(shù)是在用戶進(jìn)程上下文中執(zhí)行的。當(dāng)執(zhí)行完signal處理函數(shù)之后,再返回到用戶進(jìn)程被中斷或者system call(軟中斷或者指令陷阱)打斷的地方。
Signal機(jī)制實(shí)現(xiàn)的比較靈活,用戶進(jìn)程由于中斷或者system call陷入內(nèi)核之后,將斷點(diǎn)信息都保存到了堆棧中,在內(nèi)核返回用戶態(tài)時(shí),如果存在被觸發(fā)的signal,那么直接將待執(zhí)行的signal處理函數(shù)push到堆棧中,在CPU切換到用戶模式之后,直接pop堆棧就可以執(zhí)行signal處理函數(shù)并且返回到用戶進(jìn)程了。Signal處理函數(shù)應(yīng)用了進(jìn)程上下文,并且應(yīng)用實(shí)際的中斷模擬了進(jìn)程的軟中斷過(guò)程。
最近寫程序,各種bug各種錯(cuò),有一回程序莫名退出,沒(méi)報(bào)錯(cuò),也沒(méi)產(chǎn)生日志和core文件,貌似正常退出一樣。
但又不是在程序全部走完后退出,中途莫名退出,這就叫我想到了signal,應(yīng)該是某些函數(shù)錯(cuò)誤后發(fā)送kill信號(hào)給主進(jìn)程,然后退出。
現(xiàn)在總結(jié)下signal各種類型:
Signal
Description
SIGABRT
由調(diào)用abort函數(shù)產(chǎn)生,進(jìn)程非正常退出
SIGALRM
用alarm函數(shù)設(shè)置的timer超時(shí)或setitimer函數(shù)設(shè)置的interval timer超時(shí)
SIGBUS
某種特定的硬件異常,通常由內(nèi)存訪問(wèn)引起
SIGCANCEL
由Solaris Thread Library內(nèi)部使用,通常不會(huì)使用
SIGCHLD
進(jìn)程Terminate或Stop的時(shí)候,SIGCHLD會(huì)發(fā)送給它的父進(jìn)程。缺省情況下該Signal會(huì)被忽略
SIGCONT
當(dāng)被stop的進(jìn)程恢復(fù)運(yùn)行的時(shí)候,自動(dòng)發(fā)送
SIGEMT
和實(shí)現(xiàn)相關(guān)的硬件異常
SIGFPE
數(shù)學(xué)相關(guān)的異常,如被0除,浮點(diǎn)溢出,等等
SIGFREEZE
Solaris專用,Hiberate或者Suspended時(shí)候發(fā)送
SIGHUP
發(fā)送給具有Terminal的Controlling Process,當(dāng)terminal被disconnect時(shí)候發(fā)送
SIGILL
非法指令異常
SIGINFO
BSD signal。由Status Key產(chǎn)生,通常是CTRL+T。發(fā)送給所有Foreground Group的進(jìn)程
SIGINT
由Interrupt Key產(chǎn)生,通常是CTRL+C或者DELETE。發(fā)送給所有ForeGround Group的進(jìn)程
SIGIO
異步IO事件
SIGIOT
實(shí)現(xiàn)相關(guān)的硬件異常,一般對(duì)應(yīng)SIGABRT
SIGKILL
無(wú)法處理和忽略。中止某個(gè)進(jìn)程
SIGLWP
由Solaris Thread Libray內(nèi)部使用
SIGPIPE
在reader中止之后寫Pipe的時(shí)候發(fā)送
SIGPOLL
當(dāng)某個(gè)事件發(fā)送給Pollable Device的時(shí)候發(fā)送
SIGPROF
Setitimer指定的Profiling Interval Timer所產(chǎn)生
SIGPWR
和系統(tǒng)相關(guān)。和UPS相關(guān)。
SIGQUIT
輸入Quit Key的時(shí)候(CTRL+\)發(fā)送給所有Foreground Group的進(jìn)程
SIGSEGV
非法內(nèi)存訪問(wèn)
SIGSTKFLT
Linux專用,數(shù)學(xué)協(xié)處理器的棧異常
SIGSTOP
中止進(jìn)程。無(wú)法處理和忽略。
SIGSYS
非法系統(tǒng)調(diào)用
SIGTERM
請(qǐng)求中止進(jìn)程,kill命令缺省發(fā)送
SIGTHAW
Solaris專用,從Suspend恢復(fù)時(shí)候發(fā)送
SIGTRAP
實(shí)現(xiàn)相關(guān)的硬件異常。一般是調(diào)試異常
SIGTSTP
Suspend Key,一般是Ctrl+Z。發(fā)送給所有Foreground Group的進(jìn)程
SIGTTIN
當(dāng)Background Group的進(jìn)程嘗試讀取Terminal的時(shí)候發(fā)送
SIGTTOU
當(dāng)Background Group的進(jìn)程嘗試寫Terminal的時(shí)候發(fā)送
SIGURG
當(dāng)out-of-band data接收的時(shí)候可能發(fā)送
SIGUSR1
用戶自定義signal 1
SIGUSR2
用戶自定義signal 2
SIGVTALRM
setitimer函數(shù)設(shè)置的Virtual Interval Timer超時(shí)的時(shí)候
SIGWAITING
Solaris Thread Library內(nèi)部實(shí)現(xiàn)專用
SIGWINCH
當(dāng)Terminal的窗口大小改變的時(shí)候,發(fā)送給Foreground Group的所有進(jìn)程
SIGXCPU
當(dāng)CPU時(shí)間限制超時(shí)的時(shí)候
SIGXFSZ
進(jìn)程超過(guò)文件大小限制
SIGXRES
Solaris專用,進(jìn)程超過(guò)資源限制的時(shí)候發(fā)送
signal對(duì)應(yīng)的值:
POSIX.1中列出的信號(hào):
SIGHUP 1 A 終端掛起或者控制進(jìn)程終止
SIGINT 2 A 鍵盤中斷(如break鍵被按下)
SIGQUIT 3 C 鍵盤的退出鍵被按下
SIGILL 4 C 非法指令
SIGABRT 6 C 由abort(3)發(fā)出的退出指令
SIGFPE 8 C 浮點(diǎn)異常
SIGKILL 9 AEF Kill信號(hào)
SIGSEGV 11 C 無(wú)效的內(nèi)存引用
SIGPIPE 13 A 管道破裂: 寫一個(gè)沒(méi)有讀端口的管道
SIGALRM 14 A 由alarm(2)發(fā)出的信號(hào)
SIGTERM 15 A 終止信號(hào)
SIGUSR1 30,10,16 A 用戶自定義信號(hào)1
SIGUSR2 31,12,17 A 用戶自定義信號(hào)2
SIGCHLD 20,17,18 B 子進(jìn)程結(jié)束信號(hào)
SIGCONT 19,18,25 進(jìn)程繼續(xù)(曾被停止的進(jìn)程)
SIGSTOP 17,19,23 DEF 終止進(jìn)程
SIGTSTP 18,20,24 D 控制終端(tty)上按下停止鍵
SIGTTIN 21,21,26 D 后臺(tái)進(jìn)程企圖從控制終端讀
SIGTTOU 22,22,27 D 后臺(tái)進(jìn)程企圖從控制終端寫
沒(méi)在POSIX.1中列出,而在SUSv2列出
SIGBUS 10,7,10 C 總線錯(cuò)誤(錯(cuò)誤的內(nèi)存訪問(wèn))
SIGPOLL A Sys V定義的Pollable事件,與SIGIO同義
SIGPROF 27,27,29 A Profiling定時(shí)器到
SIGSYS 12,-,12 C 無(wú)效的系統(tǒng)調(diào)用 (SVID)
SIGTRAP 5 C 跟蹤/斷點(diǎn)捕獲
SIGURG 16,23,21 B Socket出現(xiàn)緊急條件(4.2 BSD)
SIGVTALRM 26,26,28 A 實(shí)際時(shí)間報(bào)警時(shí)鐘信號(hào)(4.2 BSD)
SIGXCPU 24,24,30 C 超出設(shè)定的CPU時(shí)間限制(4.2 BSD)
SIGXFSZ 25,25,31 C 超出設(shè)定的文件大小限制(4.2 BSD)
(對(duì)于SIGSYS,SIGXCPU,SIGXFSZ,以及某些機(jī)器體系結(jié)構(gòu)下的SIGBUS,Linux缺省的動(dòng)作是A (terminate),SUSv2 是C (terminate and dump core))。
代碼測(cè)試
#include
#include
#include
#include
void when_alarm();
void when_sigint();
void when_sigchld(int);
void when_sigusr1();
void when_sigio();
int main()
{
int childpid;//子程序進(jìn)程ID號(hào)
printf("程序已經(jīng)開(kāi)始運(yùn)行,5秒鐘后將接收到時(shí)鐘信號(hào)。/n");
if ((childpid=fork())>0)//父進(jìn)程
{
signal(SIGALRM,when_alarm); //當(dāng)接收到SIGALRM信號(hào)時(shí),調(diào)用when_alarm函數(shù)
signal(SIGINT,when_sigint); //當(dāng)接收到SIGINT信號(hào)時(shí),調(diào)用when_sigint函數(shù)
signal(SIGCHLD,when_sigchld);//當(dāng)接收到SIGCHLD信號(hào)時(shí),調(diào)用when_sigchld函數(shù)
signal(SIGUSR1,when_sigusr1);//當(dāng)接收到SIGUSR1信號(hào)時(shí),調(diào)用when_sigusr1函數(shù)
signal(SIGIO,when_sigio);//當(dāng)接收到SIGIO信號(hào)時(shí),調(diào)用when_sigio函數(shù)
alarm(5); //5秒鐘之后產(chǎn)生SIGALRM信號(hào)
raise(SIGIO); //向自己發(fā)送一個(gè)SIGIO信號(hào)
pause(); //將父進(jìn)程暫停下來(lái),等待SIGALRM信號(hào)到來(lái)
pause(); //將父進(jìn)程暫停下來(lái),等待SIGUSR1信號(hào)到來(lái)
pause(); //將父進(jìn)程暫停下來(lái),等待SIGCHLD信號(hào)到來(lái)
printf("------此時(shí)程序會(huì)停下來(lái)等待,請(qǐng)按下ctrl+c送出SIGINT信號(hào)-------/n");
pause(); //將父進(jìn)程暫停下來(lái),等待SIGINT信號(hào)到來(lái)
}
else if(childpid==0) //子進(jìn)程
{
int timer;
for(timer=7;timer>=0;timer--) //時(shí)鐘計(jì)時(shí)5秒產(chǎn)生SIGALRM信號(hào),再過(guò)2秒子進(jìn)程退出,產(chǎn)生SIGCHLD信號(hào)
{
if(timer>2)
printf("距離SIGALRM信號(hào)到來(lái)還有%d秒。/n",timer-2);
if(timer==4)
kill(getppid(),SIGUSR1); //向父進(jìn)程發(fā)送一個(gè)SIGUSR1信號(hào)
if((timer<=2)&&(timer>0))
printf("子進(jìn)程還剩%d秒退出,屆時(shí)會(huì)產(chǎn)生SIGCHLD信號(hào)。/n",timer);
if(timer==0) //子進(jìn)程退出,產(chǎn)生SIGCHLD信號(hào)
raise(SIGKILL); //子進(jìn)程給自己發(fā)一個(gè)結(jié)束信號(hào)
sleep(1); //每個(gè)循環(huán)延時(shí)1秒鐘
}
}
else
printf("fork()函數(shù)調(diào)用出現(xiàn)錯(cuò)誤!/n");
return 0;
}
void when_alarm()
{
printf("5秒鐘時(shí)間已到,系統(tǒng)接收到了SIGALRM信號(hào)!/n");
}
void when_sigint()
{
printf("已經(jīng)接收到了SIGINT信號(hào),程序?qū)⑼顺觯?n");
exit(0);
}
void when_sigchld(int SIGCHLD_num)
{
printf("收到SIGCHLD信號(hào),表明我的子進(jìn)程已經(jīng)中止,SIGCHLD信號(hào)的數(shù)值是:%d。/n",SIGCHLD_num);
}
void when_sigusr1()
{
printf("系統(tǒng)接收到了用戶自定義信號(hào)SIGUSR1。/n");
}
void when_sigio()
{
printf("系統(tǒng)接收到了SIGIO信號(hào)。/n");
}
Linux 任務(wù)調(diào)度
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。