Linux环境进程间通信---信号(下)
?一、信號生命周期
從信號發送到信號處理函數的執行完畢
對于一個完整的信號生命周期(從信號發送到相應的處理函數執行完畢)來說,可以分為三個重要的階段,這三個階段由四個重要事件來刻畫:信號誕生;信號在進程中注冊完畢;信號在進程中的注銷完畢;信號處理函數執行完畢。相鄰兩個事件的時間間隔構成信號生命周期的一個階段。
?
下面闡述四個事件的實際意義:
| struct sigpending pending: struct sigpending{struct sigqueue *head, **tail;sigset_t signal; }; |
第三個成員是進程中所有未決信號集,第一、第二個成員分別指向一個sigqueue類型的結構鏈(稱之為"未決信號信息鏈")的首尾,信息鏈中的每個sigqueue結構刻畫一個特定信號所攜帶的信息,并指向下一個sigqueue結構:
| struct sigqueue{struct sigqueue *next;siginfo_t info; } |
信號在進程中注冊指的就是信號值加入到進程的未決信號集中(sigpending結構的第二個成員sigset_t signal),并且信號所攜帶的信息被保留到未決信號信息鏈的某個sigqueue結構中。 只要信號在進程的未決信號集中,表明進程已經知道這些信號的存在,但還沒來得及處理,或者該信號被進程阻塞。
注:?
當一個實時信號發送給一個進程時,不管該信號是否已經在進程中注冊,都會被再注冊一次,因此,信號不會丟失,因此,實時信號又叫做"可靠信號"。這意味著同一個實時信號可以在同一個進程的未決信號信息鏈中占有多個sigqueue結構(進程每收到一個實時信號,都會為它分配一個結構來登記該信號信息,并把該結構添加在未決信號鏈尾,即所有誕生的實時信號都會在目標進程中注冊);?
當一個非實時信號發送給一個進程時,如果該信號已經在進程中注冊,則該信號將被丟棄,造成信號丟失。因此,非實時信號又叫做"不可靠信號"。這意味著同一個非實時信號在進程的未決信號信息鏈中,至多占有一個sigqueue結構(一個非實時信號誕生后,(1)、如果發現相同的信號已經在目標結構中注冊,則不再注冊,對于進程來說,相當于不知道本次信號發生,信號丟失;(2)、如果進程的未決信號中沒有相同信號,則在進程中注冊自己)。
進程在執行信號相應處理函數之前,首先要把信號在進程中注銷。
注:?
1)信號注冊與否,與發送信號的函數(如kill()或sigqueue()等)以及信號安裝函數(signal()及sigaction())無關,只與信號值有關(信號值小于SIGRTMIN的信號最多只注冊一次,信號值在SIGRTMIN及SIGRTMAX之間的信號,只要被進程接收到就被注冊)。?
2)在信號被注銷到相應的信號處理函數執行完畢這段時間內,如果進程又收到同一信號多次,則對實時信號來說,每一次都會在進程中注冊;而對于非實時信號來說,無論收到多少次信號,都會視為只收到一個信號,只在進程中注冊一次。
回頁首
二、信號編程注意事項
考慮到程序的可移植性,應該盡量采用POSIX信號函數,POSIX信號函數主要分為兩類:
- POSIX 1003.1信號函數: Kill()、sigaction()、sigaddset()、sigdelset()、sigemptyset()、sigfillset()、sigismember()、sigpending()、sigprocmask()、sigsuspend()。
- POSIX 1003.1b信號函數。POSIX 1003.1b在信號的實時性方面對POSIX 1003.1做了擴展,包括以下三個函數: sigqueue()、sigtimedwait()、sigwaitinfo()。 其中,sigqueue主要針對信號發送,而sigtimedwait及sigwaitinfo()主要用于取代sigsuspend()函數,后面有相應實例。
#include <signal.h> int sigwaitinfo(sigset_t *set, siginfo_t *info).
該函數與sigsuspend()類似,阻塞一個進程直到特定信號發生,但信號到來時不執行信號處理函數,而是返回信號值。因此為了避免執行相應的信號處理函數,必須在調用該函數前,使進程屏蔽掉set指向的信號,因此調用該函數的典型代碼是: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) {.. }
調用成功返回信號值,否則返回-1。sigtimedwait()功能相似,只不過增加了一個進程等待的時間。
為了增強程序的穩定性,在信號處理函數中應使用可重入函數。
信號處理程序中應當使用可再入(可重入)函數(注:所謂可重入函數是指一個可以被多個任務調用的過程,任務在調用時不必擔心數據是否會出錯)。因為進程在收到信號后,就將跳轉到信號處理函數去接著執行。如果信號處理函數中使用了不可重入函數,那么信號處理函數可能會修改原來進程中不應該被修改的數據,這樣進程從信號處理函數中返回接著執行時,可能會出現不可預料的后果。不可再入函數在信號處理函數中被視為不安全函數。
滿足下列條件的函數多數是不可再入的:(1)使用靜態的數據結構,如getlogin(),gmtime(),getgrgid(),getgrnam(),getpwuid()以及getpwnam()等等;(2)函數實現時,調用了malloc()或者free()函數;(3)實現時使用了標準I/O函數的。The Open Group視下列函數為可再入的:
_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()。
即使信號處理函數使用的都是"安全函數",同樣要注意進入處理函數時,首先要保存errno的值,結束時,再恢復原值。因為,信號處理過程中,errno值隨時可能被改變。另外,longjmp()以及siglongjmp()沒有被列為可再入函數,因為不能保證緊接著兩個函數的其它調用是安全的。
回頁首
三、深入淺出:信號應用實例
linux下的信號應用并沒有想象的那么恐怖,程序員所要做的最多只有三件事情:
實際上,對有些信號來說,只要安裝信號就足夠了(信號處理方式采用缺省或忽略)。其他可能要做的無非是與信號集相關的幾種操作。
實例一:信號發送及處理?
實現一個信號接收程序sigreceive(其中信號安裝由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); } |
?
說明,命令行參數為信號值,后臺運行sigreceive signo &,可獲得該進程的ID,假設為pid,然后再另一終端上運行kill -s signo pid驗證信號的發送接收及處理。同時,可驗證信號的排隊問題。?
注:可以用sigqueue實現一個命令行信號發送程序sigqueuesend,見?附錄1。
實例二:信號傳遞附加信息?
主要包括兩個實例:
| #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;//三參數信號處理函數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");sigqueue(pid,sig,mysigval);//向本進程發送信號,并傳遞附加信息} } void new_op(int signum,siginfo_t *info,void *myact)//三參數信號處理函數的實現 {int i;for(i=0;i<10;i++){printf("%c\n ",(*( (char*)((*info).si_ptr)+i)));}printf("handle signal %d over;",signum); } |
這個例子中,信號實現了附加信息的傳遞,信號究竟如何對這些信息進行處理則取決于具體的應用。
信號接收程序:
| #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); } |
信號發送程序:命令行第二個參數為信號值,第三個參數為接收進程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;//不代表具體含義,只用于說明問題if(sigqueue(pid,signum,mysigval)==-1)printf("send error\n");sleep(2); } |
注:實例2的兩個例子側重點在于用信號來傳遞信息,目前關于在linux下通過信號傳遞信息的實例非常少,倒是Unix下有一些,但傳遞的基本上都是關于傳遞一個整數,傳遞指針的我還沒看到。我一直沒有實現不同進程間的指針傳遞(實際上更有意義),也許在實現方法上存在問題吧,請實現者email我。
實例三:信號阻塞及信號集操作
| #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); } |
?
編譯該程序,并以后臺方式運行。在另一終端向該進程發送信號(運行kill -s 42 pid,SIGRTMIN+10為42),查看結果可以看出幾個關鍵函數的運行機制,信號集相關操作比較簡單。
注:在上面幾個實例中,使用了printf()函數,只是作為診斷工具,pringf()函數是不可重入的,不應在信號處理函數中使用。
回頁首
結束語:
系統地對linux信號機制進行分析、總結使我受益匪淺!感謝王小樂等網友的支持!?
Comments and suggestions are greatly welcome!
回頁首
附錄1:
用sigqueue實現的命令行信號發送程序sigqueuesend,命令行第二個參數是發送的信號值,第三個參數是接收該信號的進程ID,可以配合實例一使用:
| #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內核源代碼情景分析(上),毛德操、胡希明著,浙江大學出版社,當要驗證某個結論、想法時,最好的參考資料;
? - UNIX環境高級編程,作者:W.Richard Stevens,譯者:尤晉元等,機械工業出版社。對信號機制的發展過程闡述的比較詳細。
? - signal、sigaction、kill等手冊,最直接而可靠的參考資料。
? - http://www.linuxjournal.com/modules.php?op=modload&name=NS-help&file=man提供了許多系統調用、庫函數等的在線指南。?
? - http://www.opengroup.org/onlinepubs/007904975/可以在這里對許多關鍵函數(包括系統調用)進行查詢,非常好的一個網址。?
? - http://unix.org/whitepapers/reentrant.html對函數可重入進行了闡述。?
? - http://www.uccs.edu/~compsvcs/doc-cdrom/DOCS/HTML/APS33DTE/DOCU_006.HTM對實時信號給出了相當好的描述。?
?
關于作者
鄭彥興,國防科大攻讀博士學位。聯系方式:?mlinux@163.com.
原文地址:http://www.ibm.com/developerworks/cn/linux/l-ipc/part2/index2.html
轉載于:https://blog.51cto.com/2382965/706808
總結
以上是生活随笔為你收集整理的Linux环境进程间通信---信号(下)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 纯营养加铁米粉好不好
- 下一篇: 倾城女儿腰草本果蔬压片糖果怎么吃