1. 程式人生 > >Linux下的訊號(signal)

Linux下的訊號(signal)

一、訊號的概念:
要理解訊號,我們先來進入一個場景。使用者在shell下開啟一個前臺程序,正在執行。在鍵盤上按下ctrl+C的組合鍵,當前前臺程序會中斷。是因為鍵盤上輸入的訊號通過硬體傳輸給驅動程式,將ctrl+C轉化為SIGNAL傳給該程序的PCB,修改了PCB裡的某些欄位,也就是說給程序傳送了一個訊號。
輸入指令:kill -l 可以檢視訊號
這裡寫圖片描述
二、訊號產生的幾個主要條件:
1.硬體異常,硬體檢測到異常之後會通知核心(kernel),由核心傳送給程序。例如如果訪問了非法地址,會導致硬體異常。有些人會疑惑為什麼非法地址和硬體扯上關係,這裡要說明。系統內所說記憶體的地址都是虛擬地址,需要通過頁表和MMU通過對映關係找到實體記憶體(硬碟)。訪問地址出錯,MMU就會報錯,輸入硬體錯誤。核心講這個錯誤解釋為SIGSEGV。
2.通過鍵盤輸入某些指令,產生訊號。比如Ctrl+C 、Ctrl+Z等等。
3.一個程序呼叫kill 函式,就可以傳送該訊號給程序。也可以在命令列上輸入kill 加要傳送的訊號加上程序號來發送訊號。其實kill命令實質就是呼叫kill函式。
三、接收到訊號處理動作:(有以下三種)
1.忽略此訊號。
2.執行該訊號的預設動作(大多數情況下是關閉該程序)。
3.提供一個訊號處理函式,要求核心在處理該訊號時切換到使用者態執行該函式,這種方式成為catch一個訊號。
❤通過終端按鍵產生:
上面說到接收到訊號預設處理動作是終止程序並且Core Dump (核心轉儲)。core dump是當一個程序被終止時,用來儲存程序的使用者空間上所有的資料儲存到磁碟上。檔名通常上core 。事後偵錯程式會排查錯誤資訊。這個過程叫做Post-mortem Debug(事後除錯)。但系統是預設不嘗試core這個檔案的。需要ulimit命令來解除限制。允許產生。
這裡寫圖片描述


就可以產生core檔案。
❤通過系統函式向程序發訊號:
在命令列傳送kill 指令可以發生訊號,比如說建立一個死迴圈 讓其在後臺執行(只需要在執行時後面加& 就可,例如執行mysig:後臺:./mysig &)。傳送kill -9 (pid)。
這裡寫圖片描述
傳送kill -9 (pid),殺死程序。
這裡寫圖片描述
或者可以通過以下函式:

#include<signal.h>
int kill(pid_t pid,int signo);
int rasie(int signo);
//引數1:程序號,引數2:訊號。成功返回0,失敗返回-1。
void abort(void);
//類似於exit()。

四、訊號在核心裡的表示:
上面討論了訊號產生的各情況。而實際處理訊號的動作叫做遞達(Delivery)。而訊號從產生到遞達的過程的狀態叫做未決(Pending)。程序可以選擇阻塞(block)某個訊號。被阻塞的訊號將一直處於未決狀態,直到程序解除對該訊號的阻塞,才能遞達。
注意:這裡的阻塞不是忽略的意思,而是暫時“遮蔽”後面還可以解除阻塞。

這裡寫圖片描述
作業系統提供了三個4位元組的點陣圖,分別是阻塞表,未決表,抵達表來維護訊號的狀態。
五、訊號集操作函式:
siget_t 型別對於每個訊號都有“有效”,“無效”兩個狀態。

#include <signal.h>
int sigemptyset(sigset_t *set
);//初始化set所指向的訊號集,使其中所有訊號的對應bit位清零,表示該訊號集不包含任何有效訊號。 int sigfillset(sigset_t *set);//初始化set所指向的訊號集,使其中所有訊號的對應bit置位1,表示該訊號集的有效訊號包括系統支援的所有訊號 int sigaddset(sigset_t *set, int signo);//為訊號集中新增 某種有效訊號 int sigdelset(sigset_t *set, int signo);//為訊號集中刪除某種有效訊號 int sigismember(const sigset_t *set, int signo);//判斷⼀一個訊號集的有效訊號中是否包含某種訊號,若包含則返回1,不包含則返回0,出錯返回-1

❤呼叫函式sigprocmask可以讀取或者更改程序的訊號遮蔽字(阻塞訊號集)。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);

返回值:若成功則為0,若出錯則為-1。

引數:
oset:如果oset是非空指標,則讀取程序的當前訊號遮蔽字通過oset引數傳出。如果set是非空指標,則更改程序的訊號遮蔽字,引數how指示如何更改。
set:如果oset和set都是非空指標,則先將原來的訊號遮蔽字備份到oset裡,然後根據set和how引數更改訊號遮蔽字。
how:how有三個值可選
SIG_BLOCK /SIG_UNBLOCK 分別是新增和刪除當前訊號遮蔽字。
SIG_SETMASK 設定當前訊號遮蔽字為set指向的值。
如果呼叫sigprocmask解除了對當前若干個未決訊號的阻塞,則在sigprocmask返回前,至少將其中一個訊號遞達。
❤sigpending讀取當前程序的未決訊號集,通過set引數傳出。

#include <signal.h>
int sigpending(sigset_t *set);

返回值:呼叫成功則返回0,出錯則返回-1。