信號傳遞過程
- 信號源產生信號,由內核向進程發送信號
- 進程選擇是否阻塞進程,若阻塞,則信號進入阻塞信號列表,只有當解除阻塞後,進程才接收該信號,若一直不接觸,內核則將該信號從阻塞列表中移除並丟棄;若不阻塞,則進程接收信號
進程接收信號後,進程可屏蔽該信號,或者執行用戶編寫的處理函數,或者執行默認動作
以上便是linux進程對信號的處理過程,如果你足夠細心,你會提出疑問:我如何才能讓進程對一個信號進行處理呢?這也就是我們這篇博客要解決的問題
一些名詞
信號集
信號集
顧名思義,就是信號的集合。在linux中,它的類型是sigset_t,大小是64bits。
此時你應該又有疑問了:爲何是64bits?原因很簡單,因爲目前linux流行版本一共有64個信號(不同版本信號格式不同),我們一個bit來表示一種信號,一共只需要64bits就行啦!!!
那麼信號集的作用到底是什麼呢?信號集一共有64bits,自然,第一位就是1號信號(SIGHUP),第二位就是2號信號(SIGINT)….以此類推(我們假設位號是從1開始,而不是從0開始)。那麼,假如1號位如果是1,則表示1號信號已經註冊到該信號集中;如果是0,就是爲註冊到該信號集中咯!爲什麼要把信號註冊到信號集中呢?當然是爲了批量管理信號!!!具體的用法,看到下面你就知道啦抵達(delivery) && 未決(pending)
抵達:執行信號的處理動作叫抵達。也就是說信號被進程接收啦!抵達包括:忽略
,執行默認動作
,執行處理函數
未決:信號從產生到抵達直接的狀態。說白了未決就是未抵達。自然,一個信號被阻塞啦,那麼該信號就是處於未決狀態。信號屏蔽狀態字(block) && 信號未決狀態字(pending)
在進程控制塊(PCB)中的結構體中,有三個比較重要的變量,分別是:信號屏蔽狀態字
,信號未決狀態字
,是否忽略標誌
信號屏蔽狀態字(block):64bits,每一位代表該進程對對應號碼的信號是否屏蔽:1是屏蔽,0是不屏蔽
信號未決狀態字(pending):64bits,每一位代表該進程對對應號碼的信號的狀態:1是未決,0是不抵達
是否忽略標誌:這裏我們不討論
需要注意的是:這些變量之間也存在一些關係,比如:進程將信號屏蔽字的2號爲置爲1,也就是說屏蔽SIGINT信號,那麼但你向進程發送該信號時(ctrl+c),該信號必然處於未決狀態。那麼,信號未決狀態字的2號位自然也就是1啦。
信號集操作函數
POSIX.1 定義了一個數據類型sigset_t,用於表示信號集。另外,頭文件 signal.h 提供了下列五個處理信號集的函數
sigemptyset 初始化set所指向的信號集,清除裏面所有已經註冊的信號,即將所有位置0
int sigemptyset(sigset_t *set);
返回值:若成功則返回0,若出錯則返回-1sigfillset 初始化由 set 指向的信號集,使其包含所有信號。即將所有位置1
int sigfillset(sigset_t *set);
返回值:若成功則返回0,若出錯則返回-1sigaddset 將一個信號 signo 添加到現有信號集 set 中。即將該信號對應的位置1
int sigaddset(sigset_t *set, int signo);
返回值:若成功則返回0,若出錯則返回-1sigdelset 將一個信號 signo 從信號集 set 中刪除。即將該信號對應的位置0
int sigdelset(sigset_t *set, int signo);
返回值:若成功則返回0,若出錯則返回-1sigismember 判斷指定信號 signo 是否在信號集 set 中。
int sigismember(const sigset_t *set, int signo);
返回值:若真則返回1,若假則返回0,若出錯則返回-1
這些函數的具體運用會在下面的代碼中
sigprocmask && 設置進程的信號屏蔽字
sigprocmask 函數可以檢測或者設置進程的信號屏蔽字。
#include <signal.h>
int sigprocmask(int how, const sigset_t * set, sigset_t * oset);
返回值:若成功則返回0,若出錯則返回-1參數說明
set:如果set是非空指針,則更改進程的信號屏蔽字
oset:如果oset是非空指針,則讀取進程的當前信號屏蔽字通過oset參數傳出
how:參數how指示如何更改
注意:如果set爲空,那how參數顯然也沒有意義啦,信號集都沒有,我哪知道要改哪個信號的屏蔽狀態字!!!how 參數可選值
how | 說明 |
---|---|
SIG_BLOCK | 該進程新的信號屏蔽字是其當前信號屏蔽字和 set 指向信號集的並集。set 包含了我們希望阻塞的信號。 |
SIG_UNBLOCK | 該進程的信號屏蔽字是當前信號屏蔽字和 set 所指向信號集補給的交集。set 包含了我們希望解除阻塞的信號。 |
SIG_SETMASK | 設置當前信號屏蔽字設置爲 set 所指向的信號集。 |
上面的東西是不是不是很懂,那麼我們通俗點講
how | 說明 |
---|---|
SIG_BLOCK | 把set信號集裏面的信號全部設置爲阻塞 |
SIG_UNBLOCK | 把set信號集裏面的信號全部解除阻塞 |
SIG_SETMASK | 把set信號集裏面的信號全部設置爲阻塞或者解除阻塞(當前信號屏蔽字可能是阻塞,,也可能是接觸阻塞) |
- 下面我們來看一個具體的例子
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
void handler()
{
printf("SIG_INT respond\n");
return;
}
int main()
{
char tmp = 'a';
sigset_t bset; //用來設置阻塞的信號集
sigemptyset(&bset); //清空信號集
sigaddset(&bset, SIG_INT); //將SIG_INT信號添加到信號集中
if(signal(SIG_INT, handler) == SiG_ERR)//註冊安裝處理函數
perror("signal err:");
sigprocmask(SIG_BLOCK, &bset, NULL); //阻塞SIG_INT信號
while(tmp != 'q' )
{
tmp = getchar();
}
sigprocmask(SIG_UNBLOCK, &bset, NULL);//解鎖阻塞
pause();
return 0;
}
編譯運行此函數,按一下步驟輸入:
程序運行到getchar(), 輸入ctrl+c,此時並未看見執行信號處理函數
輸入'q',退出循環,再按ctrl+c,看見控制端打印SIG_INT respond
sigpending 獲取進程未決的信號集
- sigpending 獲取當前進程所有未決的信號。通過其 set 參數返回未決的信號集。
#include <signal.h>
int sigpending(sigset_t *set);
返回值:若成功則返回0,若出錯則返回-1
通俗地將,就是:一旦調用sigpending函數,那麼set信號集中,處於未決狀態的信號對應的位,被置爲1
- 看個例子
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
void printsigset(sigset_t *set)
{
int i;
for (i=1; i<NSIG; ++i)
{
if (sigismember(set, i))//置1的位,說明對應信號在信號集 set中。返回真,則打印1。如01000000000000....說明2號信號是處於未決狀態
putchar('1');
else
putchar('0');
}
printf("\n");
}
int main()
{
char tmp = 'a';
sigset_t bset; //用來設置阻塞的信號集
sigset_t pset; //用來打印未決信號集
sigemptyset(&bset); //清空信號集
sigaddset(&bset, SIG_INT); //將SIG_INT信號添加到信號集中
sigprocmask(SIG_BLOCK, &bset, NULL); //阻塞SIG_INT信號
for(;;;)
{
//獲取未決 字信息
sigpending(&pset);//若一信號是未決狀態,則將set對應位置1
//打印信號未決 sigset_t字
printsigset(&pset);
sleep(1);
}
pause();
return 0;
}
程序開始執行:打印000000000000......
輸入ctrl+c,因爲SIG_INT被阻塞,所以該信號處於未決狀態
故輸出01000000000000000......
需要注意的是:如果在信號處理函數中對某個信號進行解除阻塞時,則只是將pending位清0,讓此信號遞達一次(同個實時信號產生多次進行排隊都會抵達),但不會將block位清0,即再次產生此信號時還是會被阻塞,處於未決狀態。