Linux环境进程间通信---信号(下)
?一、信號(hào)生命周期
從信號(hào)發(fā)送到信號(hào)處理函數(shù)的執(zhí)行完畢
對(duì)于一個(gè)完整的信號(hào)生命周期(從信號(hào)發(fā)送到相應(yīng)的處理函數(shù)執(zhí)行完畢)來(lái)說(shuō),可以分為三個(gè)重要的階段,這三個(gè)階段由四個(gè)重要事件來(lái)刻畫:信號(hào)誕生;信號(hào)在進(jìn)程中注冊(cè)完畢;信號(hào)在進(jìn)程中的注銷完畢;信號(hào)處理函數(shù)執(zhí)行完畢。相鄰兩個(gè)事件的時(shí)間間隔構(gòu)成信號(hào)生命周期的一個(gè)階段。
?
下面闡述四個(gè)事件的實(shí)際意義:
| struct sigpending pending: struct sigpending{struct sigqueue *head, **tail;sigset_t signal; }; |
第三個(gè)成員是進(jìn)程中所有未決信號(hào)集,第一、第二個(gè)成員分別指向一個(gè)sigqueue類型的結(jié)構(gòu)鏈(稱之為"未決信號(hào)信息鏈")的首尾,信息鏈中的每個(gè)sigqueue結(jié)構(gòu)刻畫一個(gè)特定信號(hào)所攜帶的信息,并指向下一個(gè)sigqueue結(jié)構(gòu):
| struct sigqueue{struct sigqueue *next;siginfo_t info; } |
信號(hào)在進(jìn)程中注冊(cè)指的就是信號(hào)值加入到進(jìn)程的未決信號(hào)集中(sigpending結(jié)構(gòu)的第二個(gè)成員sigset_t signal),并且信號(hào)所攜帶的信息被保留到未決信號(hào)信息鏈的某個(gè)sigqueue結(jié)構(gòu)中。 只要信號(hào)在進(jìn)程的未決信號(hào)集中,表明進(jìn)程已經(jīng)知道這些信號(hào)的存在,但還沒(méi)來(lái)得及處理,或者該信號(hào)被進(jìn)程阻塞。
注:?
當(dāng)一個(gè)實(shí)時(shí)信號(hào)發(fā)送給一個(gè)進(jìn)程時(shí),不管該信號(hào)是否已經(jīng)在進(jìn)程中注冊(cè),都會(huì)被再注冊(cè)一次,因此,信號(hào)不會(huì)丟失,因此,實(shí)時(shí)信號(hào)又叫做"可靠信號(hào)"。這意味著同一個(gè)實(shí)時(shí)信號(hào)可以在同一個(gè)進(jìn)程的未決信號(hào)信息鏈中占有多個(gè)sigqueue結(jié)構(gòu)(進(jìn)程每收到一個(gè)實(shí)時(shí)信號(hào),都會(huì)為它分配一個(gè)結(jié)構(gòu)來(lái)登記該信號(hào)信息,并把該結(jié)構(gòu)添加在未決信號(hào)鏈尾,即所有誕生的實(shí)時(shí)信號(hào)都會(huì)在目標(biāo)進(jìn)程中注冊(cè));?
當(dāng)一個(gè)非實(shí)時(shí)信號(hào)發(fā)送給一個(gè)進(jìn)程時(shí),如果該信號(hào)已經(jīng)在進(jìn)程中注冊(cè),則該信號(hào)將被丟棄,造成信號(hào)丟失。因此,非實(shí)時(shí)信號(hào)又叫做"不可靠信號(hào)"。這意味著同一個(gè)非實(shí)時(shí)信號(hào)在進(jìn)程的未決信號(hào)信息鏈中,至多占有一個(gè)sigqueue結(jié)構(gòu)(一個(gè)非實(shí)時(shí)信號(hào)誕生后,(1)、如果發(fā)現(xiàn)相同的信號(hào)已經(jīng)在目標(biāo)結(jié)構(gòu)中注冊(cè),則不再注冊(cè),對(duì)于進(jìn)程來(lái)說(shuō),相當(dāng)于不知道本次信號(hào)發(fā)生,信號(hào)丟失;(2)、如果進(jìn)程的未決信號(hào)中沒(méi)有相同信號(hào),則在進(jìn)程中注冊(cè)自己)。
進(jìn)程在執(zhí)行信號(hào)相應(yīng)處理函數(shù)之前,首先要把信號(hào)在進(jìn)程中注銷。
注:?
1)信號(hào)注冊(cè)與否,與發(fā)送信號(hào)的函數(shù)(如kill()或sigqueue()等)以及信號(hào)安裝函數(shù)(signal()及sigaction())無(wú)關(guān),只與信號(hào)值有關(guān)(信號(hào)值小于SIGRTMIN的信號(hào)最多只注冊(cè)一次,信號(hào)值在SIGRTMIN及SIGRTMAX之間的信號(hào),只要被進(jìn)程接收到就被注冊(cè))。?
2)在信號(hào)被注銷到相應(yīng)的信號(hào)處理函數(shù)執(zhí)行完畢這段時(shí)間內(nèi),如果進(jìn)程又收到同一信號(hào)多次,則對(duì)實(shí)時(shí)信號(hào)來(lái)說(shuō),每一次都會(huì)在進(jìn)程中注冊(cè);而對(duì)于非實(shí)時(shí)信號(hào)來(lái)說(shuō),無(wú)論收到多少次信號(hào),都會(huì)視為只收到一個(gè)信號(hào),只在進(jìn)程中注冊(cè)一次。
回頁(yè)首
二、信號(hào)編程注意事項(xiàng)
考慮到程序的可移植性,應(yīng)該盡量采用POSIX信號(hào)函數(shù),POSIX信號(hào)函數(shù)主要分為兩類:
- POSIX 1003.1信號(hào)函數(shù): Kill()、sigaction()、sigaddset()、sigdelset()、sigemptyset()、sigfillset()、sigismember()、sigpending()、sigprocmask()、sigsuspend()。
- POSIX 1003.1b信號(hào)函數(shù)。POSIX 1003.1b在信號(hào)的實(shí)時(shí)性方面對(duì)POSIX 1003.1做了擴(kuò)展,包括以下三個(gè)函數(shù): sigqueue()、sigtimedwait()、sigwaitinfo()。 其中,sigqueue主要針對(duì)信號(hào)發(fā)送,而sigtimedwait及sigwaitinfo()主要用于取代sigsuspend()函數(shù),后面有相應(yīng)實(shí)例。
#include <signal.h> int sigwaitinfo(sigset_t *set, siginfo_t *info).
該函數(shù)與sigsuspend()類似,阻塞一個(gè)進(jìn)程直到特定信號(hào)發(fā)生,但信號(hào)到來(lái)時(shí)不執(zhí)行信號(hào)處理函數(shù),而是返回信號(hào)值。因此為了避免執(zhí)行相應(yīng)的信號(hào)處理函數(shù),必須在調(diào)用該函數(shù)前,使進(jìn)程屏蔽掉set指向的信號(hào),因此調(diào)用該函數(shù)的典型代碼是:sigset_t newmask; int rcvd_sig; siginfo_t info; sigemptyset(&newmask); sigaddset(&newmask, SIGRTMIN); sigprocmask(SIG_BLOCK, &newmask, NULL); rcvd_sig = sigwaitinfo(&newmask, &info) if (rcvd_sig == -1) {.. }
調(diào)用成功返回信號(hào)值,否則返回-1。sigtimedwait()功能相似,只不過(guò)增加了一個(gè)進(jìn)程等待的時(shí)間。
為了增強(qiáng)程序的穩(wěn)定性,在信號(hào)處理函數(shù)中應(yīng)使用可重入函數(shù)。
信號(hào)處理程序中應(yīng)當(dāng)使用可再入(可重入)函數(shù)(注:所謂可重入函數(shù)是指一個(gè)可以被多個(gè)任務(wù)調(diào)用的過(guò)程,任務(wù)在調(diào)用時(shí)不必?fù)?dān)心數(shù)據(jù)是否會(huì)出錯(cuò))。因?yàn)檫M(jìn)程在收到信號(hào)后,就將跳轉(zhuǎn)到信號(hào)處理函數(shù)去接著執(zhí)行。如果信號(hào)處理函數(shù)中使用了不可重入函數(shù),那么信號(hào)處理函數(shù)可能會(huì)修改原來(lái)進(jìn)程中不應(yīng)該被修改的數(shù)據(jù),這樣進(jìn)程從信號(hào)處理函數(shù)中返回接著執(zhí)行時(shí),可能會(huì)出現(xiàn)不可預(yù)料的后果。不可再入函數(shù)在信號(hào)處理函數(shù)中被視為不安全函數(shù)。
滿足下列條件的函數(shù)多數(shù)是不可再入的:(1)使用靜態(tài)的數(shù)據(jù)結(jié)構(gòu),如getlogin(),gmtime(),getgrgid(),getgrnam(),getpwuid()以及getpwnam()等等;(2)函數(shù)實(shí)現(xiàn)時(shí),調(diào)用了malloc()或者free()函數(shù);(3)實(shí)現(xiàn)時(shí)使用了標(biāo)準(zhǔn)I/O函數(shù)的。The Open Group視下列函數(shù)為可再入的:
_exit()、access()、alarm()、cfgetispeed()、cfgetospeed()、cfsetispeed()、cfsetospeed()、chdir()、chmod()、chown() 、close()、creat()、dup()、dup2()、execle()、execve()、fcntl()、fork()、fpathconf()、fstat()、fsync()、getegid()、 geteuid()、getgid()、getgroups()、getpgrp()、getpid()、getppid()、getuid()、kill()、link()、lseek()、mkdir()、mkfifo()、 open()、pathconf()、pause()、pipe()、raise()、read()、rename()、rmdir()、setgid()、setpgid()、setsid()、setuid()、 sigaction()、sigaddset()、sigdelset()、sigemptyset()、sigfillset()、sigismember()、signal()、sigpending()、sigprocmask()、sigsuspend()、sleep()、stat()、sysconf()、tcdrain()、tcflow()、tcflush()、tcgetattr()、tcgetpgrp()、tcsendbreak()、tcsetattr()、tcsetpgrp()、time()、times()、 umask()、uname()、unlink()、utime()、wait()、waitpid()、write()。
即使信號(hào)處理函數(shù)使用的都是"安全函數(shù)",同樣要注意進(jìn)入處理函數(shù)時(shí),首先要保存errno的值,結(jié)束時(shí),再恢復(fù)原值。因?yàn)?#xff0c;信號(hào)處理過(guò)程中,errno值隨時(shí)可能被改變。另外,longjmp()以及siglongjmp()沒(méi)有被列為可再入函數(shù),因?yàn)椴荒鼙WC緊接著兩個(gè)函數(shù)的其它調(diào)用是安全的。
回頁(yè)首
三、深入淺出:信號(hào)應(yīng)用實(shí)例
linux下的信號(hào)應(yīng)用并沒(méi)有想象的那么恐怖,程序員所要做的最多只有三件事情:
實(shí)際上,對(duì)有些信號(hào)來(lái)說(shuō),只要安裝信號(hào)就足夠了(信號(hào)處理方式采用缺省或忽略)。其他可能要做的無(wú)非是與信號(hào)集相關(guān)的幾種操作。
實(shí)例一:信號(hào)發(fā)送及處理?
實(shí)現(xiàn)一個(gè)信號(hào)接收程序sigreceive(其中信號(hào)安裝由sigaction())。
| #include <signal.h> #include <sys/types.h> #include <unistd.h> void new_op(int,siginfo_t*,void*); int main(int argc,char**argv) {struct sigaction act; int sig;sig=atoi(argv[1]);sigemptyset(&act.sa_mask);act.sa_flags=SA_SIGINFO;act.sa_sigaction=new_op;if(sigaction(sig,&act,NULL) < 0){printf("install sigal error\n");}while(1){sleep(2);printf("wait for the signal\n");} } void new_op(int signum,siginfo_t *info,void *myact) {printf("receive signal %d", signum);sleep(5); } |
?
說(shuō)明,命令行參數(shù)為信號(hào)值,后臺(tái)運(yùn)行sigreceive signo &,可獲得該進(jìn)程的ID,假設(shè)為pid,然后再另一終端上運(yùn)行kill -s signo pid驗(yàn)證信號(hào)的發(fā)送接收及處理。同時(shí),可驗(yàn)證信號(hào)的排隊(duì)問(wèn)題。?
注:可以用sigqueue實(shí)現(xiàn)一個(gè)命令行信號(hào)發(fā)送程序sigqueuesend,見(jiàn)?附錄1。
實(shí)例二:信號(hào)傳遞附加信息?
主要包括兩個(gè)實(shí)例:
| #include <signal.h> #include <sys/types.h> #include <unistd.h> void new_op(int,siginfo_t*,void*); int main(int argc,char**argv) {struct sigaction act; union sigval mysigval;int i;int sig;pid_t pid; char data[10];memset(data,0,sizeof(data));for(i=0;i < 5;i++)data[i]='2';mysigval.sival_ptr=data;sig=atoi(argv[1]);pid=getpid();sigemptyset(&act.sa_mask);act.sa_sigaction=new_op;//三參數(shù)信號(hào)處理函數(shù)act.sa_flags=SA_SIGINFO;//信息傳遞開關(guān)if(sigaction(sig,&act,NULL) < 0){printf("install sigal error\n");}while(1){sleep(2);printf("wait for the signal\n");sigqueue(pid,sig,mysigval);//向本進(jìn)程發(fā)送信號(hào),并傳遞附加信息} } void new_op(int signum,siginfo_t *info,void *myact)//三參數(shù)信號(hào)處理函數(shù)的實(shí)現(xiàn) {int i;for(i=0;i<10;i++){printf("%c\n ",(*( (char*)((*info).si_ptr)+i)));}printf("handle signal %d over;",signum); } |
這個(gè)例子中,信號(hào)實(shí)現(xiàn)了附加信息的傳遞,信號(hào)究竟如何對(duì)這些信息進(jìn)行處理則取決于具體的應(yīng)用。
信號(hào)接收程序:
| #include <signal.h> #include <sys/types.h> #include <unistd.h> void new_op(int,siginfo_t*,void*); int main(int argc,char**argv) {struct sigaction act;int sig;pid_t pid; pid=getpid();sig=atoi(argv[1]); sigemptyset(&act.sa_mask);act.sa_sigaction=new_op;act.sa_flags=SA_SIGINFO;if(sigaction(sig,&act,NULL)<0){printf("install sigal error\n");}while(1){sleep(2);printf("wait for the signal\n");} } void new_op(int signum,siginfo_t *info,void *myact) {printf("the int value is %d \n",info->si_int); } |
信號(hào)發(fā)送程序:命令行第二個(gè)參數(shù)為信號(hào)值,第三個(gè)參數(shù)為接收進(jìn)程ID。
| #include <signal.h> #include <sys/time.h> #include <unistd.h> #include <sys/types.h> main(int argc,char**argv) {pid_t pid;int signum;union sigval mysigval;signum=atoi(argv[1]);pid=(pid_t)atoi(argv[2]);mysigval.sival_int=8;//不代表具體含義,只用于說(shuō)明問(wèn)題if(sigqueue(pid,signum,mysigval)==-1)printf("send error\n");sleep(2); } |
注:實(shí)例2的兩個(gè)例子側(cè)重點(diǎn)在于用信號(hào)來(lái)傳遞信息,目前關(guān)于在linux下通過(guò)信號(hào)傳遞信息的實(shí)例非常少,倒是Unix下有一些,但傳遞的基本上都是關(guān)于傳遞一個(gè)整數(shù),傳遞指針的我還沒(méi)看到。我一直沒(méi)有實(shí)現(xiàn)不同進(jìn)程間的指針傳遞(實(shí)際上更有意義),也許在實(shí)現(xiàn)方法上存在問(wèn)題吧,請(qǐng)實(shí)現(xiàn)者email我。
實(shí)例三:信號(hào)阻塞及信號(hào)集操作
| #include "signal.h" #include "unistd.h" static void my_op(int); main() {sigset_t new_mask,old_mask,pending_mask;struct sigaction act;sigemptyset(&act.sa_mask);act.sa_flags=SA_SIGINFO;act.sa_sigaction=(void*)my_op;if(sigaction(SIGRTMIN+10,&act,NULL))printf("install signal SIGRTMIN+10 error\n");sigemptyset(&new_mask);sigaddset(&new_mask,SIGRTMIN+10);if(sigprocmask(SIG_BLOCK, &new_mask,&old_mask))printf("block signal SIGRTMIN+10 error\n");sleep(10); printf("now begin to get pending mask and unblock SIGRTMIN+10\n");if(sigpending(&pending_mask)<0)printf("get pending mask error\n");if(sigismember(&pending_mask,SIGRTMIN+10))printf("signal SIGRTMIN+10 is pending\n");if(sigprocmask(SIG_SETMASK,&old_mask,NULL)<0)printf("unblock signal error\n");printf("signal unblocked\n");sleep(10); } static void my_op(int signum) {printf("receive signal %d \n",signum); } |
?
編譯該程序,并以后臺(tái)方式運(yùn)行。在另一終端向該進(jìn)程發(fā)送信號(hào)(運(yùn)行kill -s 42 pid,SIGRTMIN+10為42),查看結(jié)果可以看出幾個(gè)關(guān)鍵函數(shù)的運(yùn)行機(jī)制,信號(hào)集相關(guān)操作比較簡(jiǎn)單。
注:在上面幾個(gè)實(shí)例中,使用了printf()函數(shù),只是作為診斷工具,pringf()函數(shù)是不可重入的,不應(yīng)在信號(hào)處理函數(shù)中使用。
回頁(yè)首
結(jié)束語(yǔ):
系統(tǒng)地對(duì)linux信號(hào)機(jī)制進(jìn)行分析、總結(jié)使我受益匪淺!感謝王小樂(lè)等網(wǎng)友的支持!?
Comments and suggestions are greatly welcome!
回頁(yè)首
附錄1:
用sigqueue實(shí)現(xiàn)的命令行信號(hào)發(fā)送程序sigqueuesend,命令行第二個(gè)參數(shù)是發(fā)送的信號(hào)值,第三個(gè)參數(shù)是接收該信號(hào)的進(jìn)程ID,可以配合實(shí)例一使用:
| #include <signal.h> #include <sys/types.h> #include <unistd.h> int main(int argc,char**argv) {pid_t pid;int sig;sig=atoi(argv[1]);pid=atoi(argv[2]);sigqueue(pid,sig,NULL);sleep(2); } |
?
參考資料
- linux內(nèi)核源代碼情景分析(上),毛德操、胡希明著,浙江大學(xué)出版社,當(dāng)要驗(yàn)證某個(gè)結(jié)論、想法時(shí),最好的參考資料;
? - UNIX環(huán)境高級(jí)編程,作者:W.Richard Stevens,譯者:尤晉元等,機(jī)械工業(yè)出版社。對(duì)信號(hào)機(jī)制的發(fā)展過(guò)程闡述的比較詳細(xì)。
? - signal、sigaction、kill等手冊(cè),最直接而可靠的參考資料。
? - http://www.linuxjournal.com/modules.php?op=modload&name=NS-help&file=man提供了許多系統(tǒng)調(diào)用、庫(kù)函數(shù)等的在線指南。?
? - http://www.opengroup.org/onlinepubs/007904975/可以在這里對(duì)許多關(guān)鍵函數(shù)(包括系統(tǒng)調(diào)用)進(jìn)行查詢,非常好的一個(gè)網(wǎng)址。?
? - http://unix.org/whitepapers/reentrant.html對(duì)函數(shù)可重入進(jìn)行了闡述。?
? - http://www.uccs.edu/~compsvcs/doc-cdrom/DOCS/HTML/APS33DTE/DOCU_006.HTM對(duì)實(shí)時(shí)信號(hào)給出了相當(dāng)好的描述。?
?
關(guān)于作者
鄭彥興,國(guó)防科大攻讀博士學(xué)位。聯(lián)系方式:?mlinux@163.com.
原文地址:http://www.ibm.com/developerworks/cn/linux/l-ipc/part2/index2.html
轉(zhuǎn)載于:https://blog.51cto.com/2382965/706808
總結(jié)
以上是生活随笔為你收集整理的Linux环境进程间通信---信号(下)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 纯营养加铁米粉好不好
- 下一篇: 倾城女儿腰草本果蔬压片糖果怎么吃