lienhua34
2014-11-03
1 信號傳遞過程
信號源為目標進程產生了一個信號,然后由內核來決定是否要將該信號傳遞給目標進程。從信號產生到傳遞給目標進程的流程圖如圖 1 所示,
圖 1: 信號產生、傳遞到處理的流程圖
進程可以阻塞信號的傳遞。當信號源為目標進程產生了一個信號之后,內核會執行依次執行下面操作,
1. 如果目標進程設置了忽略該信號,則內核直接將該信號丟棄。
2. 如果目標進程沒有阻塞該信號,則內核將該信號傳遞給目標進程,由目標進程執行相對應操作。
3. 如果目標進程設置阻塞該信號,則內核將該信號放到目標進程的阻塞信號列表中,等待目標進程對該類型信號的下一步設置。若目標進程后續設置忽略該信號,則內核將該信號從目標進程的阻塞信號列表中移除並丟棄。若目標進程對該信號解除了阻塞,內核將該信號傳遞給目標進程進行相對應的操作。
在信號產生到信號傳遞給目標進程之間的時間間隔內,我們稱該信號為未決的(pending)。
每個進程都有一個信號屏蔽字(signal mask),它規定了當前要阻塞傳遞給該進程的信號集。對於每種可能的信號,信號屏蔽字中都有一位與之對應。
2 信號集及其操作
POSIX.1 定義了一個數據類型sigset_t,用於表示信號集。另外,頭文件 signal.h 提供了下列五個處理信號集的函數。
函數 sigemptyset 初始化由 set 指向的信號集,清除其中所有信號。
int sigemptyset(sigset_t *set);
返回值:若成功則返回0,若出錯則返回-1
函數 sigfillset 初始化由 set 指向的信號集,使其包含所有信號。
int sigfillset(sigset_t *set);
返回值:若成功則返回0,若出錯則返回-1
函數 sigaddset 將一個信號 signo 添加到現有信號集 set 中。
int sigaddset(sigset_t *set, int signo);
返回值:若成功則返回0,若出錯則返回-1
函數 sigdelset 將一個信號 signo 從信號集 set 中刪除。
int sigdelset(sigset_t *set, int signo);
返回值:若成功則返回0,若出錯則返回-1
函數 sigismember 判斷指定信號 signo 是否在信號集 set 中。
int sigismember(const sigset_t *set, int signo);
返回值:若真則返回1,若假則返回0,若出錯則返回-1
3. sigprocmask 檢 或設置進程的信號屏蔽字
調用 sigprocmask 函數可以檢測或者設置進程的信號屏蔽字。
#include <signal.h>
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
返回值:若成功則返回0,若出錯則返回-1
若 oset 參數是一個非空指針,則進程的當前信號屏蔽字將通過 oset 返回。若 set 參數是一個非空指針,則參數 how 將指示如何修改當前信號屏蔽字。how 的可選值如表 1 所示,
how | 說明 |
SIG_BLOCK | 該進程新的信號屏蔽字是其當前信號屏蔽字和 set 指向信號集的並集。 |
SIG_UNBLOCK | 該進程的信號屏蔽字是當前信號屏蔽字和 set 所指向信號集補給的交集。set 包含了我們希望解除阻塞的信號。 |
SIG_SETMASK | 該進程新的信號屏蔽字設置為 set 所指向的信號集。 |
下面我們來看一個例子。在下面的程序文件中先調用 sigprocmask 設置阻塞信號 SIGALRM,然后調用 alarm(2) 設置一個兩秒鍾的鬧鍾(兩秒鍾之后將向當前進程產生一個 SIGALRM 信號)。在睡眠 4 秒鍾之后(此時應該已經產生了 SIGALRM 信號),調用 sigprocmask 函數解除對信號SIGALRM 的阻塞。

#include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <signal.h> static void sig_alrm(int signo) { printf("received SIGALRM\n"); } int main(void) { sigset_t sigset; sigemptyset(&sigset); sigaddset(&sigset, SIGALRM); if (sigprocmask(SIG_BLOCK, &sigset, NULL) < 0) { printf("sigprocmask error: %s\n", strerror(errno)); exit(-1); } if (signal(SIGALRM, sig_alrm) < 0) { printf("signal error: %s\n", strerror(errno)); exit(-1); } alarm(2); sleep(4); printf("before unblock sigprocmask\n"); if (sigprocmask(SIG_UNBLOCK, &sigset, NULL) < 0) { printf("sigprocmask SIG_UNBLOCK error: %s\n", strerror(errno)); exit(-1); } printf("before exit\n"); exit(0); }
編譯該程序文件 sigprocmaskdemo.c,生成並執行文件 sigprocmaskdemo,
lienhua34:demo$ gcc -o sigprocmaskdemo sigprocmaskdemo.c lienhua34:demo$ ./sigprocmaskdemo before unblock sigprocmask received SIGALRM before exit
從上面的執行輸出,我們看到信號 SIGALRM 是在調用 sigprocmask函 數 執 行 unblock 之 后 才 被 傳 遞 給 當 前 進 程 進 行 處 理 的。 如 果 我 們 將sigprocmaskdemo.c 中的sigprocemask(SIG_BLOCK, &sigset, NULL) 注釋掉,編譯執行,生成如下結果,
lienhua34:demo$ ./sigprocmaskdemo
received SIGALRM
before unblock sigprocmask
before exit
4 sigpending 獲取進程未決的信號集
函數 sigpending 獲取當前進程所有未決的信號。通過其 set 參數返回未決的信號集。
#include <signal.h>
int sigpending(sigset_t *set);
返回值:若成功則返回0,若出錯則返回-1
下面我們來看一個例子,

#include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <signal.h> void alrm_is_pending(const char *str) { sigset_t pendingsigset; printf("%s: ", str); if (sigpending(&pendingsigset) < 0) { printf("sigpending error: %s\n", strerror(errno)); exit(-1); } if (sigismember(&pendingsigset, SIGALRM)) { printf("SIGALRM is pending\n"); } else { printf("SIGALRM is not pending\n"); } } int main(void) { sigset_t sigset; sigemptyset(&sigset); sigaddset(&sigset, SIGALRM); if (sigprocmask(SIG_BLOCK, &sigset, NULL) < 0) { printf("sigprocmask error: %s\n", strerror(errno)); exit(-1); } alrm_is_pending("before alarm"); alarm(2); sleep(4); alrm_is_pending("after alarm"); exit(0); }
編譯該程序 sigpendingdemo.c,生成並執行文件 sigpendingdemo。從下面的運行結果,我們看到調用 alarm 函數產生信號 SIGALRM 之后,該信號在 sigpending 函數的 set 參數指向的信號集中。
lienhua34:demo$ gcc -o sigpendingdemo sigpendingdemo.c lienhua34:demo$ ./sigpendingdemo before alarm: SIGALRM is not pending after alarm: SIGALRM is pending
(done)