歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> Linux信號處理

Linux信號處理

日期:2017/3/1 17:16:34   编辑:關於Linux

Linux信號處理 這兩天一直在看Linux 中信號的知識,看完了也應該總結總結了.當然以下結論可能還有不足之處,有的話盡管言明,大家共同學習學習. 其實在現實生活中我們都知道信號是什麼意思,在生活中很簡單很容易理解,信號嘛就是一個信息的意思,比如說我們古代打仗的時候,兵將什麼時候開始向前沖,什麼時候開始奮戰,當指揮部發出一個"命令"的時候,這個命令可能是吹號角,也可能是放煙火等等,再如現在,我們接到一個電話,某個朋友拜托我們去干一件事,那我們接完電話後就可能立即去辦事情了,這些,"號角","煙火","電話"都是我們收到的"信號",而後面的動作就是我們收到信號後要做的動作.其實計算機也是一樣的,它的內部有好多進程在執行,如果一個進程想讓另一個進程辦一件事情,那麼它也可以通過發信號來通知另一個進程,這時候,信號在進程間的通信就起到了一定的作用,接下來我們具體看看信號的來源,種類,以及進程對信號的響應. www.2cto.com 其實在計算機中信號就相當於是軟中斷,,它提供了一種處理異步事件的方法,也是進程鍵唯一的異步通信方式,我們知道異步就是說,根本沒有時間規定,你(一個進程)現在可以處理你想干的事情,而當檢測到有信號的時候再轉去處理信號,也就是說這種信號的發生是隨機的. 信號的來源有兩種方式:硬件方式和軟件方式. 信號的種類有好多中,在Linux下面,我們在終端通過使用命令 kill -l 便可以查看Linux系統支持的全部信號,至於信號的含義,這裡不再贅述,有興趣的讀者可以翻閱相關的資料了解.我們也可以在頭文件<signal.h>中查看這些信號. 而進程對信號有三種處理方式:捕捉信號,忽略信號,按照系統默認方式處理. 信號的捕捉和處理:Linux 系統對信號的處理主要由signal和sigaction函數來處理. 1.signal函數:用來設置進程在接收到信號時的動作,我們在shell下面輸入man signal可獲取該函數的原型. #include<signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum,sighandler_t handler); signal會根據signum指定的信號編號來設置該信號的處理函數,當指定的信號到達時,就會轉到參數handler指定的參數執行,如果參數handler不是函數指針,則必須是常數SIG_INT(忽略該信號)或者是SIG_DFL(采用默認方式處理信號).若handler是一個函數指針,它所指向的函數的類型是sighandler_t,,即它所指向函數有一個int型的參數,且返回值類型為void.函數執行成功返回以前的信號處理函數指針,當有錯誤發生時返回SIG_ERR(即-1). 下面通過具體的例子看一個signal函數的用法: #include<stdio.h> www.2cto.com #include<signal.h> /*信號處理函數*/ void handler_sigint(int signo) { printf("recv SIGINT\n"); } int main() { /*安裝信號處理函數*/ signal(SIGINT,handler_sigint); while(1); return ; } 當我們在終端中運行該程序後,按下Ctrl+C鍵,則會出現recv SIGINT,繼續按鍵繼續出現recv SIGINT,但是當我們按下Ctrl+\後則退出了,我們按下的Ctrl+\鍵就相當與進程收到了SIGQUIT這個信號,而這個信號的作用就是讓進程終止,那麼如果我們忽略這個信號呢,則程序就會永遠的死在那裡了,退不出來了,如改為如下所示: #include<stdio.h> #include<signal.h> void handler_sigint(int signo) { printf("recv SIGINT\n"); } www.2cto.com int main() { signal(SIGINT,handler_sigint); signal(SIGQUIT,SIG_INT);//忽略該信號,此時Ctrl+\已不再起作用了. while(1); return ; } 上面為什麼會退出呢,就是因為進程沒有對信號SIGQUIT 做處理,那麼它就按照默認的方式走,即結束進程. 接下來我們看sigaction函數:同樣man sigaction一下你會看到原型: #include<signal.h> int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact) signum與上面的相同,即可以是SIGKILL和SIGSTOP以外的任意信號,而act是一個結構體指針,如果act不是空指針,則為signum設置心的信號處理函數,如果oldact不是空指針,則舊的處理函數將被存儲在oldact中. 接下來看一個sigaction的例子: #include<stdio.h> #include<signal.h> int temp=0; void handler_sigint(int signo) { printf("recv SIGINT\n"); sleep(5); temp+=1; printf("the value of temp is :%d\n",temp); printf("in handler_sigint,after sleep\n"); } int main() { struct sigaction act; act.sa_handler=handler_sigint; act.sa_flags=SA_NOMASK;//意思是在處理次信號前允許此信號再次傳遞,相當於中斷嵌套 sigaction(SIGINT,&act,NULL); while(1); return 0; } www.2cto.com 運行結果如下: hfm@hfm-Lenovo-KL6B-KL6C-Z475:~$ gcc -o sigaction sigaction.c hfm@hfm-Lenovo-KL6B-KL6C-Z475:~$ ./sigaction ^Crecv SIGINT ^Crecv SIGINT ^Crecv SIGINT ^Crecv SIGINT the value of temp is :1 in handler_sigint,after sleep the value of temp is :2 in handler_sigint,after sleep the value of temp is :3 in handler_sigint,after sleep the value of temp is :4 in handler_sigint,after sleep
注意我上面提到的中斷嵌套的含義,當我們按下Ctrl+c的時候,發出SIGINT信號,打印,然後休眠5秒,但是在這5秒之內呢,你又按下了Ctrl+c,於是從sleep函數處嵌套調用信號處理函數handler_sigint,5秒之後呢,函數打印出temp的值,又返回到sleep處繼續執行你剛按下的第二次Ctrl+c,一次類推執行完四次之後,temp的值也增加到了4,於是程序返回到住函數處繼續執行,基本上的執行過程就是這樣的啦! 信號處理函數的發送主要由函數:kill,faise,sigqueue,alarm,setitimer,abort來完成. 通過man命令可以查看kill函數的原型: #include<sys/types.h> #include<signal.h> int kill(pid_t pid,int sig); pid顧名思義就是進程號了,而sig是發送信號的編號,我們要注意,只有root權限的進程才能向其他任一進程發送信號,而非root權限的進程只能向同一組或同一用戶的進程發送信號. 看一個簡單模擬kill命令的例子:kill.c #include<stdio.h> #include<signal.h> #include<stdlib.h> #include<sys/types.h> int main(int argc,char **argv) { www.2cto.com int i,j; int signum=SIGTERM; pid_t pid; if(argc!=2&&argc!=4){ printf("Usage:./kill<-s signum>[pid]\n"); exit(0); } for(i=1;i<argc;i++){ if(!strcmp(argv[i],"-s")){ signum=argv[i+1]; break; } } if(argc==2){ pid=atoi(argv[1]); }else{ for(j=1;j<argc;j++){ if(j!=i&&j!=i+1){ pid=atoi(argv[j]); break; } } } if(kill(pid,signum)<0){ perror("kill"); exit(0); } return 0; } 信號SIGINT 的編號在所有的Linux系統中都為2. 函數raise比較簡單,用來給調用它的進程發送信號:原型:int faise(int sig);參數sig表示要發送的信號編號,成功返回0,失敗返回非0值. sigqueue函數:它是一個比較新的發送信號函數,通過man命令可以查看原型.該函數不僅可以向進程發送信號,還可以給進程發送數據,其原型為: int sigqueue(pid_t pid,int sig,const union sigval value);
下面來看一個例子:send_data_signo.c:利用信號傳遞數據,本程序發送數據 #include<stdio.h> #include<sys/types.h> #include<signal.h> #include<stdlib.h> int main(int argc,char **argv) { union sigval value; int signum=SIGTERM; pid_t pid; int i; www.2cto.com value.sival_int=0; if(argc!=3&&argc!=5&&argc!=7){ printf("./send_data_signo <-d data> <-s signum> [-p] [data]\n"); exit(1); } /*從命令行解析出信號編號,PID 以及待傳送的數據*/ for(i=1;i<argc;i++){ if(!strcmp(argv[i],"-d")){ value.sival_int=atoi(argv[i+1]); continue; } if(!strcmp(argv[i],"-s")){ signum=atoi(argv[i+1]); continue; } if(!strcmp(argv[i],"-p")){ pid=atoi(argv[i+1]); continue; } } /*利用sigqueue函數來發送信號*/ if(sigqueue(pid,signum,value)<0){ perror("sigqueue"); exit(1); } return 0; } 這個例子是:利用信號傳遞數據,本程序接收數據 #include<stdio.h> #include<signal.h> void handler_sigint(int signo,siginfo_t *siginfo,void *pvoid) { printf("recv SIGINT,the data value is : %d\n",siginfo->si_int); } int main() { struct sigaction act; act.sa_sigaction=handler_sigint; act.sa_flags=SA_SIGINFO;//指定使用三參數的信號處理函數 sigaction(SIGINT,&act,NULL); while(1); www.2cto.com return 0; } 為了容易寫,我把發送數據的進程的名字名為b.c,接收進程的名字命為c.c 運行上面的程序結果如下所示: hfm@hfm-Lenovo-KL6B-KL6C-Z475:~$ ./c & [1] 4229 hfm@hfm-Lenovo-KL6B-KL6C-Z475:~$ ./b -s 2 -d 100 -p 4229 recv SIGINT,the data value is : 100 alarm函數:可以用來設置定時器,定時器超時將產生SIGALRM信號給調用進程,在shell下面可以看原型: unsigned int alarm(unsigned int seconds);參數seconds表示設定的秒數,經過seconds後,內核將給調用該函數的進程發送一個SIGALRM 信號,如果seconds為0將不再發送信號,最新一次調用該函數將取消之前一次的設定.alarm只設定為發送一次信號,如果要多次發送,就要對alarm進行多次調用.如果之前已經調用過alarm函數,則返回之前設置的定時器剩余時間,否則如果之前沒有設置過定時器,則返回0. 下面通過一個模擬網絡ping的例子來看一下alarm函數的用法: #include<stdio.h> #include<signal.h> #include<unistd.h> void send_ip() { printf("send a icmp packet\n"); } void recv_ip() { while(1); } void handler_sigint(int signo) { send_ip(); alarm(2); } int main() { signal(SIGALRM,handler_sigint); raise(SIGALRM);//觸發一個SIGALRM信號給本進程 recv_ip(); return 0; } 運行結果如下所示: hfm@hfm-Lenovo-KL6B-KL6C-Z475:~$ gcc -o m m.c hfm@hfm-Lenovo-KL6B-KL6C-Z475:~$ ./m send a icmp packet www.2cto.com send a icmp packet send a icmp packet send a icmp packet send a icmp packet send a icmp packet send a icmp packet send a icmp packet send a icmp packet .............. 我們可以看到每當2秒過後就用raise()函數出發一個SIGALRM信號給本進程,實現ping程序的定時發包功能.同時我們也可以想象一下網絡裡面的路由器是怎麼發送報文段的,它也是有時間規定的.我們可以通過設定alarm()函數中的值來設定.其實還有一個函數也是用來設定定時器的,它比alarm具有更多的功能,即setitimer()函數,它的原型如下: int setitimer(int which,const struct itimerval *value,struct itimerval *ovalue);有興趣的讀者可以下去自己看一下. void abort(void)函數用來向進程發送SIGABRT信號來終止進程,如果進程設置了SIGABRT被阻塞或忽略,abort()將覆蓋這種設置. 目前所掌握的就這些了,以後接觸到要再做研究,有待於更深層次的研究. 作者 hfm_honey
Copyright © Linux教程網 All Rights Reserved