【Linux】信號的基本概念、信號的產生、記錄與處理

一、信號的基本概念

信號在我們生活中隨處可見,上課鈴聲、喇叭、紅綠燈、警報、鬧鐘、電話鈴聲.....等等。我們知道即使信號沒有產生,我們也知道該如何處理它,比如,紅燈我們就該停,電話響就該接....。那是因爲在第一次遇到他時,我們就記住了它的特徵及其處理方法,所以我麼就知道,如果產生這種信號該怎麼辦。那麼總結如下:

生活中:① 信號的產生的隨機的;②  信號沒有產生時,我們也知道它的作用;③ 我們曾經把信號的特徵及處理方法記錄下來了,已變成了條件反射。

計算機中也是信號的。信號是進程之間事件異步通知的一種方式,屬於軟中斷。

在計算機中①相對於進程而言,信號的產生是異步的;② 即使還未產生信號,也知道如何處理;③ 進程知道如何處理一個信號是因爲提前記錄了什麼信號、如何處理。

那麼我們需要知道進程是怎麼記錄信號、在哪裏記錄信號呢?

首先先來看下信號列表:在Linux中,kill -l可以查看信號列表,在信號列表中,1-34號爲普通信號(其中,不存在0、32、33號信號),34以上爲實時信號。這裏我們只討論1-34號普通信號。

每個信號都有一個編號和一個宏定義名稱,這些宏定義可以在signal.h中找到,例如其中有定義 #define SIGINT 2

二、信號的產生

信號的產生必須直接或者間接通過操作系統

其實準確的說不是發信號,而是操作系統向進程的PCB寫信號

信號的產生一共有四種途徑:

1.通過終端按鍵產生信號

如果我們不下心寫了一個死循環,當我們要終止這個死循環就會按Ctrl+c,那麼Ctrl+c其實就是向目標進程或是前臺進程發送一個2號SIGINT信號,鍵盤產生了信號,但是其實是操作系統把這個信號寫到進程的PCB的,操作系統獲取到鍵盤上的信號,把它解析成2號信號,然後發給進程。進程收到2號信號之後默認就是當前進程退出。所以你就可以結束掉你寫的這個死循環。

SIGINT的默認處理動作是終止進程SIGQUIT的默認處理動作是終止進程並且Core Dump,Core Dump 是什麼以及它的驗證可以看這篇文章--->【Linux】什麼是Core Dump值

2.系統調用(命令或者函數)

①命令kill

我們在後臺跑起來一個死循環進程 a.out ,再打開一個終端,使用命令查看該進程,我們發現該進程的狀態爲R狀態,此時我們使用kill+進程id命令,發現該進程立馬退出。這樣就結束掉了一個死循環。

其實我們可以看看,kill實際上也是發了一個SIGSEGV信號才結束掉了這個死循環。

17261是test進程的id。之所以要再次回車才顯示 Segmentation fault ,是因爲在4568進程終止掉之前已經回到了Shell提示符等待用戶輸入下一條命令,Shell不希望Segmentation fault信息和用戶的輸入交錯在一起,所以等用戶輸入命令之後才顯示。

指定發送某種信號的kill命令可以有多種寫法,上面的命令還可以寫成 kill -SIGSEGV 17261 kill -11 17261,11是信號SIGSEGV的編號。以往遇 到的段錯誤都是由非法內存訪問產生的,而這個程序本身沒錯,給它發SIGSEGV也能產生段錯誤。
 

②系統調用函數

如果不想用命令結束掉死循環,還可以用系統調用函數來完成。kill命令就是調用kill函數實現的。kill函數可以給一個指定的進程發送指定的信號。還是剛剛的例子:

#include <signal.h>
#include <sys/types.h>

int kill(pid_t pid,int sig);

函數寫好後,我們來看結果:

再打開一個終端,查看死循環進程的進程ID,運行kill程序,將該進程的進程ID以及要發送的信號以命令行參數進行傳入kill系統調用函數,最後使用kill系統調用函數將該死循環進程終止。

3.由軟件條件產生信號

(1)比如在某個進程裏創建了一個管道,操作系統識別到這個事件,然後讀端被關閉了,這叫做軟件條件,操作系統檢測到之後認爲沒有必要在進行寫了,所以操作系統就會自發的向該進程發送13號信號:SIGPIPE 來關閉寫端,從而終止該進程。

也就是說:操作系統識別了一個事件,雖然沒有發生錯誤,但是發現這個事件不具備某些條件了,所以操作系統就會自發的向進程發送信號。大部分信號都會導致進程退出。

(2)來看一下14號信號:SIGALRM,這個信號叫做超時信號或者鬧鐘信號。如果我們在某個進程中用alarm函數設定了一個鬧鐘,那麼鬧鐘一旦超時之後,操作系統就會給該進程發送SIGALRM。

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

調用alarm函數可以設定一個鬧鐘,也就是告訴內核在seconds秒之後給當前進程發SIGALRM信號, 該信號的默認處理動作是終止當前進程。

來驗證一下:一秒之後鬧鐘響,操作系統給進程發送了一個SIGALARM信號,進程終止。

4.異常(硬件、軟件引發的)

進程出現異常,觸發軟硬件的異常機制,操作系統主動發送異常信號給進程。

例如:我們此時在代碼裏寫了一個野指針,那麼這個指針會指向任意位置,這是不被允許的,那麼在進行虛擬內存地址空間映射到物理內存的這個過程中,MMU就會對頁表進行檢查,此時MMU就發現了這是不對的,所以MMU就會告訴操作系統,然後操作系統就會給該進程發送11號SIGSEGV信號(segmentation fault:段錯誤),然後這個進程就會在合適的時間對信號進行處理。

再舉個栗子:我們此時寫了一個除法運算的程序,但是在該程序的代碼中,我們出現了除0操作。那麼在編譯運算期間,CPU就會檢測到你的除0非法操作,所以此時CPU就會告訴操作系統,然後操作系統就會給該進程發送8號SIGFPE信號(floating point exception:浮點數異常),然後這個進程就會在合適的時間對信號進行處理。

注意:

  1. Ctrl-C 產生的信號只能發給前臺進程。一個命令後面加個&可以放到後臺運行,這樣Shell不必等待進程結束就可以接受新的命令,啓動新的進程。
  2. Shell可以同時運行一個前臺進程和任意多個後臺進程,只有前臺進程才能接到像 Ctrl-C 這種控制鍵產生的信號。
  3. 前臺進程在運行過程中用戶隨時可能按下 Ctrl-C 而產生一個信號,也就是說該進程的用戶空間代碼執行到任何地方都有可能收到 SIGINT 信號而終止,所以信號相對於進程的控制流程來說是異步(Asynchronous)的

三、信號的記錄

進程要對操作系統發出的信號進行處理,從而做出反應,肯定要先記錄下來,那麼要記錄,就要看看進程是怎麼知道一個信號產生了呢?

  • 實際執行信號的處理動作稱爲信號遞達(Delivery)
  • 信號從產生到遞達之間的狀態,稱爲信號未決(Pending)。
  • 進程可以選擇阻塞 (Block )某個信號
  • 被阻塞的信號產生時將保持在未決狀態,直到進程解除對此信號的阻塞,才執行遞達的動作。
  • 注意:阻塞和忽略是不同的,只要信號被阻塞就不會遞達,而忽略是在遞達之後可選的一種處理動作
     

描述進程用的是PCB,信號要麼產生了要麼沒產生,不會有第三態,所以最好的記錄信號的方法就是位圖(0-31對應的比特位代表信號的編號,比特位的內容代表是或否收到信號),所以在進程PCB裏會用位圖來記錄信號的產生,當進程發現PCB裏的位圖對應的比特位由0變爲1時,就知道產生了對應的信號,然後會在合適的時候進行信號的處理。信號是由操作系統直接或間接產生的,發信號的本質是修改PCB中位圖對應的比特位(把對應信號的比特位從0變爲1),修改PCB只能通過操作系統修改,因此信號是由操作系統直接或間接產生的。

那麼在PCB中描述阻塞信號和未決信號都用的是位圖,分別是block表和pending表,31個信號對應的信號操作都是函數,因此在PCB中還有一個函數指針數組來存放信號處理動作的函數的地址,我們稱其爲handler表。

  • 每個信號都有兩個標誌位分別表示阻塞(block)和未決(pending),還有一個函數指針表示處理動作。信號產生時,內核在進程控制塊中設置該信號的未決標誌,直到信號遞達才清除該標誌。在上圖的例子中,SIGHUP信號未阻塞也未產生過,當它遞達時執行默認處理動作。
  • SIGINT信號產生過,但正在被阻塞,所以暫時不能遞達。雖然它的處理動作是忽略,但在沒有解除阻塞之前不能忽略這個信號,因爲進程仍有機會改變處理動作之後再解除阻塞。
  • SIGQUIT信號未產生過,一旦產生SIGQUIT信號將被阻塞,它的處理動作是用戶自定義函數sighandler。如果在進程解除對某信號的阻塞之前這種信號產生過多次,將如何處理?Linux是這樣實現的:常規信號在遞達之前產生多次只記一次,而實時信號在遞達之前產生多次可以依次放在一個隊列裏。

四、信號的處理

現在我們知道了信號的產生有四種方式,也知道了信號在處理之前會被記錄在位圖裏,那麼接下來就該處理了。信號產生後不是立即被處理的,而是在合適的時候,那麼什麼是合適的時候呢?主要是由操作系統決定的。那麼來看一下信號的幾種處理方式。可歸納爲三額動作。

信號處理的三種動作:

  • ①默認動作:執行該信號的默認動作
  • ②忽略動作:忽略此信號
  • ③自定義動作:提供一個信號處理函數,要求內核在處理信號時切換到用戶態執行這個函數,這種方式也叫信號捕捉

這裏的前兩個動作很好理解,通過位圖就可以知道它的處理,最後一個自定義纔是最麻煩的。下面分兩篇文章來說。

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