信號對於我們來說已經很熟悉了,走在路上紅燈亮起,這時我們就停下腳步,綠燈亮起,又繼續前行;當水壺哨聲響起的時候,這時我們就過去關掉電源……
那麼我們爲什麼會對那種現象進行識別,並做出一系列的反應呢?
這是因爲大腦能記住生活中的這種信號進行識別並做出動作。那麼進程也是如此。
信號的產生:
1.用戶在終端按下某些鍵時,終端驅動程序會發送信號給前臺進程;
終止程序:在鍵盤按下ctrl+\ 組合鍵產生信號SIGQUIT,SIGQUIT默認動作是終止程序並Core Dump(核心轉儲).
Core Dump:當一個進程在異常終止的時候,操作系統會將內存中進程的有效數據拷貝到磁盤上,文件名通常是core。
查詢core文件大小命令:ulimit -a
修改core 文件大小命令:ulimit -c 大小
Core Dump:可以定位程序錯誤
2.硬件異常產生信號(如:除數爲0,無效的內存引用等),硬件檢測到並通知內核,內核會向當前進程發送信號;
#include<stdio.h>
#include<stdlib.h>
int main(int argc,char* argv)
{
if(argc!=2){
printf("Usage:%s,symbol",argv[0]);
exit(1);
}
return 0;
}
3.用戶調用kill命令將信號發送給其他進程,進程調用kill函數將信號發送給另一個進程或進程組;
函數:
int kill(pid_t pid,int signo)
:給指定進程髮指定信號
int main(int argc,char* argv[])
{
if(argc!=3){
printf("Error:%s,signo,pid\n",argv[0]);
exit(1);
}
kill(atoi(argv[2]),atoi(argv[1]));
return 0;
}
int raise(int signo)
:給當前進程發送特定信號
void abort(void)
:使當前進程接收到信號異常終止
4.當檢測到某些軟件條件已經產生,將其通知有關進程時產生信號。
int count=0;
void handler(int signo)
{
printf("signo:%d count:%d\n",signo,count);
exit(1);
}
int main(int argc,char* argv[])
{
signal(14,handler);
alarm(1);//信號沒產生前的一秒,將數據累加多少次
while(1){
printf("count=%d\n",count);
count++;
}
return 0;
}
信號在內核中的存在:
遞達(Delivery):實際執行信號的處理動作;
阻塞(Block):被阻塞的信號產生時將保持在未決狀態,直到進程解除對此信號的阻塞,才執行遞達的動作;
未決(Pending):信號從產生到遞達之間的狀態。
信號集:
每個信號只有一個bit的未決標誌或阻塞標誌,不記錄該信號產生多少次,因此可以用相同的數據類型sigset_t來存儲,sigset_t稱爲信號集。
使用者只能調用以下函數對sigset_t變量進行操作,不應該對它的內部數據進行解釋。
int sigemptyset(sigset_t* set);
初始化set所指向的信號集,使其中所有信號的對應bit位清零,表示該信號集不包含任何有效信號;
int sigfillset(sigset_t* set);
初始化set所指向的信號集,使其中所有信號的對應bit置位,表示該信號集的有效信號包含系統支持的所有信號;
int sigaddset(sigset* set,int signo);
將一個信號signo添加到set所指向的信號集中;
int sigdelset(sigset_t* set,int signo);
將一個信號signo從set所指向的信號集中刪除;
以上四個函數都是成功返回0,失敗返回-1.
int sigismember(sigset_t* set,int signo);
判斷一個信號signo是否在set所指向的信號集中。
sigismember函數是一個bool函數,包含則返回1,不包含則返回0,出錯返回-1.
讀取或更改進程的信號屏蔽字函數(阻塞信號集):
int sigprocmask(int how,const sigset_t *set,sigset_t* oset);
參數:how:如何更改當前信號屏蔽字;
讀取進程的當前信號屏蔽字由oset參數傳出,若set和oset都爲非空,則將原來的信號屏蔽字備份到oset裏。
讀取當前進程的未決信號集:
int sigpending(sigset_t* set);
函數返回信號集,其中的各個信號對於調用進程是阻塞的而不能遞達,因此也一定是當前未決的。信號集由set參數返回。
成功返回0,失敗返回-1.
void Print(sigset_t* set)
{
int i=0;
for(;i<32;++i){
//判斷信號是否在信號集中,在的話就將對應比特位置1
if(sigismember(set,i)){
putchar('1');
}
else{
putchar('0');
}
}
printf("\n");
}
int main()
{
sigset_t sig,psig;
//對信號集進行初始化
sigemptyset(&sig);
//將信號SIGINT添加到信號集中
sigaddset(&sig,SIGINT);
//設置進程的信號屏蔽字
sigprocmask(SIG_BLOCK,&sig,NULL);
while(1){
//獲取當前進程的未決信號集
sigpending(&psig);
Print(&psig);
sleep(1);
}
return 0;
}
捕捉信號:
什麼時候對信號進行捕捉是合適的時機?
從上圖可知:捕捉信號的最佳時機在進程從內核態返回到用戶態的時候,這個時候會進行一次信號的檢測和處理,如果信號的處理動作是用戶自定義函數,信號遞達時調用這個處理函數,這個過程稱爲信號的捕捉。
如何去捕捉信號?
讀取或修改與指定信號相關聯的處理動作函數:
int sigaction(int signo,const struct sigaction* act,struct sigaction* oact);
結構體:struct sigaction
{
void(*)(int) sa_handle;
sigset_t sa_mask;
int sa_flags;
}
act:若非空,則根據act修改該信號的處理動作;
oact:若非空,則根據oact傳出原來的信號處理動作。
返回值:成功返回0,失敗返回-1.
使調用進程掛起直到有信號遞達函數:
int pause(void)
如果信號處理動作是終止進程,進程終止,函數不返回;
如果信號處理動作是忽略,進程還是處於掛起狀態,函數不返回;
如果信號處理動作是捕捉,則調用信號處理函數,成功pause返回-1,出錯error設置爲EINTR。
思考:調用pause()函數之後,信號遞達,設置的信號處理函數啥都不做,那麼信號處理函數有沒有必要存在?
有必要。
調用pause()函數之後,進程處於掛起狀態,當信號遞達的時候信號自動添加到信號屏蔽字中(當該信號再次到來的時候會被阻塞到當前處理結束),信號處理函數調用完畢之後信號解除屏蔽,pause()函數返回,進程繼續執行;如果沒有信號處理函數,pause()函數將使進程一直處於掛起狀態。
競態條件:由於程序執行的時序問題導致的錯誤。
int sigsuspend(const sigset_t* sigmask )
通過指定的sigmask來臨時解除對某些信號的屏蔽。沒有成功返回值,調用信號處理函數返回-1,error設置爲EINTR。
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
void handler(int signo)
{
printf("get a signo:%d\n",signo);
exit(1);
}
void mysleep(int seconds)
{
struct sigaction new, old;
sigset_t newmask,oldmask,midmask;
unsigned int count=0;
new.sa_handler=handler;
sigemptyset(&new.sa_mask);
new.sa_flags=0;
sigaction(SIGALRM,&new,&old);
sigemptyset(&newmask);
sigaddset(&newmask,SIGALRM);
sigprocmask(SIG_BLOCK,&newmask,&oldmask);
alarm(seconds);
midmask=oldmask;
sigdelset(&midmask,SIGALRM);
sigsuspend(&midmask);
count=alarm(0);
sigaction(SIGALRM,&old,NULL);
sigprocmask(SIG_SETMASK,&oldmask,NULL);
}
int main()
{
while(1){
printf("I like eating icecream!\n");
mysleep(1);
}
return 0;
}