進程信號詳解
1.引入信號的概念
信號是軟件中斷。它給我們提供了一種能夠異步處理事件的方法。事實上,進程並不知道信號何時到來。
比如,當我們的某一個進程失去控制,而我們想讓他終止運行時,通常使用Ctrl+c的方式使進程強制終止,Ctrl+c雖然由硬件產生,但是它在操作系統內核中會被視作SIGINT信號(signal interrupt 中斷信號)。
信號也與版本有關,不同的操作系統所支持的信號種類和數量有差異(如下圖),本篇博客僅講解linux操作系統中的信號。
2.信號的生命週期
linux下進程一般經過4個步驟:
信號的產生–>信號的註冊–>信號的註銷–>信號的處理,下面我們分步講解信號的各個階段。
3.信號的產生
在linux操作系統中,有很多條件能夠產生信號:
-
通過硬件中斷產生信號:
比如在中斷使用Ctrl+c,Delete等按鍵可以產生硬件中斷。
-
程序執行出現異常會產生信號
在程序中,某些錯誤會讓操作系統內核該程序對應的進程產生相應的信號。 比如,當程序中有對無效內存做引用時,操作系統內核會產生併發送SIFGSEGV信號給該進程
-
使用者用kill(1)在命令行,向某個進程發送指定的信號,(或者在某個進程調用kill(2)函數接口亦可)
在另一個終端給某個進程發送 kill -n pid 即可給pid所對應的進程發送n號信號。 在某個進程中使用int kill(pid_t pid,int sig)向pid進程發送sig號信號。
-
當進程滿足某些軟件條件時,也會產生信號
比如當子進程退出後,會給父進程發送SIGCHLD信號,提示父進程接收他的退出信息,方便釋放子進程資源。
4.信號的註冊
4.0遞達/未決/未決信號集/未決信號鏈
在講解信號註冊之前,要先理解兩個概念:
遞達:執行信號所代表的系統調用函數叫做遞達。
未決:從信號產生到遞達之前的之一段時間叫信號未決。
下面我們來講講信的註冊。
首先,要明白信號是如何存儲的。
在進程PCB中有3張位圖block,pending,handler,他們分別標誌這阻塞信號,未決信號,以及當前信號的處理方式。
struct sigpending pending://未決信號的數據成員
struct sigpending{
struct sigqueue *head, **tail;//信號集的頭尾指針
sigset_t signal; //未決信號集,每一位都標誌着一個信號。
};
struct sigqueue{ //未決信號鏈
struct sigqueue *next;
siginfo_t info;
}
信號在進程中註冊指的就是:
信號值加入到進程的未決信號集sigset_t signal(每個信號佔用一位)中,
並且信號所攜帶的信息被保留到未決信號信息鏈的某個sigqueue結構中。
只要信號在進程的未決信號集中,表明進程已經知道這些信號的存在,但還沒來得及處理,或者該信號被進程阻塞。
4.1可靠信號與不可靠信號
linux下進程信號有62種,分爲兩類,其中1-31號爲不可靠信號,34-64爲可靠信號。
不可靠信號又稱非實時信號,當一個非實時信號要插入sigqueue未決信號鏈之前,操作系統會先查看pending位圖該信號的位置是否已經被置爲1,如果是,則直接丟棄該信號。
可靠信號又稱實時信號,實時信號不管pending位圖中該信號位置是0還是1,都會插入sigqueue中。
所以,我們可以這樣想,sigqueue中最多只能有一個不可靠信號的節點信息,但可能有多個可靠信號的信息。
4.2 信號集及操作函數
信號集被定義爲一種數據類型:
typedef struct
{
unsigned long sig[_NSIG_WORDS];
} sigset_t;
信號集用來描述信號的集合,每個信號佔用一位。
Linux所支持的所有信號可以全部或部分的出現在信號集中,主要與信號阻塞相關函數配合使用。
下面是爲信號集操作定義的相關函數:
#include <signal.h>
int sigemptyset(sigset_t *set)
//初始化由set指定的信號集,信號集裏面的所有信號被清空;(每創建一個信號集一定要記得調用)
int sigfillset(sigset_t *set)
//調用該函數後,set指向的信號集中將包含linux支持的64種信號,即填充信號集;
int sigaddset(sigset_t *set, int signum);
//在set指向的信號集中加入signum信號;
int sigdelset(sigset_t *set, int signum)
//在set指向的信號集中刪除signum信號;
int sigismember(const sigset_t *set, int signum)
//判定信號signum是否在set指向的信號集中。
int sigpending(sigset_t *set);
// 將當前pending集合(信號註冊集合)中的信號取出來放到set中
對阻塞信號集操作主要用到的是sigprocmask函數
int sigprocmask(int how,sigset_t *set,sigset_t oldset);
how:
SIG_BLOCK 在進程當前阻塞信號集中添加set指向信號集中的信號
SIG_UNBLOCK 如果進程阻塞信號集中包含set指向信號集中的信號,則解除對該信號的阻塞
SIG_SETMASK 更新進程阻塞信號集爲set指向的信號集
set:與阻塞信號集進行交互的信號集
oldset:保存當前阻塞信號集,方便回退。
說了這麼多,我們來實際操作一下:
1 #include<iostream>
2 #include<unistd.h>
3 #include<signal.h>
4 using namespace std;
5
6 void sig_print(sigset_t *set)
7 {
8 int i=0;
9 for (;i<32;i++)//打印set
10 {
11 if (sigismember(set,i))
12 {
13 cout<<"1";
14 }
15 else
16 {
17 cout<<"0";
18 }
19
20 }
21 cout<<endl;
22
23 }
24
25 int main()
26 {
27 sigset_t s,p;//創建信號集s,p
28
29 sigemptyset(&s);//將信號集的內容置空
30 sigaddset(&s,SIGINT);//將SIGINT信號添加進信號集s中
31 sigprocmask(SIG_BLOCK,&s,NULL);
//將信號集s中的信號添加進當前進程的block位圖。這裏也就是將SIGINT阻塞了。
32
33
34 while(1)
35 {
36 sigpending(&p);//取出當前進程的pending位圖放進p中
37 sig_print(&p);
38 sleep(1);
39 }
40
41
42
43 return 0;
44 }
這段程序就是將SIGINT信號阻塞掉,然後持續輸出pending位圖,那麼我們來運行一下試試:
我們發現,就算使用Ctrl+c也無法結束這個進程,原因就是Ctrl+c這個硬件中斷在操作系統看來就是一個SIGINT信號,發送給該進程後,由於block中阻塞了該信號,所以進程遲遲不能結束。
但是我們還是能夠用Ctrl+\結束掉進程的。
5.信號的註銷
如果存在未決信號等待處理且該信號沒有被進程阻塞,則在運行相應的信號處理函數前,進程會把信號在未決信號鏈中佔有的結構卸掉。
對於不可靠信號(非實時信號)來說,由於在未決信號信息鏈中最多隻佔用一個sigqueue結構,因此該結構被釋放後,應該把信號在進程未決信號集中刪除(信號註銷完畢);
而對於可靠信號(實時信號)來說,可能在未決信號信息鏈中佔用多個sigqueue結構,因此應該針對佔用sigqueue結構的數目區別對待:如果只佔用一個sigqueue結構(進程只收到該信號一次),則執行完相應的處理函數後應該把信號在進程的未決信號集中刪除(信號註銷完畢)。否則待該信號的所有sigqueue處理完畢後再在進程的未決信號集中刪除該信號。
6.信號的處理
信號的處理分成3種:
-
默認處理方式(1.生成core文件然後結束進程 2.繼續執行 3.直接結束進程 4.暫停此進程 5.想父進程發送我已經執行結束,等待回收資源.)
-
忽略處理方式(如果存在未決信號等待處理且該信號沒有被進程阻塞,則在運行相應的信號處理函數前,進程會把信號在未決信號鏈中佔有的結構卸掉。)
-
自定義處理方式,讓信號按照你自定義的函數執行。
我們主要討論的是第三種,自定義的處理方式。
6.1信號的捕捉
在進程處理信號之前,進程要先捕捉到這個信號,那麼,進程是怎麼捕捉到信號的呢?是信號剛註冊完畢就立即捕捉並處理嗎?如果信號阻塞又怎樣呢?這些都是我們要考慮的問題。
下面我們用一個例子來講講信號從產生到處理的全過程。
舉例:
-
用戶註冊了SIGQUIT信號的處理函數sighandler.。
-
SIGINT註冊進進程的pending位圖中,sighandler註冊進handler位圖中。
-
當前正在執行main函數,這時發生中斷或異常切換到內核態.
-
在中斷處理完畢後要返回用戶態的main函數之前檢查到有信號SIGQUIT遞達.
-
進程識別到SIGINT信號的處理方式是用戶自定義函數且信號無阻塞。
-
內核決定返回用戶態後不是恢復main函數的上下文繼續執行,而是執行sighandler函數. sighandler和main函數使用不同的堆棧空間,他們之間不存在調用和被調用的關係,是兩個獨立的控制流程.
-
sighandler函數返回後自動執行特殊的系統調用sigreturn再次進入內核態
-
如果沒有新的信號抵達,這次再返回用戶態就是恢復main函數的上下文繼續執行了
6.2 信號處理函數
在這裏插入代碼片
6.3 cure-dump
後續補充