這篇博客http://blog.csdn.net/l_xrui/article/details/72885978講了信號的基本概念與產生方式。
瞭解以下三種概念:
信號遞達(Delivery):實際執行信號的處理動作(三種);
信號未決(Pending):信號從產生到遞達之間的狀態;
信號阻塞(Block):進程可以選擇阻塞某個信號,被阻塞的信號產生時將保持在未決狀態,直到進程解除對此信號的阻塞,才執行遞達的動作。
注意,阻塞和忽略是不同的,只要信號被阻塞就不會遞達,而忽略是在遞達之後可選的一種處理動作。
在每個進程的task_struct中,操作系統爲每個進程提供一套信號機制:
以上可知分別有三個表:阻塞(block)表,未決(pending)表,遞達表
block表與pending表在task_struct中其是以相同的數據類型sigset_t來存儲,sigset_t稱爲信號集,這個類型變量對應每一bit位可以表示對應每個信號的“有效(1)”或“無效(0)”狀態,在阻塞信號集(block)中“有效”和“無效”的含義是該信號是否被阻塞,而在未決信號集中“有效”和“無效”的含義是該信是否處於未決狀態;
遞達表則是以數組來表示,數組下標表示哪一個信號,數組裏內容爲函數指針,指向對應信號的遞達動作。
每個信號都有兩個標誌位分別表示阻塞(block)和未決(pending),還有一個函數指針表示處理動作。信號產生時,內核在進程控制塊中設置該信號的未決標誌,直到信號遞達才清除該標誌,若該信號被阻塞,信號產生時,一直處在未決狀態,直至信號被取消阻塞。
如果在進程解除對某信號的阻塞之前這種信號產生過多次,Linux處理機制是:常規信號在遞達之前產生多次只計一次,而實時信號在遞達之前產生多次可以依次放在一個隊列裏記錄。
由此也可描述上圖:
(1). 1號信號未被阻塞也未產生,它的遞達動作爲忽略;
(2). 2號信號被阻塞並已產生,所以一直在未決狀態,它的遞達動作爲自定義處理動作,當其被取消阻塞,則會在適當時間執行其遞達動作;
(3). 3號信號被阻塞但未產生,其遞達動作爲默認,但當它產生因被阻塞也會一直在阻塞狀態。
由上一篇博客可知指針非法操作進程異常退出可這樣描述:
在進程運行中,非法指針地址通過頁表映射時不可映射訪問被MMU發現,此時操作系統發現MMU出錯,則查找出錯誤原因發送正確信號(SIGSEGV)11號信號給進程,即改變進程pending表的11位改爲有效狀態(1),然後進程發現自己pending表發生改變,接收到信號,其再去查詢block表11號信號是否被阻塞,沒有被阻塞,則信號遞達,信號pending表恢復無效(0),進程就去找到hander表保存的對應的函數地址,去執行了默認動作,最後退出。
信號集操作函數:
使用者只能調用以下函數來操作sigset_t變量:
#include <signal.h>
int sigemptyset(sigset_t *set); //初始化set所指向的信號集,使其中所有信號的對應bit清零
int sigfillset(sigset_t *set); //初始化set所指向的信號集,使其中所有信號的對應bit置有效1位
int sigaddset(sigset_t *set, int signo); //向信號集set中添加使signo位信號有效
int sigdelset(sigset_t *set, int signo); //向信號集set中刪除使signo位信號無效
int sigismember(const sigset_t *set, int signo); //判斷信號集set的有效信號中是否包含signo信號,若包含則返回1,不包含則返回0,出錯返回-1
調用函數sigprocmask可以讀取或更改進程的信號屏蔽字(阻塞信號集):
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
參數:
oset:若set是空指針,oset是非空指針,則讀取進程的當前信號屏蔽字通過oset參數傳出;
若oset和set都是非空指針,則先將原來的信號屏蔽字備份到oset裏,然後根據set和how參數更改信號屏蔽字;
set:若set是非空指針,則更改進程的信號屏蔽字;
how:如何修改,可選值如下:
1. SIG_BLOCK //將set信號集中的有效信號添加到當前阻塞表中阻塞它們
2. SIG_UNBLOCK //將set信號集中的有效信號從當前阻塞表中刪除對它們取消阻塞
3.SIG_SETMASK //將當前阻塞表清空只將set信號集中的有效信號添加進去阻塞它們
返回值:若成功則爲0,若出錯則爲-1
注意:如果調用sigprocmask解除了對當前若干個未決信號的阻塞,則在sigprocmask返回前,至少將其中一個信號遞達。
函數:
int sigpending(sigset_t *set); //sigpending讀取當前進程的未決信號集,通過set參數傳出
返回值:調用成功則返回0,出錯則返回-1
以下用代碼驗證三張表以上關係:
sigblock.c:此處函數實現功能以2號信號(Ctrl-C)舉例:
(1)設置信號集將2號信號加入信號集中,並調用函數sigprocmask()將其阻塞,併爲2號信號設自定義信號捕捉函數(打印當前進程pid與接收到的信號sig);
(2)設一個計數器count,進入無限循環,每隔一秒打印一次block表與pending表,並使count++;
(3)在無限循環中,當count==10時,再調用函數sigprocmask()對2號信號取消阻塞。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
//信號遞達動作
void myhander(int sig)
{
printf("pid## %d receiving sig## %d\n",getpid(),sig);
}
// 打印pending表(信號未決)
void PrintPending(sigset_t set)
{
int i=1;
for(;i<32;++i)
{
if(sigismember(&set,i))
{
printf("1 ");
}
else
{
printf("0 ");
}
}
printf("\n");
}
int main()
{
//定義信號集
sigset_t s;
//初始化信號集爲0
sigemptyset(&s);
//將2號信號加入信號集中:ctrl c==2
sigaddset(&s,2);
sigset_t oldset; //以保存block表中舊的信號屏蔽字
//sigprocmask(SIG_BLOCK,&s,&oldset); //將S信號集中信號加入block(阻塞表)中 :此處阻塞了2號信號
sigprocmask(SIG_SETMASK,&s,&oldset); //將block表中阻塞信號清空只將S信號集中的信號屏蔽(設爲S的值)
signal(2,myhander); //給2號信號設捕捉函數
int count=0;
while(1)
{
//獲取當前阻塞(block)表,並打印
sigset_t s1;
sigprocmask(0,NULL,&s1);
printf("block list:");
PrintPending(s1);
//定義 pending表
sigset_t p;
sigpending(&p); //獲取當前進程的pending表(即進程收到哪些信號並處在未決狀態)
printf("pend list:");
PrintPending(p); //打印查看pending表
if(count==10)
{
//sigprocmask(SIG_UNBLOCK,&s,&oldset); //從block表中刪除信號集S中的信號,即取消它們的阻塞
sigprocmask(SIG_SETMASK,&oldset,&s); //此代碼中以此可實現同樣效果 :以上oldset保存的爲全0
}
sleep(1);
++count;
}
return 0;
}
效果如下:
(1)在前10秒count<=10時,2號信號被阻塞,此時block表與pending表依次爲:
0 1 0 0 0 0 0 0 0 0.........
0 0 0 0 0 0 0 0 0 0.........
在前10秒因爲2號信號被阻塞,若這時鍵盤發送Ctrl-C,2號信號不會遞達(不執行自定義捕捉函數),其會先保持在未決狀態,此時block表與pending表依次爲:
0 1 0 0 0 0 0 0 0 0.........
0 1 0 0 0 0 0 0 0 0.........
(2)當剛過10秒,count==10,2號信號被取消阻塞,這時(1)剛纔發送的2號信號遞達,則執行自定義捕捉函數,並且block表與pending表(2號信號不在未決狀態恢復爲0)依次變爲:
0 0 0 0 0 0 0 0 0 0.........
0 0 0 0 0 0 0 0 0 0.........
(3)在以後,2號信號再沒被阻塞,此時在發送則執行自定義捕捉函數,並且block表與pending表保持爲:
0 0 0 0 0 0 0 0 0 0.........
0 0 0 0 0 0 0 0 0 0.........