linux--進程信號詳解

1.引入信號的概念

信號是軟件中斷。它給我們提供了一種能夠異步處理事件的方法。事實上,進程並不知道信號何時到來。

比如,當我們的某一個進程失去控制,而我們想讓他終止運行時,通常使用Ctrl+c的方式使進程強制終止,Ctrl+c雖然由硬件產生,但是它在操作系統內核中會被視作SIGINT信號(signal interrupt 中斷信號)。

信號也與版本有關,不同的操作系統所支持的信號種類和數量有差異(如下圖),本篇博客僅講解linux操作系統中的信號。
在這裏插入圖片描述

2.信號的生命週期

linux下進程一般經過4個步驟:
信號的產生–>信號的註冊–>信號的註銷–>信號的處理,下面我們分步講解信號的各個階段。

3.信號的產生

在linux操作系統中,有很多條件能夠產生信號:

  1. 通過硬件中斷產生信號:

     比如在中斷使用Ctrl+c,Delete等按鍵可以產生硬件中斷。
    
  2. 程序執行出現異常會產生信號

     在程序中,某些錯誤會讓操作系統內核該程序對應的進程產生相應的信號。
     比如,當程序中有對無效內存做引用時,操作系統內核會產生併發送SIFGSEGV信號給該進程
    
  3. 使用者用kill(1)在命令行,向某個進程發送指定的信號,(或者在某個進程調用kill(2)函數接口亦可)

     在另一個終端給某個進程發送 kill -n  pid 即可給pid所對應的進程發送n號信號。
     
     在某個進程中使用int kill(pid_t pid,int sig)向pid進程發送sig號信號。
    
  4. 當進程滿足某些軟件條件時,也會產生信號

     比如當子進程退出後,會給父進程發送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. 默認處理方式(1.生成core文件然後結束進程 2.繼續執行 3.直接結束進程 4.暫停此進程 5.想父進程發送我已經執行結束,等待回收資源.)

  2. 忽略處理方式(如果存在未決信號等待處理且該信號沒有被進程阻塞,則在運行相應的信號處理函數前,進程會把信號在未決信號鏈中佔有的結構卸掉。)

  3. 自定義處理方式,讓信號按照你自定義的函數執行。

我們主要討論的是第三種,自定義的處理方式。

6.1信號的捕捉

在進程處理信號之前,進程要先捕捉到這個信號,那麼,進程是怎麼捕捉到信號的呢?是信號剛註冊完畢就立即捕捉並處理嗎?如果信號阻塞又怎樣呢?這些都是我們要考慮的問題。

下面我們用一個例子來講講信號從產生到處理的全過程。
在這裏插入圖片描述
舉例:

  1. 用戶註冊了SIGQUIT信號的處理函數sighandler.。

  2. SIGINT註冊進進程的pending位圖中,sighandler註冊進handler位圖中。

  3. 當前正在執行main函數,這時發生中斷或異常切換到內核態.

  4. 在中斷處理完畢後要返回用戶態的main函數之前檢查到有信號SIGQUIT遞達.

  5. 進程識別到SIGINT信號的處理方式是用戶自定義函數且信號無阻塞。

  6. 內核決定返回用戶態後不是恢復main函數的上下文繼續執行,而是執行sighandler函數. sighandler和main函數使用不同的堆棧空間,他們之間不存在調用和被調用的關係,是兩個獨立的控制流程.

  7. sighandler函數返回後自動執行特殊的系統調用sigreturn再次進入內核態

  8. 如果沒有新的信號抵達,這次再返回用戶態就是恢復main函數的上下文繼續執行了

6.2 信號處理函數

在這裏插入代碼片

6.3 cure-dump

後續補充

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章