信號是軟件中斷,是在軟件層次上對中斷機制的一種模擬,在原理上,一個進程收到一個信號與處理器收到一個中斷請求可以說是一樣的。信號是異步的,一個進程不必通過任何操作來等待信號的到達,事實上,進程也不知道信號到底什么時候到達。
信號是進程間通信機制中唯一的異步通信機制,可以看作是異步通知,通知接收信號的進程有哪些事情發生了。信號機制經過POSIX實時擴展后,功能更加強大,除了基本通知功能外,還可以傳遞附加信息。
1、可靠信號與不可靠信號
"不可靠信號"
Linux信號機制基本上是從Unix系統中繼承過來的。早期Unix系統中的信號機制比較簡單和原始,后來在實踐中暴露出一些問題,因此,把那些建立在早期機制上的信號叫做"不可靠信號",信號值小於SIGRTMIN(Red hat 7.2中,SIGRTMIN=32,SIGRTMAX=63)的信號都是不可靠信號。
這就是"不可靠信號"的來源。它的主要問題是:
進程每次處理信號后,就將對信號的響應設置為默認動作。在某些情況下,將導致對信號的錯誤處理;因此,用戶如果不希望這樣的操作,那么就要在信號處理函數結尾再一次調用signal(),重新安裝該信號。
信號可能丟失,后面將對此詳細闡述。 如果在進程對某個信號進行處理時,這個信號發生多次,對后到來的這類信號不排隊,那么僅傳送該信號一次,即發生了信號丟失。
因此,早期unix下的不可靠信號主要指的是進程可能對信號做出錯誤的反應以及信號可能丟失。
Linux支持不可靠信號,但是對不可靠信號機制做了改進:在調用完信號處理函數后,不必重新調用該信號的安裝函數(信號安裝函數是在可靠機制上的實現)。因此,Linux下的不可靠信號問題主要指的是信號可能丟失。
"可靠信號"
隨着時間的發展,實踐證明了有必要對信號的原始機制加以改進和擴充。所以,后來出現的各種Unix版本分別在這方面進行了研究,力圖實現"可靠信號"。由於原來定義的信號已有許多應用,不好再做改動,最終只好又新增加了一些信號,並在一開始就把它們定義為可靠信號,這些信號支持排隊,不會丟失。同時,信號的發送和安裝也出現了新版本:信號發送函數sigqueue()及信號安裝函數sigaction()。POSIX.4對可靠信號機制做了標准化。但是,POSIX只對可靠信號機制應具有的功能以及信號機制的對外接口做了標准化,對信號機制的實現沒有作具體的規定。
信號值位於SIGRTMIN和SIGRTMAX之間的信號都是可靠信號,可靠信號克服了信號可能丟失的問題。Linux在支持新版本的信號安裝函數sigation()以及信號發送函數sigqueue()的同時,仍然支持早期的signal()信號安裝函數,支持信號發送函數kill()。
注:不要有這樣的誤解:由sigqueue()發送、sigaction安裝的信號就是可靠的。事實上,可靠信號是指后來添加的新信號(信號值位於SIGRTMIN及SIGRTMAX之間);不可靠信號是信號值小於SIGRTMIN的信號。信號的可靠與不可靠只與信號值有關,與信號的發送及安裝函數無關。目前linux中的signal()是通過sigation()函數實現的,因此,即使通過signal()安裝的信號,在信號處理函數的結尾也不必再調用一次信號安裝函數。同時,由signal()安裝的實時信號支持排隊,同樣不會丟失。
對於目前linux的兩個信號安裝函數:signal()及sigaction()來說,它們都不能把SIGRTMIN以前的信號變成可靠信號(都不支持排隊,仍有可能丟失,仍然是不可靠信號),而且對SIGRTMIN以后的信號都支持排隊。這兩個函數的最大區別在於,經過sigaction安裝的信號都能傳遞信息給信號處理函數(對所有信號這一點都成立),而經過signal安裝的信號卻不能向信號處理函數傳遞信息。對於信號發送函數來說也是一樣的。
接下來,用兩個例程來說明可靠信號和不可靠信號的排隊問題:
一個發送進程,發送三次不可靠信號SIGINT,發送三次可靠信號SIGRTMIN(34); 再發送一次SIGUSR1信號。接收程序中對SIGINT和SIGRTMIN阻塞,然后在接受SIGUSR1的信號處理函數中再解除對信號SIGINT和SIGRTMIN的阻塞,查看排隊丟失情況。
1 #include<unistd.h> 2 #include<sys/types.h> 3 #include<sys/stat.h> 4 #include<fcntl.h> 5 #include<stdlib.h> 6 #include<stdio.h> 7 #include<errno.h> 8 #include<string.h> 9 10 #include<signal.h> 11 #define ERR_EXIT(m)\ 12 do\ 13 {\ 14 perror(m);\ 15 exit(EXIT_FAILURE);\ 16 }while(0) //宏要求一條語句 17 int main(int argc,char*argv[]) 18 { 19 if(argc!=2){ 20 fprintf(stderr,"Usage %s pid\n",argv[0]); 21 exit(EXIT_FAILURE); 22 } 23 pid_t pid=atoi(argv[1]);//recv進程運行時的進程號 24 union sigval v; 25 sigqueue(pid,SIGINT,v); 26 sigqueue(pid,SIGINT,v); 27 sigqueue(pid,SIGINT,v); 28 sigqueue(pid,SIGRTMIN,v); 29 sigqueue(pid,SIGRTMIN,v); 30 sigqueue(pid,SIGRTMIN,v); 31 sleep(3); 32 kill(pid,SIGUSR1); 33 return 0; 34 }
下一個是接收進程,接收進程的進程號發給上面的sigqueue函數,接受進程中先對SIGINT,SIGRTMIN阻塞,然后接收處理SIGUSR1信號,在處理程序中解除對上面兩個信號的阻塞。最后只收到一個SIGINT信號,收到三次SIGRTMIN信號(支持排隊)。
#include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<stdlib.h> #include<stdio.h> #include<errno.h> #include<string.h> #include<signal.h> #define ERR_EXIT(m)\ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0) //宏要求一條語句 void handler(int sig); int main(int argc,char*argv[]) { struct sigaction act; act.sa_handler=handler;//不能使用sa_handler函數了,要使用 void (*sa_sigaction) (int,siginfo_t*,void*) sigemptyset(&act.sa_mask);//針對信號處理程序過程中阻塞 act.sa_flags=0; sigset_t s; sigaddset(&s,SIGINT); sigaddset(&s,SIGRTMIN); sigprocmask(SIG_BLOCK,&s,NULL);//針對進程對信號阻塞。 if(sigaction(SIGINT,&act,NULL)<0) ERR_EXIT("sigaction error\n"); if(sigaction(SIGRTMIN,&act,NULL)<0) ERR_EXIT("sigaction error\n"); if(sigaction(SIGUSR1,&act,NULL)<0) ERR_EXIT("sigaction error\n"); for(;;) pause(); return 0; } void handler(int sig) { if(sig==SIGINT||sig==SIGRTMIN) printf("receive a sig=%d\n",sig);//接收到三個SIGRTMIN,只接收到一個SIGINT else if(sig==SIGUSR1) { sigset_t s; sigemptyset(&s); sigaddset(&s,SIGINT); sigaddset(&s,SIGRTMIN); sigprocmask(SIG_UNBLOCK,&s,NULL); } }