在談linux信號處理函數前,有必要先聊下linux信號的機制。包含信號的作用、信號的產生、信號的阻塞等。
信號機制
信號是linux進程通信的一種方式,很多情況下,信號是由一個錯誤產生的,通知進程修改行爲,但是,也由很大一部分場景是由人爲產生信號,通知進程執行某些動作。
信號的產生
信號的產生主要有以下幾種情況:
- 用戶在終端(比如:鍵盤)按下某些按鍵,終端(鍵盤)驅動程序會發送信號給前臺進程。比如:ctrl+c產生SIGINT(2)信號;ctrl+\產生SIGQUIT(3)信號;ctrl+z產生SIGTSTP(20)信號。
- 硬件異常產生信號,硬件檢測到異常就通知內核,然後內核進程向當前用戶進程發送適當的信號。比如:用戶進程進行了除以0的操作,CPU運算單元會產生異常,內核將這個異常解釋爲SIGFPE(8)信號發送給用戶進程。比如:當前用戶進程訪問了非法內存地址,MMU會產生異常,內核將這個異常解釋爲SIGEGV(11)信號發送個用戶進程。
- 用戶調用某些函數(例如kill函數),就可以發送信號到另一個進程。比如:終端下,輸入kill命令發送信號給某個進程(kill命令內部也是調用kill函數實現的),如果kill命令沒有明確指定信號,默認發送的是SIGTERM(15)信號,該信號的默認處理是終止進程。
- 當內核檢測到某種軟件條件發生時,也會通過信號通知用戶進程。比如:鬧鐘超時信號SIGALRM(14),向讀端已經關閉的管道寫數據時會產生SIGPIPE(13)信號。
信號的阻塞
實際執行信號的處理動作稱爲信號抵達,信號從產生到抵達之間的狀態,稱爲信號未決。進程可以選擇阻塞某個信號。 被阻塞的信號產生時將保持在未決狀態,直到進程解除對此信號的阻塞,才執行抵達的動作。阻塞和忽略是不相同的,只要信號被阻塞就不會抵達,而忽略是在抵達之後可選的一種處理動作。
舉個例子:
上圖中,
- SIGHUP(1)信號未產生過(pending=0)也未阻塞(block=0),當它抵達時執行默認處理動作。
- SIGINT(2)信號產生過(pending=1),但正在被阻塞(block=1),所以暫時不能抵達。雖然它的處理動作是忽略,但在沒有解除阻塞之前不能忽略這個信號,因爲進程仍有機會改變處理動作之後再解除阻塞。
- SIGQUIT(3)信號未產生過(pending=0),一旦產生SIGQUIT信號將被阻塞(block=1),它的處理動作是用戶自定義函數sighandler。
linux信號相關數據結構
相關數據結構可以直接到linux上grep -R查找,一般是在<signal.h>頭文件中。
struct sigaction
struct sigaction定義如下,鏈接:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
struct sigset_t
struct sigset_t就是信號集合,看代碼就知道是一個信號數組。定義如下:
# define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
typedef struct
{
unsigned long int __val[_SIGSET_NWORDS];
} __sigset_t;
...
typedef __sigset_t sigset_t;
linux信號相關處理函數
sigemptyset
sigemptyset
初始化set所指向的信號集,使其中所有信號的對應的bit清零,表示該信號集不包含任何有效信號。
sigfillset
sigfillset
初始化set所指向的信號集,使其中所有信號的對應bit置位,表示該信號集的有效信號包括系統支持的所有信號。
sigaddset
sigaddset
在該信號集中添加某種有效信號。
sigdelset
sigdelset
在該信號集中刪除某種有效信號。
sigismember
sigismember
是一個布爾函數,用於判斷一個信號集的有效信號中是否包含某種信號,若包含賊返回1,不包含則返回0,出錯返回-1。
sigprocmask
sigprocmask
讀取或更改進程的信號掩碼,如果成功返回0,失敗返回-1。
信號掩碼是進程當前被阻塞的信號的集合,即阻塞信號集合。
SIG_BLOCK,就是進程當前已有的阻塞信號集和set參數指定的信號集的並集。
SIG_UNBLOCK,就是進程當前已有的阻塞信號集和set參數指定的信號集的差集。
SIG_SETMASK,就是直接用set參數指定的信號集來設置進程的阻塞信號集。
sigpending
sigpending
讀取當前進程的未決信號集,通過set參數傳出,調用成功則返回0,出錯則返回-1。
舉例1
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void printsigset(sigset_t *set)
{
int i;
for(i=1;i<=31;i++){
if(sigismember(set,i))
putchar('Y');
else
putchar('N');
}
puts("");
}
int main()
{
sigset_t s,p; //定義兩個信號集,s和p
sigemptyset(&s); //初始化清空信號集s
sigemptyset(&p); //初始化清空信號集p
sigaddset(&s,SIGINT); //往信號集s中添加信號SIGINT
sigprocmask(SIG_BLOCK,&s,NULL); //設置阻塞信號集,把SIGINT加入阻塞信號集
while(1)
{
sigpending(&p); //獲取未決信號集,通過參數p返回
printsigset(&p);
sleep(1);
}
return 0;
}
運行結果:
信號抵達的處理函數
linux信號抵達處理函數有兩個,一個是signal,一個是sigaction。
signal各個UNIX系統版本實現不同,不符合POSIX標準,即:移植性不好。sigaction符合POSIX標準,移植性好。並且sigaction功能比signal更全更強大。