信號,將內核和進程聯繫起來,是操作系統內部交互的機制。
通過kill -l指令查看所有信號
注意,這裏沒有32,33號信號,也是從這裏將信號分爲兩類,1~31位普通信號,34~64爲實時信號
信號產生原因
ctrl-c:2號信號(SIGINT),終止當前運行進程,注意:ctrl-c 產生的信號只發給前臺進程
ctrl-\:3號信號(SIGQUIT),終止進程並且產生coredump文件
CPU :0作爲除數時,CPU運算單元會產生異常,產生8號信號(SIGFPE)
MMU:訪問內存越界或者非法的時候,MMU發現當前內存不合法,就會通知CPU向該進程發送一個11號信號(SIGSEGV),使終止進程(段錯誤)
kill 給某個進程發送某個信號
管道讀端關閉,嘗試寫,會觸發13號信號(SIGPIPE)
alarm,若干秒後,操作系統會發送14號鬧鐘信號(SIGALRM)
關於信號處理的三種方式
- 忽略 利用signal給信號註冊一個函數
- 默認 通常情況下是讓信號異常終止
- 捕捉 利用signal,有一個函數指針,在函數指針中進行處理(無窮大符號,用戶和內核如何切換)
//代碼功能:利用MyHnadler函數將產生的SIGINT(ctrl-c)信號捕捉,一旦產生SIGINT信號,就打印對應的值
#include <stdio.h>
#include <signal.h>
void MyHandler(int sig)
{
printf("sig = %d\n",sig);
}
int main()
{
signal(SIGINT,MyHandler);
while(1)
{}
return 0;
}
下圖展示信號捕捉的時候,用戶和內核的幾次交互
- 執行順序,一個正無窮符號。
- main和MyHandler函數有各自的執行流,各自有各自的調用棧。
- MyHandler函數結束以前,main函數需等待,這也限制了信號捕捉的使用場景,比如7*24小時的服務器程序時不允許main函數停止的。
- 本次過程,內核態和用戶態共交互4次,途中紅圈表示。
信號的阻塞
PCB中維護了兩個位圖,分別表示已經收到但是沒來得及處理的信號(未決信號集),和要去阻塞的信號(信號屏蔽字)
信號集操作函數
對於未決信號集和信號屏蔽字的操作其實就是在操作一個位圖,所以我們需要一個位圖結構體(sigset_t)
每個信號只用一個bit位表示,非0即1,不表示多少,只表示信號的有效無效狀態。
在未決信號集中,‘有效’和‘無效’狀態分別表示是否處於未決狀態。
在信號屏蔽字中,‘有效’和‘無效’狀態分別表示信號是否被阻塞。
int sigemptyset(sigset_t *set); //初始化位圖,並將所有位置爲0
int sigfillset(sigset_t *set); //將所有位置爲1
int sigaddset (sigset_t *set, int signo);//給位圖中某一位置爲1
int sigdelset(sigset_t *set, int signo); //給位圖中某一位置爲0
int sigismember(const sigset_t *set, int signo);//判斷某一位是0/1
返回值
前四個函數,成功返回0,失敗返回-1;
sigismember函數,若包含返回1,不包含返回0,失敗返回-1
sigpromask - 讀取或更改進程的信號屏蔽字,指定某一位信號被阻塞
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
參數
how : 將哪一位進行改變
set : 輸入型參數,通過設置set中的位
oset : 輸出型參數,設置新的信號屏蔽字之前把舊的備份下來,以便於恢復
返回值:若成功則爲0,若出錯則爲-1
sigpending - 讀取未決信號集
int sigpending(sigset_t* set)
參數
通過set參數傳出。
返回值
調⽤成功則返回0,出錯則返回-1。 下⾯⽤剛學的⼏個函數做個 實驗。程序如下:
代碼演示
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void MyHandler(int sig)
{
printf("sig = %d\n",sig);
}
void PrintSigSet(sigset_t* set)
{
int i = 1;
for(; i < 32; ++i) //只考慮普通信號
{
if(sigismember(set,i))
{
printf("1");
}
else
{
printf("0");
}
}
printf("\n");
}
int main()
{
//1.捕捉 SIGINT 信號
signal(SIGINT,MyHandler);
//2.把 SIGINT 信號屏蔽掉
sigset_t set;
sigset_t oset;
sigemptyset(&set);
sigaddset(&set,SIGINT);
sigprocmask(SIG_BLOCK,&set,&oset);
//3.循環讀取未決信號集
int count = 0;
while(1)
{
++count;
if(count >= 5)
{
count = 0;
//解除信號屏蔽字,再設置上
printf("解除信號屏蔽字\n");
sigprocmask(SIG_SETMASK,&oset,NULL);
sleep(1);
printf("再次設置信號屏蔽字\n");
sigprocmask(SIG_BLOCK,&set,&oset);
}
sigset_t pending_set;
sigpending(&pending_set);
PrintSigSet(&pending_set);
sleep(1);
}
//循環若干次,自動解除屏蔽
return 0;
}
結果演示:
起始時,未決信號集中沒有未決信號,所以全0
當鍵入ctrl-c時,未決信號集的2號位置(從1開始)被設置爲1
到了第五秒的時候,程序自動解除信號屏蔽字
這時SIGINT信號被我們所定義的MyHandler函數所捕捉,打印了一句 sig = %d
回到main函數的時候,重新將信號屏蔽字集置爲全0