一、訊號在核心中的表示
實際執行訊號的處理動作稱為訊號遞達(Delivery),訊號從產生到遞達之間的狀態,稱為訊號未決(Pending)。進程可以選擇阻塞(Block)某個訊號。被阻塞的訊號產生時將保持在未決狀態,直到進程解除對此訊號的阻塞,才執行遞達的動作。注意,阻塞和忽略是不同的,只要訊號被阻塞就不會遞達,而忽略是在遞達之後可選的一種處理動作。訊號在核心中的表示可以看作是這樣的:
每個訊號都有兩個標誌位分別表示阻塞和未決,還有一個函數指標表示處理動作。訊號產生時,核心在進程式控制制塊中設定該訊號的未決標誌,直到訊號遞達才清除該標誌。在的例子中,
1. SIGHUP訊號未阻塞也未產生過,當它遞達時執行預設處理動作。
2. SIGINT訊號產生過,但正在被阻塞,所以暫時不能遞達。雖然它的處理動作是忽略,但在沒有解除阻塞之前不能忽略這個訊號,因為進程仍有機會改變處理動作之後再解除阻塞。
3. SIGQUIT訊號未產生過,一旦產生SIGQUIT訊號將被阻塞,它的處理動作是使用者自訂函數sighandler。
未決和阻塞標誌可以用相同的資料類型sigset_t來儲存,sigset_t稱為訊號集,這個類型可以表示每個訊號的“有效”或“無效”狀態,,在阻塞訊號集中“有效”和“無效”的含義是該訊號是否被阻塞,而在未決訊號集中“有效”和“無效”的含義是該訊號是否處於未決狀態。阻塞訊號集也叫做當前進程的訊號屏蔽字(Signal
Mask),這裡的“屏蔽”應該理解為阻塞而不是忽略。
二、訊號集處理函數
sigset_t類型(64bit)對於每種訊號用一個bit表示“有效”或“無效”狀態,至於這個類型內部如何儲存這些bit則依賴於系統實現,從使用者的角度是不必關心的,使用者只能調用以下函數來操作sigset_t變數,而不應該對它的內部資料做任何解釋,比如用printf直接列印sigset_t變數是沒有意義的。
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
函數sigemptyset初始化set所指向的訊號集,使其中所有訊號的對應bit清零,表示該訊號集不包含任何有效訊號。函數sigfillset初始化set所指向的訊號集,使其中所有訊號的對應bit置位,表示該訊號集的有效訊號包括系統支援的所有訊號。注意,在使用sigset_t類型的變數之前,一定要調用sigemptyset或sigfillset做初始化,使訊號集處於確定的狀態。初始化sigset_t變數之後就可以在調用sigaddset和sigdelset在該訊號集中添加或刪除某種有效訊號。這四個函數都是成功返回0,出錯返回-1。sigismember是一個布爾函數,用於判斷一個訊號集的有效訊號中是否包含某種訊號,若包含則返回1,不包含則返回0,出錯返回-1。
三、sigprocmask 和 sigpending 函數
1、調用函數sigprocmask可以讀取或更改進程的訊號屏蔽字。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
傳回值:若成功則為0,若出錯則為-1
如果oset是非null 指標,則讀取進程的當前訊號屏蔽字通過oset參數傳出。如果set是非null 指標,則更改進程的訊號屏蔽字,參數how指示如何更改。如果oset和set都是非null 指標,則先將原來的訊號屏蔽字備份到oset裡,然後根據set和how參數更改訊號屏蔽字。假設當前的訊號屏蔽字為mask,下表說明了how參數的可選值。
2、sigpending讀取當前進程的未決訊號集,通過set參數傳出。調用成功則返回0,出錯則返回-1。
#include <signal.h>
int sigpending(sigset_t *set);
樣本程式:
C++ Code
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
|
|
/************************************************************************* > File Name: process_.c > Author: Simba > Mail: dameng34@163.com > Created Time: Sat 23 Feb 2013 02:34:02 PM CST ************************************************************************/ #include<sys/types.h> #include<sys/stat.h> #include<unistd.h> #include<fcntl.h> #include<stdio.h> #include<stdlib.h> #include<errno.h> #include<string.h> #include<signal.h>#define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) void handler(int sig); void printsigset(sigset_t *set) { int i; for (i = 1; i < NSIG; i++) { if (sigismember(set, i)) putchar('1'); else putchar('0'); } printf("\n"); } int flag = 0; int main(int argc, char *argv[]) { if (signal(SIGINT, handler) == SIG_ERR) ERR_EXIT("signal error"); if (signal(SIGQUIT, handler) == SIG_ERR) ERR_EXIT("signal error"); sigset_t pset; // 64bit sigset_t bset; sigemptyset(&bset); sigaddset(&bset, SIGINT); sigprocmask(SIG_BLOCK, &bset, NULL); for (; ;) { sigpending(&pset); printsigset(&pset); sleep(1); if (flag == 1) sigprocmask(SIG_UNBLOCK, &bset, NULL); } return 0; } void handler(int sig) { if (sig == SIGINT) printf("recv a sig=%d\n", sig); else if (sig == SIGQUIT) { printf("rev a sig=%d\n", sig); sigset_t uset; sigemptyset(&uset); sigaddset(&uset, SIGINT); sigprocmask(SIG_UNBLOCK, &uset, NULL); flag = 1; } } |
如果將程式中的37,57,58,75關於flag變數的語句注釋掉,則輸出如下:
simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$ ./sigprocmask
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
...................................................................................
^C0100000000000000000000000000000000000000000000000000000000000000
0100000000000000000000000000000000000000000000000000000000000000
...................................................................................................................................................
^\rev a sig=3
recv a sig=2
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
............................................................................................................................................
^C0100000000000000000000000000000000000000000000000000000000000000
0100000000000000000000000000000000000000000000000000000000000000
...........................................................................................................................................
在程式的一開始將SIGINT訊號添加進阻塞訊號集(即訊號屏蔽字),死迴圈中一直在列印進程的訊號未決集,當我們按下ctrl+c,因為訊號被阻塞,故處於未決狀態,所以輸出的第二位為1(SIGINT是2號訊號),接著當我們按下ctrl+\,即發送SIGQUIT訊號,我們在handler中解除了對SIGINT的阻塞,故2號訊號被遞達,列印兩行recv語句,此時訊號未決集又變成全0。比較讓人疑惑的是我們貌似已經解除了對SIGINT的屏蔽,但當我們再次ctrl+c
時,訊號還是處於未決狀態。後來我寫了個測試程式,發現解除阻塞時只是將未決標誌pending位清0,而block位一直為1,但還是覺得很不解,難道一個進程運行期間只要阻塞了一個訊號,只能每次靠清除pending位讓其遞達,即治標不治本?後來覺得會不會是因為在handler裡進行解除才會這樣呢?於是設定了一個標誌位flag,即把前面說的4行代碼補上,則前面的輸出是一樣的,但在主函數中再次解除阻塞後,按下ctrl+c,讓人驚喜的是2號訊號順利遞達,如下:
^Crecv a sig=2
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
...........................................................................................................................................
我查遍了sigprocmask 的 man手冊,也沒發現說明這一點,但實際測試是這樣的,即如果在訊號處理函數中對某個訊號進行解除阻塞時,則只是將pending位清0,讓此訊號遞達一次,但不會將block位清0,即再次產生此訊號時還是會被阻塞,處於未決狀態。
現在使用ctrl+c , ctrl+\ 都終止不了程式了,可以另開個終端kill -9 pid 殺死進程。
參考:《APUE》、《linux c 編程一站式學習》