1. 信號概念
信號是進程在運行過程中,由自身產生或由進程外部發過來的消息(事件)。信號是硬件中斷的軟件模擬(軟中斷)。每個信號用一個整型常量宏表示,以SIG開頭,比如SIGCHLD、SIGINT等,它們在系統頭文件<signal.h>中定義,也可以通過在shell下鍵入kill –l查看信號列表,或者鍵入man 7 signal查看更詳細的說明。
信號的生成來自內核,讓內核生成信號的請求來自3個地方:
l 用戶:用戶能夠通過輸入CTRL+c、Ctrl+/,或者是終端驅動程序分配給信號控制字符的其他任何鍵來請求內核產生信號;
l 內核:當進程執行出錯時,內核會給進程發送一個信號,例如非法段存取(內存訪問違規)、浮點數溢出等;
l 進程:一個進程可以通過系統調用kill給另一個進程發送信號,一個進程可以通過信號和另外一個進程進行通信。
由進程的某個操作產生的信號稱爲同步信號(synchronous signals),例如除0;由象用戶擊鍵這樣的進程外部事件產生的信號叫做異步信號。(asynchronous signals)。
進程接收到信號以後,可以有如下3種選擇進行處理:
l 接收默認處理:接收默認處理的進程通常會導致進程本身消亡。例如連接到終端的進程,用戶按下CTRL+c,將導致內核向進程發送一個SIGINT的信號,進程如果不對該信號做特殊的處理,系統將採用默認的方式處理該信號,即終止進程的執行;
l 忽略信號:進程可以通過代碼,顯示地忽略某個信號的處理,例如:signal(SIGINT,SIGDEF);但是某些信號是不能被忽略的,
l 捕捉信號並處理:進程可以事先註冊信號處理函數,當接收到信號時,由信號處理函數自動捕捉並且處理信號。
有兩個信號既不能被忽略也不能被捕捉,它們是SIGKILL和SIGSTOP。即進程接收到這兩個信號後,只能接受系統的默認處理,即終止線程。
2. signal信號處理機制
可以用函數signal註冊一個信號捕捉函數。原型爲:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signal的第1個參數signum表示要捕捉的信號,第2個參數是個函數指針,表示要對該信號進行捕捉的函數,該參數也可以是SIG_DEF(表示交由系統缺省處理,相當於白註冊了)或SIG_IGN(表示忽略掉該信號而不做任何處理)。signal如果調用成功,返回以前該信號的處理函數的地址,否則返回SIG_ERR。
sighandler_t是信號捕捉函數,由signal函數註冊,註冊以後,在整個進程運行過程中均有效,並且對不同的信號可以註冊同一個信號捕捉函數。該函數只有一個參數,表示信號值。
示例:
1、 捕捉終端CTRL+c產生的SIGINT信號:
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
void SignHandler(int iSignNo)
{
printf("Capture sign no:%d/n",iSignNo);
}
int main()
{
signal(SIGINT,SignHandler);
while(true)
sleep(1);
return 0;
}
該程序運行起來以後,通過按 CTRL+c將不再終止程序的運行。應爲CTRL+c產生的SIGINT信號已經由進程中註冊的SignHandler函數捕捉了。該程序可以通過 Ctrl+/終止,因爲組合鍵Ctrl+/能夠產生SIGQUIT信號,而該信號的捕捉函數尚未在程序中註冊。
2、 忽略掉終端CTRL+c產生的SIGINT信號:
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
signal(SIGINT,SIG_IGN);
while(true)
sleep(1);
return 0;
}
該程序運行起來以後,將CTRL+C產生的SIGINT信號忽略掉了,所以CTRL+C將不再能是該進程終止,要終止該進程,可以向進程發送SIGQUIT信號,即組合鍵CTRL+/
3、 接受信號的默認處理,接受默認處理就相當於沒有寫信號處理程序:
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
signal(SIGINT,DEF);
while(true)
sleep(1);
return 0;
}
3. sigaction信號處理機制
3.1. 信號處理情況分析
在signal處理機制下,還有許多特殊情況需要考慮:
1、 冊一個信號處理函數,並且處理完畢一個信號之後,是否需要重新註冊,才能夠捕捉下一個信號;
2、 如果信號處理函數正在處理信號,並且還沒有處理完畢時,又發生了一個同類型的信號,這時該怎麼處理;
3、 如果信號處理函數正在處理信號,並且還沒有處理完畢時,又發生了一個不同類型的信號,這時該怎麼處理;
4、 如果程序阻塞在一個系統調用(如read(...))時,發生了一個信號,這時是讓系統調用返回錯誤再接着進入信號處理函數,還是先跳轉到信號處理函數,等信號處理完畢後,系統調用再返回。
示例:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
int g_iSeq=0;
void SignHandler(int iSignNo)
{
int iSeq=g_iSeq++;
printf("%d Enter SignHandler,signo:%d./n",iSeq,iSignNo);
sleep(3);
printf("%d Leave SignHandler,signo:%d/n",iSeq,iSignNo);
}
int main()
{
char szBuf[8];
int iRet;
signal(SIGINT,SignHandler);
signal(SIGQUIT,SignHandler);
do{
iRet=read(STDIN_FILENO,szBuf,sizeof(szBuf)-1);
if(iRet<0){
perror("read fail.");
break;
}
szBuf[iRet]=0;
printf("Get: %s",szBuf);
}while(strcmp(szBuf,"quit/n")!=0);
return 0;
}
程序運行時,針對於如下幾種輸入情況(要輸入得快),看輸出結果:
1、 CTRL+c] [CTRL+c] [CTRL+c]
2、 [CTRL+c] [CTRL+/]
3、 hello [CTRL+/] [Enter]
4、 [CTRL+/] hello [Enter]
5、 hel [CTRL+/] lo[Enter]
針對於上面各種情況,不同版本OS可能有不同的響應結果。
3.2. sigaction信號處理註冊
如果要想用程序控制上述各種情況的響應結果,就必須採用新的信號捕獲機制,即使用sigaction信號處理機制。
函數原型:
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
sigaction也用於註冊一個信號處理函數。
參數signum爲需要捕捉的信號;
參數 act是一個結構體,裏面包含信號處理函數地址、處理方式等信息。
參數oldact是一個傳出參數,sigaction函數調用成功後,oldact裏面包含以前對signum的處理方式的信息。
如果函數調用成功,將返回0,否則返回-1
結構體 struct sigaction(注意名稱與函數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); // 保留,不要使用。
}
該結構體的各字段含義及使用方式:
1、字段sa_handler是一個函數指針,用於指向原型爲void handler(int)的信號處理函數地址, 即老類型 的信號處理函數;
2、字段sa_sigaction也是一個函數指針,用於指向原型爲:
void handler(int iSignNum,siginfo_t *pSignInfo,void *pReserved);
的信號處理函數,即新類型的信號處理函數。
該函數的三個參數含義爲:
iSignNum :傳入的信號
pSignInfo :與該信號相關的一些信息,它是個結構體
pReserved :保留,現沒用
3、字段sa_handler和sa_sigaction只應該有一個生效,如果想採用老的信號處理機制,就應該讓sa_handler指向正確的信號處理函數;否則應該讓sa_sigaction指向正確的信號處理函數,並且讓字段 sa_flags包含SA_SIGINFO選項。
4、字段sa_mask是一個包含信號集合的結構體,該結構體內的信號表示在進行信號處理時,將要被阻塞的信號。針對sigset_t結構體,有一組專門的函數對它進行處理,它們是:
#include <signal.h>
int sigemptyset(sigset_t *set); // 清空信號集合set
int sigfillset(sigset_t *set); // 將所有信號填充進set中
int sigaddset(sigset_t *set, int signum); // 往set中添加信號signum
int sigdelset(sigset_t *set, int signum); // 從set中移除信號signum
int sigismember(const sigset_t *set, int signum); // 判斷signnum是不是包含在set中
例如,如果打算在處理信號SIGINT時,只阻塞對SIGQUIT信號的處理,可以用如下種方法:
struct sigaction act;
sigemptyset(&act.sa_mask);
sigaddset(&act_sa_mask,SIGQUIT);
sigaction(SIGINT,&act,NULL);
5、 字段sa_flags是一組掩碼的合成值,指示信號處理時所應該採取的一些行爲,各掩碼的含義爲:
掩碼 描述
SA_RESETHAND 處理完畢要捕捉的信號後,將自動撤消信號處理函數的註冊,即必須再重新註冊信號處理函數,才能繼續處理接下來產生的信號。該選項不符合一般的信號處理流程,現已經被廢棄。
SA_NODEFER 在處理信號時,如果又發生了其它的信號,則立即進入其它信號的處理,等其它信號處理完畢後,再繼續處理當前的信號,即遞規地處理。如果sa_flags包含了該掩碼,則結構體sigaction的sa_mask將無效!
SA_RESTART 如果在發生信號時,程序正阻塞在某個系統調用,例如調用read()函數,則在處理完畢信號後,接着從阻塞的系統返回。該掩碼符合普通的程序處理流程,所以一般來說,應該設置該掩碼,否則信號處理完後,阻塞的系統調用將會返回失敗!
SA_SIGINFO 指示結構體的信號處理函數指針是哪個有效,如果sa_flags包含該掩碼,則sa_sigactiion指針有效,否則是sa_handler指針有效。
練習與驗證:
針對於先前的5種輸入情況,給下面代碼再添加一些代碼,使之能夠進行如下各種形式的響應:
1 、[CTRL+c] [CTRL+c]時,第1個信號處理阻塞第2個信號處理;
2 、[CTRL+c] [CTRL+c]時,第1個信號處理時,允許遞規地第2個信號處理;
3 、[CTRL+c] [CTRL+/]時,第1個信號阻塞第2個信號處理;
4 、read不要因爲信號處理而返回失敗結果。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
int g_iSeq=0;
void SignHandlerNew(int iSignNo,siginfo_t *pInfo,void *pReserved)
{
int iSeq=g_iSeq++;
printf("%d Enter SignHandlerNew,signo:%d./n",iSeq,iSignNo);
sleep(3);
printf("%d Leave SignHandlerNew,signo:%d/n",iSeq,iSignNo);
}
int main()
{
char szBuf[8];
int iRet;
struct sigaction act;
act.sa_sigaction=SignHandlerNew;
act.sa_flags=SA_SIGINFO;
//
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,NULL);
sigaction(SIGQUIT,&act,NULL);
do{
iRet=read(STDIN_FILENO,szBuf,sizeof(szBuf)-1);
if(iRet<0){
perror("read fail.");
break;
}
szBuf[iRet]=0;
printf("Get: %s",szBuf);
}while(strcmp(szBuf,"quit/n")!=0);
return 0;
}
3.3. sigprocmask信號阻塞
函數sigaction中設置的被阻塞信號集合只是針對於要處理的信號,例如
struct sigaction act;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask,SIGQUIT);
sigaction(SIGINT,&act,NULL);
表示只有在處理信號SIGINT時,才阻塞信號SIGQUIT;
函數sigprocmask是全程阻塞,在sigprocmask中設置了阻塞集合後,被阻塞的信號將不能再被信號處理函數捕捉,直到重新設置阻塞信號集合。
原型爲:
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
參數how的值爲如下3者之一:
a :SIG_BLOCK ,將參數2的信號集合添加到進程原有的阻塞信號集合中
b :SIG_UNBLOCK ,從進程原有的阻塞信號集合移除參數2中包含的信號
c :SIG_SET,重新設置進程的阻塞信號集爲參數2的信號集
參數set爲阻塞信號集
參數oldset是傳出參數,存放進程原有的信號集。
示例:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
int g_iSeq=0;
void SignHandlerNew(int iSignNo,siginfo_t *pInfo,void *pReserved)
{
int iSeq=g_iSeq++;
printf("%d Enter SignHandlerNew,signo:%d./n",iSeq,iSignNo);
sleep(3);
printf("%d Leave SignHandlerNew,signo:%d/n",iSeq,iSignNo);
}
int main()
{
char szBuf[8];
int iRet;
struct sigaction act;
act.sa_sigaction=SignHandlerNew;
act.sa_flags=SA_SIGINFO;
// 屏蔽掉SIGINT 信號,SigHandlerNew 將不能再捕捉SIGINT
sigset_t sigSet;
sigemptyset(&sigSet);
sigaddset(&sigSet,SIGINT);
sigprocmask(SIG_BLOCK,&sigSet,NULL);
//
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,NULL);
sigaction(SIGQUIT,&act,NULL);
do{
iRet=read(STDIN_FILENO,szBuf,sizeof(szBuf)-1);
if(iRet<0){
perror("read fail.");
break;
}
szBuf[iRet]=0;
printf("Get: %s",szBuf);
}while(strcmp(szBuf,"quit/n")!=0);
return 0;
}
4. 用程序發送信號
4.1. kill信號發送函數
原型爲:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
參數pid爲將要接受信號的進程的pid
參數sig爲要發送的信號
如果成功,返回0,否則爲-1。
示例,輸入結束後,將通過發送信號SIGQUIT把自己殺掉:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
while(true){
if(getchar()==EOF)
kill(getpid(),SIGQUIT);
}
return 0;
}
4.2. sigqueue信號發送函數
sigqueue也可以發送信號,並且能傳遞附加的信息。
原型爲:
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
參數pid爲接收信號的進程;
參數sig爲要發送的信號;
參數value爲一整型與指針類型的聯合體:
union sigval {
int sival_int;
void *sival_ptr;
};
由sigqueue函數發送的信號的第3個參數value的值,可以被進程的信號處理函數的第2個參數info->si_ptr接收到。
示例1,進程給自己發信號,並且帶上附加信息:
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
void SignHandlerNew(int signum,siginfo_t *info,void *myact)
{
char *pszInfo=(char *)(info->si_ptr);
printf("Get:%d info:%s/n",signum,pszInfo);
}
int main(int argc,char**argv)
{
struct sigaction act;
union sigval mysigval;
int sig;
char data[]="other info";
//
if(argc<2){
printf("usage: SIGNNUM/n");
return -1;
}
mysigval.sival_ptr=data;
sig=atoi(argv[1]);
sigemptyset(&act.sa_mask);
act.sa_sigaction=SignHandlerNew;
act.sa_flags=SA_SIGINFO;
sigaction(sig,&act,NULL);
while(true){
printf("wait for the signal/n");
sigqueue(getpid(),sig,mysigval);
sleep(2);
}
}
示例2:一個進程向另外一個進程發送信號。注意:發送進程不要將自己進程空間的地址發送給接收進程,因爲接收進程接收到地址也訪問不到發送進程的地址空間的。
示例2信號接收程序:
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
void SignHandlerNew(int signum,siginfo_t *info,void *myact)
{
printf("Get:%d info:%d/n",signum,info->si_int);
}
int main(int argc,char**argv)
{
struct sigaction act;
//
if(argc<2){
printf("usage: signnum/n");
return -1;
}
sigemptyset(&act.sa_mask);
act.sa_sigaction=SignHandlerNew;
act.sa_flags=SA_SIGINFO;
sigaction(atoi(argv[1]),&act,NULL);
while(1)
{
printf("wait for the signal/n");
sleep(2);
}
return 0;
}
示例2信號發送程序:
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc,char**argv)
{
union sigval mysigval;
int iPid,iSignNo,iData;
//
if(argc<4){
printf("usage: pid signnum data/n");
return -1;
}
iPid=atoi(argv[1]);
iSignNo=atoi(argv[2]);
iData=atoi(argv[3]);
mysigval.sival_int=iData;
if(sigqueue(iPid,iSignNo,mysigval)<0)
perror("Send signal fail.");
return 0;
}
5. 計時器與信號
5.1. 睡眠函數
Linux下有兩個睡眠函數,原型爲:
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
void usleep(unsigned long usec);
函數sleep讓進程睡眠seconds秒,函數usleep讓進程睡眠usec毫秒。
sleep 睡眠函數內部是用信號機制進行處理的,用到的函數有:
#include <unistd.h>
unsigned int alarm(unsigned int seconds); // 告知自身進程,要進程在seconds秒後自動產生一個//SIGALRM的信號,
int pause(void); // 將自身進程掛起,直到有信號發生時才從pause返回
示例:模擬睡眠3秒:
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void SignHandler(int iSignNo)
{
printf("signal:%d/n",iSignNo);
}
int main()
{
signal(SIGALRM,SignHandler);
alarm(3);
printf("Before pause()./n");
pause();
printf("After pause()./n");
return 0;
}
注意:因爲sleep在內部是用alarm實現的,所以在程序中最好不要sleep與alarm混用,以免造成混亂。
5.2. 時鐘處理
Linux爲每個進程維護3個計時器,分別是真實計時器、虛擬計時器和實用計時器。
l 真實計時器計算的是程序運行的實際時間;
l 虛擬計時器計算的是程序運行在用戶態時所消耗的時間(可認爲是實際時間減掉(系統調用和程序睡眠所消耗)的時間);
l 實用計時器計算的是程序處於用戶態和處於內核態所消耗的時間之和。
例如:有一程序運行,在用戶態運行了5秒,在內核態運行了6秒,還睡眠了7秒,則真實計算器計算的結果是18秒,虛擬計時器計算的是5秒,實用計時器計算的是11秒。
用指定的初始間隔和重複間隔時間爲進程設定好一個計時器後,該計時器就會定時地向進程發送時鐘信號。3個計時器發送的時鐘信號分別爲:SIGALRM,SIGVTALRM和SIGPROF。
用到的函數與數據結構:
#include <sys/time.h>
//獲取計時器的設置
//which指定哪個計時器,可選項爲ITIMER_REAL(真實計時器)、ITIMER_VITUAL(虛擬計時器、ITIMER_PROF(實用計時器))
//value爲一結構體的傳出參數,用於傳出該計時器的初始間隔時間和重複間隔時間
//如果成功,返回0,否則-1
int getitimer(int which, struct itimerval *value);
//設置計時器
//which指定哪個計時器,可選項爲ITIMER_REAL(真實計時器)、ITIMER_VITUAL(虛擬計時器、ITIMER_PROF(實用計時器))
//value爲一結構體的傳入參數,指定該計時器的初始間隔時間和重複間隔時間
//ovalue爲一結構體傳出參數,用於傳出以前的計時器時間設置。
//如果成功,返回0,否則-1
int setitimer(int which, const struct itimerval *value, struct itimer val *ovalue);
struct itimerval {
struct timeval it_interval; /* next value */ // 重複間隔
struct timeval it_value; /* current value */ // 初始間隔
};
struct timeval {
long tv_sec; /* seconds */ // 時間的秒數部分
long tv_usec; /* microseconds */ // 時間的微秒部分
};
示例:啓用真實計時器的進行時鐘處理
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
void TimeInt2Obj(int imSecond,timeval *ptVal)
{
ptVal->tv_sec=imSecond/1000;
ptVal->tv_usec=(imSecond%1000)*1000;
}
void SignHandler(int SignNo)
{
printf("Clock/n");
}
int main()
{
signal(SIGALRM,SignHandler);
itimerval tval;
TimeInt2Obj(1,&tval.it_value); // 設初始間隔爲1毫秒,注意不要爲0
TimeInt2Obj(1500,&tval.it_interval); // 設置以後的重複間隔爲1500毫秒
setitimer(ITIMER_REAL,&tval,NULL);
while(getchar()!=EOF);
return 0;
}
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/ubuntulover/archive/2009/07/13/4345534.aspx