前言
進程在收到信號之后,可以立即處理,也可以在合適的時間再處理(1-31
號普通信號可以不被立即處理)
信號不是被立即處理,信號就要被保存下來,讓進程在合適的時間再去處理。
相關概念
在了解進程是如何保存信號之前,先了解一下一些概念:
- 信號遞達:進程實際執行信號的處理動作
- 信號未決:信號從產生到遞達的狀態
- 阻塞:進程可以阻塞某一個信號;(被阻塞的信號不會被遞達,直到解除對信號的阻塞,該信號才會被遞達)
- 忽略:是進程對于已遞達的信號的一種處理方式。
信號保存
進程要將信號保存下來,在合適的時候處理;保存信號不僅要保存是否存在信號,也要保存進程是否阻塞信號以及進程對于信號的處理方式。
在Linux
內核中,對于信號存在三張表分別是:block
、pending
、handler
;它們分別存儲了進程對于信號的阻塞信號,收到信號以及進程對于信號的處理方式。
每一個信號都有阻塞、未決兩個標識位以及處理動作,這分別和block
、pending
和handler
表一一對應。
block
表,表示進程對于信號的阻塞情況;pending
表,表示處于未決的信號情況;handler
表,函數指針表,存儲著進程對于信號的處理方式。
對于block
和pending
表,可以簡單理解為位圖,對于1-31
號信號,每一個信號對應一個bit
位;
block
中bit
位為1
表示進程阻塞該信號,pending
中bit
位為1
表示該信號處于未決狀態(進程收到該信號)。
對于handler
表,其存儲的是進程對于信號的處理方式。
了解了信號的保存方式
block
、pending
、handler
表;那發送信號的本質就是將進程的
task_struct
中的pending
表對應的bit
位置1
(修改內核數據結構對象);進程處理信號本質就是檢查進程
task_struct
中block
表是否阻塞信號、pending
表是否存在信號,然后進行調用handler
表中進程對于信號的處理方法。
信號集sigset_t
信號保存的block
和pending
表,都是以位圖的方式來保存信號阻塞和未決的狀態;0
或1
。
對于阻塞和未決標識都可以使用sigset_t
(信號集)這一數據類型來存儲。
信號集
sigset_t
,可以表示信號有效和無效的狀態。在阻塞信號集中,有效和無效表示信號是否被阻塞。
在未決信號集中,有效和無效表示信號是否處于未決狀態。
信號集操作函數
對于sigset_t
信號集,我們不清楚里面有什么;不能直接對信號集進程操作,需要用到特定的函數接口。
相關信號集操作函數,例如初始化
sigemptyset
(設置為0
)、sigfillset
(設置為1
)、sigaddset
(新增信號)、sigdelset
(刪除信號)等等。
初始化信號集
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
對于初始化信號集,分為兩種:一是所有bit
位設置為0
、二是所有bit
位設置為1
。
sigemptyset
,初始化信號集,所有bit
位設置為0
sigfillset
,初始化信號集,所有bit
位設置為1
新增信號
int sigaddset(sigset_t* set, int signum);
在信號集中,新增一個信號signum
;
刪除信號
int sigdelsets(sigset_t* set, int signum);
在信號集set
這,刪除信號signum
;
對于上述的
sigemptyset
、sigfillet
、sigaddset
和sigdelset
,這些函數返回值:如果函數調用成功,返回
0
;函數調用失敗,返回
-1
。
判斷信號是否存在
int sigismember(sigset_t* set, int signum);
sigismember
函數用來判斷信號集sig
中是否存在信號signum
;
如果存在就返回1
;如果不存在就返回0
;如果函數調用失敗就返回-1
。
系統調用
上述信號集操作函數,是修改當前已有信號集sigset_t
;
而在Linux
內核中,存在block
表和pending
表來記錄進程對于信號的屏蔽和未決狀態;那我們能否修改內核中的block
表呢?能否獲取內核中的block
和pending
表呢?
當然是可以的,我們可以通過系統調用sigprocmask
和sigpending
來對內核中進程的阻塞信號集
和未決信號集
進行相關操作。
sigprocmask
調用該函數,可以修改或者讀取信號屏蔽字
int sigprocmask(int how, const sigset_t *_Nullable restrict set, sigset_t *_Nullable restrict oldset);
對于sigprocmask
函數,一共存在三個參數:
第一個參數表示要對內核中的
block
(阻塞信號集)進行什么操作;how
的取值如下圖:
第二個參數
set
指向當前信號集,我們需要自己創建信號集;第三個參數
oldset
是一個輸出型參數,當調用sigprocmask
修改內核阻塞信號集時,會將內核阻塞信號集輸出到oldset
指向的信號集中。
測試:這里簡單調用setprocmask
為進程添加屏蔽2
號信號
#include <stdio.h>
#include <signal.h>
#include <iostream>int main()
{sigset_t set;sigemptyset(&set);//初始化信號集sigaddset(&set, 2);//在信號集中添加2號信號sigset_t oldset;sigprocmask(SIG_BLOCK, &set, &oldset);while (true){}return 0;
}
sigpending
int sigpending(sigset_t *set);
調用sigpending
函數可以獲取進程的pending
表(未決信號集)。
在內核中pending
表是記錄進程2未決信號的狀態的,給進程發信號的本質就是修改內核中進程的pending
表。
在獲取了進程的未決信號集之后,我們可以使用信號集相關操作函數來判斷該信號集中是否存在未決的信號。
這里簡單驗證:阻塞的信號是不會被遞達的;
void print_pending()
{// 獲取pendingsigset_t set;sigpending(&set);//輸出位圖for (int i = 31; i >= 1; i--){if (sigismember(&set, i))std::cout << '1';elsestd::cout << '0';}std::cout << std::endl;
}
void handler(int sig)
{std::cout << "receive sig : " << sig << std::endl;
}int main()
{//自定義捕捉2號信號signal(2, handler);//屏蔽2號信號sigset_t set, oldset;sigemptyset(&set);sigaddset(&set,2);sigprocmask(SIG_BLOCK, &set, &oldset);int cnt = 10;while(cnt--){//輸出pending表print_pending();if(cnt == 0){//解除對2號信號的屏蔽sigemptyset(&set);sigprocmask(SIG_SETMASK, &oldset,nullptr);}sleep(1);}return 0;
}
總結
簡單總結上述內容:
信號保存:
block
信號屏蔽表、pending
信號未決表、handler
信號處理方式(函數指針表)信號集
sigset_t
,相關操作:初始化、新增、刪除、判斷是否存在等等系統調用:
sigprocmask
:獲取或設置內核blcok
表、sigpending
獲取內核pending
表。