信號:在生活中,我們遇到過不同種類的信號,比如:(交通信號,乃至某個人的表情,動作等帶給你不同的信號)然而,在我們的linux下,我們最熟悉的就是,當遇到一個死循環的程序時,我們第一想到的就是按ctrl+c,此時這個進程立馬終止,這是一種通過鍵盤產生的信號。而說起ctrl+c,就引出了前臺進程和后臺進程。當ctrl+c產生的信號只能發給前臺進程。
用kill -l命令就可以查看信號了;
產生信號的另一種方式是:信號異常觸發系統使該進程終止:
例子:
運行結果:
還有一種方式,通過指令來使該進程終止:
然后直接運行./test:
還有一種方式,通過alarm使進程終止;
說起alarm給大家舉個例子吧,alarm意思是鬧鐘,在這里也同樣代表著當你執行某個進程時,突然用alarm定時的時間到了,這時鬧鐘發揮了作用,使得你的進程被迫停止,比如:
這是一個統計1秒鐘計數的程序,當一秒鐘到時,就會被SIGALRM信號終止。
運行結果:
下面我們來說一下處理信號的幾種方式吧!
忽略信號;執行默認;執行自定義(信號的捕捉)三種方式。(那么問題來了,什么時候處理呢?答案是適當的時候處理)
執行信號的處理動作稱為信號遞達,信號從產生到遞達之間的狀態,稱為信號未決。進程可以選擇阻塞某個信號,被阻塞的信號產生時將保持在未決狀態,直到進程解除對此信號的阻塞,才執行遞達的動作。
阻塞和忽略是不同,只要信號被阻塞就不會遞達,而忽略則是在遞達之后可選的一種處理動作。
信號在內核中是這樣表示的:
block:代表屏蔽狀態字(1表示阻塞,0表示不阻塞)
pending:代表未決(1表示未決,0表示可以遞達)
handler:代表信號的處理方式(默認,忽略,自定義)
每個信號都有兩個標志位分別表示阻塞(block)和未決(pending),還有一個函數指針表示處理動作。信號產生時,內核在進程控制塊中設置該信號的未決標志,直到信號遞達才清除該標志。
上面那張表說明:SIGHUP是沒有阻塞也沒有產生,所以處理動作為默認處理動作
SIGINT的block為1,pending也為1,表示正在阻塞,無法遞達。它的處理動作為忽略,但是在沒有解除阻塞之前不能忽略該信號,很可能在解除阻塞前改變為其他的處理動作。
對于上述的三張表,操作系統中的每個進程運行時都會存在。
SIGQUIT的block為1,pending為0,說明正在被阻塞,解除阻塞后就可以遞達。處理動作為用戶自定義的處理動作。
還有一種現象是,對于解除阻塞之前可能會發送多次信號,這時操作系統該作何處理呢。這里主要是分為普通信號和實時信號,普通信號出現發送多次的情況,會當做是一次信號進行處理。而實時信號發送多次的情況,會將這多個信號存在一個隊列中,分別處理各個信號。我們一般討論的是普通信號,因此只記錄一次,我們將未決和阻塞狀態用同一個數據類型來存儲sigset_t,信號集為sigset_t。還需要注意的一點是,阻塞信號集也叫做當前進程的信號屏蔽字。
說了信號集,我們之前說過這些普通信號是以位圖的形式存放的。每一個bit位表示一種信號是否存在。
對于信號的操作,我們有一組信號集操作函數,如下:
函數sigemptyset初始化set所指向的信號集,使其中所有信號的對應bit清零,表示該信號集不包含任何有效信號。
函數sigfillset初始化set所指向的信號集,使其中所有信號的對應bit置位,表?示 該信號集的有效信號包括系統支
持的所有信號。
在使?用sigset_t類型的變量之前,?一定要調 ?用sigemptyset或sigfillset做初始化,使信號集處于確定的狀態。
做完初始化之后調用sigaddset和sigdelset來添加或者刪除信號。
這四個函數成功返回0,失敗返回-1;
sigismember用來表示某種信號是否出現在有效信號集中。出現返回1,不出現返回0.
*還有函數sigprocmask:(讀取或更改進程的信號屏蔽字)
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
成功返回0,出錯返回-1
這里的oset為輸出型參數,如果oset為非空,則輸出當前進程的信號屏蔽字,通過oset輸出。如果set為非空,則更改信號屏蔽字,how指示如何更改。如果set和oset都為非空,則將當前進程的信號屏蔽字備份在oset中,然后再通過how參數更改信號屏蔽字。
how主要有三種表示:SIG_BLOCK(包含了我們希望添加的信號屏蔽字),SIG_UNBLOCK(包含了我們希望從信號屏蔽字中阻塞的信號),SIG_SETMASK(設置了當前的信號屏蔽字的值);
*函數sigpending:(讀取當前進程的未決信號集)
#include <signal.h>
int sigpending(sigset_t *set);
set為輸出型參數,將信號通過set輸出
?小栗子:
#include<stdio.h>
#include<signal.h>void PrintSigset(sigset_t *sig)
{int i = 0;for(i = 1; i < 32; i++){if(sigismember(sig,i)){printf("1 ");}else{printf("0 ");}}printf("\n");
}int main()
{sigset_t sigset,osigset;sigemptyset(&sigset);sigemptyset(&osigset);sigaddset(&sigset,SIGINT);sigprocmask(SIG_BLOCK,&sigset,&osigset);while(1){sigpending(&sigset);PrintSigset(&sigset);sleep(1);}return 0;
}
這個程序實現了將2號信號設置為阻塞信號。所以一直無法遞達。
程序運行時,每秒鐘把各信號的未決狀態打印一遍,由于我們阻塞了SIGINT信號,按Ctrl-C將會使SIGINT信號處于未決
狀態,按Ctrl-\和ctrl+z仍然可以終止程序,因為SIGQUIT信號沒有阻塞。
運行結果:
還有一個小栗子,我們將2號信號阻塞之后,5秒后解除阻塞。并打印原來信號的狀態。
#include<stdio.h>
#include<signal.h>void PrintSigset(sigset_t *sig)
{int i = 0;for(i = 1; i < 32; i++){if(sigismember(sig,i)){printf("1 ");}else{printf("0 ");}}printf("\n");
}void handler(int sig)
{printf("pid: %d sig:%d \n",getpid(),sig);
}
int main()
{sigset_t sigset,osigset;sigemptyset(&sigset);sigemptyset(&osigset);sigaddset(&sigset,SIGINT);sigprocmask(SIG_BLOCK,&sigset,&osigset);int count = 0;signal(2,handler);//signal(2,SIG_DFL);while(1){sigpending(&sigset);PrintSigset(&sigset);sleep(1);if(count++ > 5){sigprocmask(SIG_SETMASK,&osigset,NULL);count = 0;}}return 0;
}
運行結果: