1. 信號概念
信號是進程在運行過程中,由自身產生或由進程外部發過來的消息(事件)。信號是硬件中斷的軟件模擬(軟中斷)。每個信號用一個整型常量宏表示,以SIG開頭,比如SIGCHLD、SIGINT等,它們在系統頭文件
信號的生成來自內核,讓內核生成信號的請求來自3個地方:
l 用戶:用戶能夠通過輸入CTRL+c、Ctrl+,或者是終端驅動程序分配給信號控制字符的其他任何鍵來請求內核產生信號;
l 內核:當進程執行出錯時,內核會給進程發送一個信號,例如非法段存取(內存訪問違規)、浮點數溢出等;
l 進程:一個進程可以通過系統調用kill給另一個進程發送信號,一個進程可以通過信號和另外一個進程進行通信。
由進程的某個操作產生的信號稱為同步信號(synchronous signals),例如除0;由象用戶擊鍵這樣的進程外部事件產生的信號叫做異步信號。(asynchronous signals)。
進程接收到信號以后,可以有如下3種選擇進行處理:
l 接收默認處理:接收默認處理的進程通常會導致進程本身消亡。例如連接到終端的進程,用戶按下CTRL+c,將導致內核向進程發送一個SIGINT的信號,進程如果不對該信號做特殊的處理,系統將采用默認的方式處理該信號,即終止進程的執行;
l 忽略信號:進程可以通過代碼,顯示地忽略某個信號的處理,例如:signal(SIGINT,SIGDEF);但是某些信號是不能被忽略的,
l 捕捉信號并處理:進程可以事先注冊信號處理函數,當接收到信號時,由信號處理函數自動捕捉并且處理信號。
有兩個信號既不能被忽略也不能被捕捉,它們是SIGKILL和SIGSTOP。即進程接收到這兩個信號后,只能接受系統的默認處理,即終止線程。
2. signal信號處理機制
可以用函數signal注冊一個信號捕捉函數。原型為:
#include
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signal 的第1個參數signum表示要捕捉的信號,第2個參數是個函數指針,表示要對該信號進行捕捉的函數,該參數也可以是SIG_DEF(表示交由系統缺省處理,相當于白注冊了)或SIG_IGN(表示忽略掉該信號而不做任何處理)。signal如果調用成功,返回以前該信號的處理函數的地址,否則返回 SIG_ERR。
sighandler_t是信號捕捉函數,由signal函數注冊,注冊以后,在整個進程運行過程中均有效,并且對不同的信號可以注冊同一個信號捕捉函數。該函數只有一個參數,表示信號值。
示例:
1、 捕捉終端CTRL+c產生的SIGINT信號:
#include
#include
#include
#include
void SignHandler(int iSignNo)
{
printf("Capture sign no:%d ",iSignNo);
}
int main()
{
signal(SIGINT,SignHandler);
while(true)
sleep(1);
return 0;
}
該程序運行起來以后,通過按 CTRL+c將不再終止程序的運行。應為CTRL+c產生的SIGINT信號已經由進程中注冊的SignHandler函數捕捉了。該程序可以通過 Ctrl+終止,因為組合鍵Ctrl+能夠產生SIGQUIT信號,而該信號的捕捉函數尚未在程序中注冊。
2、 忽略掉終端CTRL+c產生的SIGINT信號:
#include
#include
#include
#include
int main()
{
signal(SIGINT,SIG_IGN);
while(true)
sleep(1);
return 0;
}
該程序運行起來以后,將CTRL+C產生的SIGINT信號忽略掉了,所以CTRL+C將不再能是該進程終止,要終止該進程,可以向進程發送SIGQUIT信號,即組合鍵CTRL+
3、 接受信號的默認處理,接受默認處理就相當于沒有寫信號處理程序:
#include
#include
#include
#include
int main()
{
signal(SIGINT,DEF);
while(true)
sleep(1);
return 0;
}
3. sigaction信號處理機制
3.1. 信號處理情況分析
在signal處理機制下,還有許多特殊情況需要考慮:
1、 冊一個信號處理函數,并且處理完畢一個信號之后,是否需要重新注冊,才能夠捕捉下一個信號;
2、 如果信號處理函數正在處理信號,并且還沒有處理完畢時,又發生了一個同類型的信號,這時該怎么處理;
3、 如果信號處理函數正在處理信號,并且還沒有處理完畢時,又發生了一個不同類型的信號,這時該怎么處理;
4、 如果程序阻塞在一個系統調用(如read(...))時,發生了一個信號,這時是讓系統調用返回錯誤再接著進入信號處理函數,還是先跳轉到信號處理函數,等信號處理完畢后,系統調用再返回。
示例:
#include
#include
#include
#include
int g_iSeq=0;
void SignHandler(int iSignNo)
{
int iSeq=g_iSeq++;
printf("%d Enter SignHandler,signo:%d. ",iSeq,iSignNo);
sleep(3);
printf("%d Leave SignHandler,signo:%d ",iSeq,iSignNo);
}
int main()
{
char szBuf[8];
int iRet;
signal(SIGINT,SignHandler);
signal(SIGQUIT,SignHandler);
do{
iRet=read(STDIN_FILENO,szBuf,sizeof(szBuf)-1);
if(iRet<0){
perror("read fail.");
break;
}
szBuf[iRet]=0;
printf("Get: %s",szBuf);
}while(strcmp(szBuf,"quit ")!=0);
return 0;
}
程序運行時,針對于如下幾種輸入情況(要輸入得快),看輸出結果:
1、 CTRL+c] [CTRL+c] [CTRL+c]
2、 [CTRL+c] [CTRL+]
3、 hello [CTRL+] [Enter]
4、 [CTRL+] hello [Enter]
5、 hel [CTRL+] lo[Enter]
針對于上面各種情況,不同版本OS可能有不同的響應結果。
3.2. sigaction信號處理注冊
如果要想用程序控制上述各種情況的響應結果,就必須采用新的信號捕獲機制,即使用sigaction信號處理機制。
函數原型:
#include
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
sigaction也用于注冊一個信號處理函數。
參數signum為需要捕捉的信號;
參數 act是一個結構體,里面包含信號處理函數地址、處理方式等信息。
參數oldact是一個傳出參數,sigaction函數調用成功后,oldact里面包含以前對signum的處理方式的信息。
如果函數調用成功,將返回0,否則返回-1
結構體 struct sigaction(注意名稱與函數sigaction相同)的原型為:
struct sigaction {
void (*sa_handler)(int); // 老類型的信號處理函數指針
void (*sa_sigaction)(int, siginfo_t *, void *);//新類型的信號處理函數指針
sigset_t sa_mask; // 將要被阻塞的信號集合
int sa_flags; // 信號處理方式掩碼
void (*sa_restorer)(void); // 保留,不要使用。
}
該結構體的各字段含義及使用方式:
1、字段sa_handler是一個函數指針,用于指向原型為void handler(int)的信號處理函數地址, 即老類型 的信號處理函數;
2、字段sa_sigaction也是一個函數指針,用于指向原型為:
void handler(int iSignNum,siginfo_t *pSignInfo,void *pReserved);
的信號處理函數,即新類型的信號處理函數。
該函數的三個參數含義為:
iSignNum :傳入的信號
pSignInfo :與該信號相關的一些信息,它是個結構體
pReserved :保留,現沒用
3、字段sa_handler和sa_sigaction只應該有一個生效,如果想采用老的信號處理機制,就應該讓sa_handler指向正確的信號處理函數;否則應該讓sa_sigaction指向正確的信號處理函數,并且讓字段 sa_flags包含SA_SIGINFO選項。
4、字段sa_mask是一個包含信號集合的結構體,該結構體內的信號表示在進行信號處理時,將要被阻塞的信號。針對sigset_t結構體,有一組專門的函數對它進行處理,它們是:
#include
int sigemptyset(sigset_t *set); // 清空信號集合set
int sigfillset(sigset_t *set); // 將所有信號填充進set中
int sigaddset(sigset_t *set, int signum); // 往set中添加信號signum
int sigdelset(sigset_t *set, int signum); // 從set中移除信號signum
int sigismember(const sigset_t *set, int signum); // 判斷signnum是不是包含在set中
例如,如果打算在處理信號SIGINT時,只阻塞對SIGQUIT信號的處理,可以用如下種方法:
struct sigaction act;
sigemptyset(&act.sa_mask);
sigaddset(&act_sa_mask,SIGQUIT);
sigaction(SIGINT,&act,NULL);
5、 字段sa_flags是一組掩碼的合成值,指示信號處理時所應該采取的一些行為,各掩碼的含義為:
掩碼 描述
SA_RESETHAND 處理完畢要捕捉的信號后,將自動撤消信號處理函數的注冊,即必須再重新注冊信號處理函數,才能繼續處理接下來產生的信號。該選項不符合一般的信號處理流程,現已經被廢棄。
SA_NODEFER 在處理信號時,如果又發生了其它的信號,則立即進入其它信號的處理,等其它信號處理完畢后,再繼續處理當前的信號,即遞規地處理。如果sa_flags包含了該掩碼,則結構體sigaction的sa_mask將無效!
SA_RESTART 如果在發生信號時,程序正阻塞在某個系統調用,例如調用read()函數,則在處理完畢信號后,接著從阻塞的系統返回。該掩碼符合普通的程序處理流程,所以一般來說,應該設置該掩碼,否則信號處理完后,阻塞的系統調用將會返回失?。?
SA_SIGINFO 指示結構體的信號處理函數指針是哪個有效,如果sa_flags包含該掩碼,則sa_sigactiion指針有效,否則是sa_handler指針有效。