一,信號在內核中的表示
1,在此之前,必須先了解幾個概念:
信號遞達(Delivery):實際執行信號處理的動作。
信號未決(Pending):信號從產生到遞達之間的狀態。
信號阻塞(Block):被阻塞的信號產生時將保持在未決狀態,直到 進程解除對此信號的阻塞,才 執行遞達的動作。
注意:信號阻塞和信號忽略是不同的。只要信號被阻塞就不會遞達,除非解除阻塞,而忽略是在遞達之後 可選的一種處理動作。
一個信號處於Pending狀態,屏蔽之後,它永遠不會被Delivery 。
一個信號是否立即Delivery ,與Block沒有絕對關係。
一個信號是否被Block與是否Pending沒關係。
2,信號在內核中的表示—-每個進程都會維護這3張表:
每個信號都有兩個標誌位分別表示阻塞(block)和未決(pending),還有一個函數指針表示處理動作。信號產生時,內核在進程控制塊中設置該信號的未決標誌,直到信號遞達才清除該標誌。
3張表的存儲:
pending表:用4個字節的位圖表示,位圖的位置表示信號編號,內容表示是否pending。
block表:用4個字節的位圖表示,位圖的位置表示信號編號,內容表示是否block。
handler表:是一個句柄函數指針,數組即可表示,下標表示信號編號,內容表示信號處理的動作,爲NULL表示沒有處理該信號。
分析上圖中的信號:
1〉SIGHUP信號未阻塞也未產生過,當它遞達時執行默認處理動作。
2〉SIGINT信號產生過,但正在被阻塞,所以暫時不能遞達。雖然它的處理動作是忽略,但在沒有解除阻塞之前不能忽略這個信號,因爲進程仍有機會改變處理動作之後再解除阻塞。
3〉 SIGQUIT信號未產生過,一旦產生SIGQUIT信號將被阻塞,它的處理動作是用戶自定義函數sighandler。
3,如果在進程解除對某信號的阻塞之前這種信號產生過多次,將如何處理?
Linux是這樣實現的:常規信號在遞達之前產生多次只計一次,而實時信號在遞達之前產生多次可以依次放在一個隊列⾥裏。從上圖來看,每個信號只有一 個bit的未決標誌,非0即1,不記錄該信號產生了多少次,阻塞標誌也是這樣表示的。
因此,未決和阻塞標誌可以用相同的數據類型sigset_t來存儲,sigset_t稱爲信號集,這個類型可以表示每個信號的“有效”或“無效”狀態,在阻塞信號集中“有效”和“無效”的含義是該信號是否被阻塞,而在未決信號集中“有效”和“無效”的含義是該信號是否處於未決狀態。 阻塞信號集也叫做當前進程的信號屏蔽字(Signal
Mask),這裏的 “屏蔽”應該理解爲阻塞而不是忽略。
注意:信號集和信號量級二者之間沒有任何關係。
當利用信號量機制解決了單個資源的互斥訪問後,我們討論如何控制同時需要多個資源的互斥訪問,信號量集是指同時需要多個資源時的信號量操作。
4,內核是如何處理信號的?
從圖中我們能發現,當一個正在運行的進程收到了中斷或者是調用了系統調用,則該進程會從用戶態進入到內核態,當進程準備從內核態返回到用戶態時,內核會檢查要返回的進程的pcb中的signal位圖信息,如果當前的pengding表中有標誌1,那麼內核會把pengding鏈表中懸掛的信號拿出來進程處理,處理的過程如下:
如果handler指向了用戶自定義的處理函數,那麼會先從內核態返回到用戶態執行完處理函數後再返回到內核態,最後再從內核態返回到用戶態中因爲中斷或者系統調用進入內核態的代碼從而繼續執行。
5,信號集操作函數
信號集:sigsize_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);//爲信號集中添加
某種有效信號
int sigdelset(sigset_t *set, int signo);//爲信號集中刪除某種有效信號
int sigismember(const sigset_t *set, int signo);//判斷⼀一個信號集的有效信號中是否包含某種信號,若包含則返回1,不包含則返回0,出錯返回-1
- 1
- 2
- 3
- 4
- 5
- 6
- 7
這五個函數都是成功返回0,出錯返回-1。
6,函數sigprocmask可以讀取或更改進程的信號屏蔽字(阻塞信號集)
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
- 1
- 2
返回值:若成功則爲0,若出錯則爲-1。
參數:
oset:如果oset是非空指針,則讀取進程的當前信號屏蔽字通過oset參數傳出。如果set是非空指針,則更改進程的信號屏蔽字,參數how指示如何更改。
set:如果oset和set都是非空指針,則先將原來的信號屏蔽字備份到oset裏,然後根據set和how參數更改信號屏蔽字。
how參數的可選值爲:
如果調用sigprocmask解除了對當前若干個未決信號的阻塞,則在sigprocmask返回前,至少將其中一個信號遞達。
7,sigpending讀取當前進程的未決信號集,通過set參數傳出。
#include <signal.h>
int sigpending(sigset_t *set);
- 1
- 2
返回值:調用成功則返回0,出錯則返回-1。
8,以上函數的測試用例
makefile:
1 sigset:sigset.c
2 gcc -o sigset sigset.c
3 .PHONY:clean
4 clean:
5 rm -f sigset
- 1
- 2
- 3
- 4
- 5
sigset.c:
1 /**************************************
2 *文件說明:sigset.c
3 *作者:段曉雪
4 *創建時間:2017年06月04日 星期日 22時41分13秒
5 *開發環境:Kali Linux/g++ v6.3.0
6 ****************************************/
7 #include<stdio.h>
8 #include<signal.h>
9 #include<unistd.h>
10 #include<sys/types.h>
11
12 void showpending(sigset_t* pending)//打印當前進程的pending表
13 {
14 int i = 1;
15 for(; i < 32; ++i)
16 {
17 if(sigismember(pending,i))//判斷pending信號集的有效信號中是否包含信號i,若包含則返回1,不包含則返回0,出錯返回-1
18 printf("1 ");
19 else
20 printf("0 ");
21 }
22 printf("\n");
23 }
24
25 void handler(int sig)
26 {
27 printf("pid is %d,get a signo:%d\n",getpid(),sig);
28 return;
29 }
30
31 int main()
32 {
33 sigset_t sigset,osigset;//定義兩個信號集
34 sigemptyset(&sigset);//信號集siget初始化,將所有bit位清零
35 sigemptyset(&sigset);//信號集osigset初始化,將所有bit位清零
36 sigaddset(&sigset,2);//給信號集sigset添加2號信號
37 sigprocmask(SIG_SETMASK,&sigset,&osigset);//先將原來的信號屏蔽字備份到osigset裏,然後將\
38 sigset的值設置爲當前信號屏蔽字
39
40 signal(2,handler);//捕捉2號信號
41
42 int count = 0;
43 sigset_t pending;//定義信號集pending
44 while(1)
45 {
46 sigpending(&pending);//讀取當前進程的未決信號集,通過pengding傳出
47 showpending(&pending);//打印當前進程的未決信號集
48 sleep(1);
49 if(count++ > 3)
50 {
51 sigprocmask(SIG_SETMASK,&osigset,NULL);//讀取進程的當前信號屏蔽字,然後將osigset的值設置爲當前信號屏蔽字
52 count = 0;
53 }
54 }
55 return 0;
56 }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
程序運行時,每秒鐘把各信號的未決狀態打印一遍,由於我們阻塞了SIGINT信號,按Ctrl-C將會使SIGINT信號處於未決狀態,按Ctrl-\仍然可以終止程序,因爲SIGQUIT信號沒有阻塞。
運行結果: