目錄
一、信號在內核中的表示
?二、sigset_t
2.1sigset_t的概念和意義
2.2信號集操作數
三、信號集操作數的使用
3.1sigprocmask
3.2sigpending
3.3sigemptyset
四、代碼演示
一、信號在內核中的表示
實際執行信號的處理動作稱為信號 遞達(Delivery) 。
信號從產生到遞達之間的狀態,稱為信號 未決(Pending) 。
進程可以選擇 阻塞 (Block ) 某個信號。
被阻塞的信號產生時將保持在未決狀態,直到進程解除對此信號的阻塞,才執行遞達的動作。
注意,阻塞和忽略是不同的,只要信號被阻塞就不會遞達,而忽略是在遞達之后可選的一種處理動作。
信號在內核中的表示示意圖

每個信號都有兩個標志位分別表示阻塞(block)和未決(pending),還有一個函數指針表示處理動作。信號產生時,內核在進程控制塊中設置該信號的未決標志,直到信號遞達才清除該標志。在上圖的例子中
如上圖所示:
SIGHUP信號未阻塞也未產生過,當它遞達時執行默認處理動作。
SIGINT信號產生過,但正在被阻塞,所以暫時不能遞達。雖然它的處理動作是忽略,但在沒有解除阻塞之前不能忽略這個信號,因為進程仍有機會改變處理動作之后再解除阻塞。
SIGQUIT信號未產生過,一旦產生SIGQUIT信號將被阻塞,它的處理動作是用戶自定義函數sighandler。
如果在進程解除對某信號的阻塞之前這種信號產生過多次,將如何處理?POSIX.1允許系統遞送該信號一次或多次。Linux是這樣實現的:常規信號在遞達之前產生多次只計一次,而實時信號在遞達之前產生多次可以依次放在一個隊列里。
?二、sigset_t
2.1sigset_t的概念和意義
在Linux中,常用的信號有31個,內核中則存在一個類似于位圖的方式來對該進程的block,peding進行表示,由于不存在0號信號,所以信號就從1號位置開始到31,如果該位置上為1則表示該信號當前存在,為0則表示不存在。
從上圖來看,每個信號只有一個bit的未決標志,非0即1,不記錄該信號產生了多少次,阻塞標志也是這樣表示的。
因此,未決和阻塞標志可以用相同的數據類型sigset_t來存儲,sigset_t稱為信號集,這個類型可以表示每個信號的“有效”或“無效”狀態,在阻塞信號集中“有效”和“無效”的含義是該信號是否被阻塞,而在未決信號集中“有效”和“無效”的含義是該信號是否處于未決狀態。下一節將詳細介紹信號集的各種操作。 阻塞信號集也叫做當前進程的信號屏蔽字(Signal Mask),這里的“屏蔽”應該理解為阻塞而不是忽略。
而為了方便統一管理,和安全性考慮,Linux中就設置了一種sigset_t的類型專門用于表示信號集。
2.2信號集操作數
sigset_t類型對于每種信號用一個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在該信號集中添加或刪除某種有效信號。
三、信號集操作數的使用
3.1sigprocmask
調用函數 sigprocmask 可以讀取或更改進程的信號屏蔽字 ( 阻塞信號集)。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功則為0,若出錯則為-1
如果oset是非空指針,則讀取進程的當前信號屏蔽字通過oset參數傳出。如果set是非空指針,則 更改進程的信號屏蔽字,參數how指示如何更改。如果oset和set都是非空指針,則先將原來的信號 屏蔽字備份到oset里,然后根據set和how參數更改信號屏蔽字。假設當前的信號屏蔽字為mask,下表說明了how參數的可選值。

如果調用sigprocmask解除了對當前若干個未決信號的阻塞,則在sigprocmask返回前,至少將其中一個信號遞達。
3.2sigpending
#include <signal.h>sigset_t pendig;
int n=sigpending(&pending);
讀取當前進程的未決信號集,通過set參數傳出。調用成功則返回0則n=0,出錯則返回-1則n=-1。
3.3sigemptyset
清空sigset_t類型內部的數據。
sigset_t pending;
sigemptyset(&pending);
考慮到各個平臺的不同,這種方式可以很好解決在棧上生成隨機值的情況
四、代碼演示
#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <cassert>
#include <sys/wait.h>void PrintSig(sigset_t &pending)
{std::cout << "Pending bitmap: ";for (int signo = 31; signo > 0; signo--){if (sigismember(&pending, signo))//判斷該信號是否在信號集中{std::cout << "1";}else{std::cout << "0";}}std::cout << std::endl;
}void handler(int signo)
{sigset_t pending;sigemptyset(&pending);int n = sigpending(&pending); // 正在處理2號信號assert(n == 0);// 3. 打印pending位圖中的收到的信號std::cout << "遞達中...: ";PrintSig(pending); // 0: 遞達之前,pending 2號已經被清0. 1: pending 2號被清0一定是遞達之后std::cout << signo << " 號信號被遞達處理..." << std::endl;
}int main()
{// 對2號信號進行自定義捕捉 --- 不讓進程因為2號信號而終止signal(2, handler);// 1. 屏蔽2號信號sigset_t block, oblock;sigemptyset(&block);sigemptyset(&oblock);sigaddset(&block, 2); // SIGINT --- 根本就沒有設置進當前進程的PCB block位圖中// 0. for test: 如果我屏蔽了所有信號呢???// for(int signo = 1; signo <= 31; signo++) // 9, 19號信號無法被屏蔽, 18號信號會被做特殊處理// sigaddset(&block, signo); // SIGINT --- 根本就沒有設置進當前進程的PCB block位圖中// 1.1 開始屏蔽2號信號,其實就是設置進入內核中int n = sigprocmask(SIG_SETMASK, &block, &oblock);assert(n == 0);// (void)n; // 騙過編譯器,不要告警,因為我們后面用了n,不光光是定義std::cout << "block 2 signal success" << std::endl;std::cout << "pid: " << getpid() << std::endl;int cnt = 0;while (true){// 2. 獲取進程的pending位圖sigset_t pending;sigemptyset(&pending);n = sigpending(&pending);assert(n == 0);// 3. 打印pending位圖中的收到的信號PrintSig(pending);cnt++;// 4. 解除對2號信號的屏蔽if (cnt == 20){std::cout << "解除對2號信號的屏蔽" << std::endl;n = sigprocmask(SIG_UNBLOCK, &block, &oblock); // 2號信號會被立即遞達, 默認處理是終止進程assert(n == 0);}// 我還想看到pending 2號信號 1->0 : 遞達二號信號!sleep(1);}return 0;
}
通過以上代碼所演示的現象我們也可以驗證兩個結論:
1、遞達信號的時候一定會把對應的pending位圖清0。
2、先清0,再遞達。