linux下進程間通訊的幾種主要手段:php
本文講述進程間通訊方法——信號html
信號本質編程
信號是在軟件層次上對中斷機制的一種模擬,在原理上,一個進程收到一個信號與處理器收到一箇中斷請求能夠說是同樣的。信號是異步的,一個進程沒必要經過任何操做來等待信號的到達,事實上,進程也不知道信號到底何時到達。session
信號是進程間通訊機制中惟一的異步通訊機制,能夠看做是異步通知,通知接收信號的進程有哪些事情發生了。信號機制通過POSIX實時擴展後,功能更增強大,除了基本通知功能外,還能夠傳遞附加信息。數據結構
信號來源異步
信號事件的發生有兩個來源:硬件來源(好比咱們按下了鍵盤或者其它硬件故障);軟件來源,最經常使用發送信號的系統函數是kill, raise, alarm和setitimer以及sigqueue函數,軟件來源還包括一些非法運算等操做。函數
能夠從兩個不一樣的分類角度對信號進行分類:(1)可靠性方面:可靠信號與不可靠信號;(2)與時間的關係上:實時信號與非實時信號。在《Linux環境進程間通訊(一):管道及有名管道》的附1中列出了系統所支持的全部信號。spa
"不可靠信號"
Linux信號機制基本上是從Unix系統中繼承過來的。早期Unix系統中的信號機制比較簡單和原始,後來在實踐中暴露出一些問題,所以,把那些創建在早期機制上的信號叫作"不可靠信號",信號值小於SIGRTMIN(Red hat 7.2中,SIGRTMIN=32,SIGRTMAX=63)的信號都是不可靠信號。這就是"不可靠信號"的來源。它的主要問題是:
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安裝的信號卻不能向信號處理函數傳遞信息。對於信號發送函數來講也是同樣的。
早期Unix系統只定義了32種信號,Ret hat7.2支持64種信號,編號0-63(SIGRTMIN=31,SIGRTMAX=63),未來可能進一步增長,這須要獲得內核的支持。前32種信號已經有了預約義值,每一個信號有了肯定的用途及含義,而且每種信號都有各自的缺省動做。如按鍵盤的CTRL ^C時,會產生SIGINT信號,對該信號的默認反應就是進程終止。後32個信號表示實時信號,等同於前面闡述的可靠信號。這保證了發送的多個實時信號都被接收。實時信號是POSIX標準的一部分,可用於應用進程。
非實時信號都不支持排隊,都是不可靠信號;實時信號都支持排隊,都是可靠信號。
進程能夠經過三種方式來響應一個信號:(1)忽略信號,即對信號不作任何處理,其中,有兩個信號不能忽略:SIGKILL及SIGSTOP;(2)捕捉信號。定義信號處理函數,當信號發生時,執行相應的處理函數;(3)執行缺省操做,Linux對每種信號都規定了默認操做,詳細狀況請參考[2]以及其它資料。注意,進程對實時信號的缺省反應是進程終止。
Linux究竟採用上述三種方式的哪個來響應信號,取決於傳遞給相應API函數的參數。
發送信號的主要函數有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。
一、kill()
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid,int signo)
參數pid的值 | 信號的接收進程 |
pid>0 | 進程ID爲pid的進程 |
pid=0 | 同一個進程組的進程 |
pid<0 pid!=-1 | 進程組ID爲 -pid的全部進程 |
pid=-1 | 除發送進程自身外,全部進程ID大於1的進程 |
Sinno是信號值,當爲0時(即空信號),實際不發送任何信號,但照常進行錯誤檢查,所以,可用於檢查目標進程是否存在,以及當前進程是否具備向目標發送信號的權限(root權限的進程能夠向任何進程發送信號,非root權限的進程只能向屬於同一個session或者同一個用戶的進程發送信號)。
Kill()最經常使用於pid>0時的信號發送,調用成功返回 0; 不然,返回 -1。 注:對於pid<0時的狀況,對於哪些進程將接受信號,各類版本說法不一,其實很簡單,參閱內核源碼kernal/signal.c便可,上表中的規則是參考red hat 7.2。
二、raise()
#include <signal.h>
int raise(int signo)
向進程自己發送信號,參數爲即將發送的信號值。調用成功返回 0;不然,返回 -1。
三、sigqueue()
#include <sys/types.h>
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval val)
調用成功返回 0;不然,返回 -1。
sigqueue()是比較新的發送信號系統調用,主要是針對實時信號提出的(固然也支持前32種),支持信號帶有參數,與函數sigaction()配合使用。
sigqueue的第一個參數是指定接收信號的進程ID,第二個參數肯定即將發送的信號,第三個參數是一個聯合數據結構union sigval,指定了信號傳遞的參數,即一般所說的4字節值。
typedef union sigval { int sival_int; void *sival_ptr; }sigval_t; |
sigqueue()比kill()傳遞了更多的附加信息,但sigqueue()只能向一個進程發送信號,而不能發送信號給一個進程組。若是signo=0,將會執行錯誤檢查,但實際上不發送任何信號,0值信號可用於檢查pid的有效性以及當前進程是否有權限向目標進程發送信號。
在調用sigqueue時,sigval_t指定的信息會拷貝到3參數信號處理函數(3參數信號處理函數指的是信號處理函數由sigaction安裝,並設定了sa_sigaction指針,稍後將闡述)的siginfo_t結構中,這樣信號處理函數就能夠處理這些信息了。因爲sigqueue系統調用支持發送帶參數信號,因此比kill()系統調用的功能要靈活和強大得多。
注:sigqueue()發送非實時信號時,第三個參數包含的信息仍然可以傳遞給信號處理函數; sigqueue()發送非實時信號時,仍然不支持排隊,即在信號處理函數執行過程當中到來的全部相同信號,都被合併爲一個信號。
四、alarm()
#include <unistd.h>
unsigned int alarm(unsigned int seconds)
專門爲SIGALRM信號而設,在指定的時間seconds秒後,將向進程自己發送SIGALRM信號,又稱爲鬧鐘時間。進程調用alarm後,任何之前的alarm()調用都將無效。若是參數seconds爲零,那麼進程內將再也不包含任何鬧鐘時間。
返回值,若是調用alarm()前,進程中已經設置了鬧鐘時間,則返回上一個鬧鐘時間的剩餘時間,不然返回0。
五、setitimer()
#include <sys/time.h>
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue));
setitimer()比alarm功能強大,支持3種類型的定時器:
Setitimer()第一個參數which指定定時器類型(上面三種之一);第二個參數是結構itimerval的一個實例,結構itimerval形式見附錄1。第三個參數可不作處理。
Setitimer()調用成功返回0,不然返回-1。
六、abort()
#include <stdlib.h>
void abort(void);
向進程發送SIGABORT信號,默認狀況下進程會異常退出,固然可定義本身的信號處理函數。即便SIGABORT被進程設置爲阻塞信號,調用abort()後,SIGABORT仍然能被進程接收。該函數無返回值。
若是進程要處理某一信號,那麼就要在進程中安裝該信號。安裝信號主要用來肯定信號值及進程針對該信號值的動做之間的映射關係,即進程將要處理哪一個信號;該信號被傳遞給進程時,將執行何種操做。
linux主要有兩個函數實現信號的安裝:signal()、sigaction()。其中signal()在可靠信號系統調用的基礎上實現, 是庫函數。它只有兩個參數,不支持信號傳遞信息,主要是用於前32種非實時信號的安裝;而sigaction()是較新的函數(由兩個系統調用實現:sys_signal以及sys_rt_sigaction),有三個參數,支持信號傳遞信息,主要用來與 sigqueue() 系統調用配合使用,固然,sigaction()一樣支持非實時信號的安裝。sigaction()優於signal()主要體如今支持信號帶有參數。
一、signal()
#include <signal.h>
void (*signal(int signum, void (*handler))(int)))(int);
若是該函數原型不容易理解的話,能夠參考下面的分解方式來理解:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler));
第一個參數指定信號的值,第二個參數指定針對前面信號值的處理,能夠忽略該信號(參數設爲SIG_IGN);能夠採用系統默認方式處理信號(參數設爲SIG_DFL);也能夠本身實現處理方式(參數指定一個函數地址)。
若是signal()調用成功,返回最後一次爲安裝信號signum而調用signal()時的handler值;失敗則返回SIG_ERR。
二、sigaction()
#include <signal.h>
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));
sigaction函數用於改變進程接收到特定信號後的行爲。該函數的第一個參數爲信號的值,能夠爲除SIGKILL及SIGSTOP外的任何一個特定有效的信號(爲這兩個信號定義本身的處理函數,將致使信號安裝錯誤)。第二個參數是指向結構sigaction的一個實例的指針,在結構sigaction的實例中,指定了對特定信號的處理,能夠爲空,進程會以缺省方式對信號處理;第三個參數oldact指向的對象用來保存原來對相應信號的處理,可指定oldact爲NULL。若是把第2、第三個參數都設爲NULL,那麼該函數可用於檢查信號的有效性。
第二個參數最爲重要,其中包含了對指定信號的處理、信號所傳遞的信息、信號處理函數執行過程當中應屏蔽掉哪些函數等等。
sigaction結構定義以下:
struct sigaction { union{ __sighandler_t _sa_handler; void (*_sa_sigaction)(int,struct siginfo *, void *); }_u sigset_t sa_mask; unsigned long sa_flags; void (*sa_restorer)(void); } |
其中,sa_restorer,已過期,POSIX不支持它,不該再被使用。
一、聯合數據結構中的兩個元素_sa_handler以及*_sa_sigaction指定信號關聯函數,即用戶指定的信號處理函數。除了能夠是用戶自定義的處理函數外,還能夠爲SIG_DFL(採用缺省的處理方式),也能夠爲SIG_IGN(忽略信號)。
二、由_sa_handler指定的處理函數只有一個參數,即信號值,因此信號不能傳遞除信號值以外的任何信息;由_sa_sigaction是指定的信號處理函數帶有三個參數,是爲實時信號而設的(固然一樣支持非實時信號),它指定一個3參數信號處理函數。第一個參數爲信號值,第三個參數沒有使用(posix沒有規範使用該參數的標準),第二個參數是指向siginfo_t結構的指針,結構中包含信號攜帶的數據值,參數所指向的結構以下:
siginfo_t { int si_signo; /* 信號值,對全部信號有意義*/ int si_errno; /* errno值,對全部信號有意義*/ int si_code; /* 信號產生的緣由,對全部信號有意義*/ union{ /* 聯合數據結構,不一樣成員適應不一樣信號 */ //確保分配足夠大的存儲空間 int _pad[SI_PAD_SIZE]; //對SIGKILL有意義的結構 struct{ ... }... ... ... ... ... //對SIGILL, SIGFPE, SIGSEGV, SIGBUS有意義的結構 struct{ ... }... ... ... } } |
注:爲了更便於閱讀,在說明問題時常把該結構表示爲附錄2所表示的形式。
siginfo_t結構中的聯合數據成員確保該結構適應全部的信號,好比對於實時信號來講,則實際採用下面的結構形式:
typedef struct { int si_signo; int si_errno; int si_code; union sigval si_value; } siginfo_t; |
結構的第四個域一樣爲一個聯合數據結構:
union sigval { int sival_int; void *sival_ptr; } |
採用聯合數據結構,說明siginfo_t結構中的si_value要麼持有一個4字節的整數值,要麼持有一個指針,這就構成了與信號相關的數據。在信號的處理函數中,包含這樣的信號相關數據指針,但沒有規定具體如何對這些數據進行操做,操做方法應該由程序開發人員根據具體任務事先約定。
前面在討論系統調用sigqueue發送信號時,sigqueue的第三個參數就是sigval聯合數據結構,當調用sigqueue時,該數據結構中的數據就將拷貝到信號處理函數的第二個參數中。這樣,在發送信號同時,就可讓信號傳遞一些附加信息。信號能夠傳遞信息對程序開發是很是有意義的。
信號參數的傳遞過程可圖示以下:
三、sa_mask指定在信號處理程序執行過程當中,哪些信號應當被阻塞。缺省狀況下當前信號自己被阻塞,防止信號的嵌套發送,除非指定SA_NODEFER或者SA_NOMASK標誌位。
注:請注意sa_mask指定的信號阻塞的前提條件,是在由sigaction()安裝信號的處理函數執行過程當中由sa_mask指定的信號才被阻塞。
四、sa_flags中包含了許多標誌位,包括剛剛提到的SA_NODEFER及SA_NOMASK標誌位。另外一個比較重要的標誌位是SA_SIGINFO,當設定了該標誌位時,表示信號附帶的參數能夠被傳遞到信號處理函數中,所以,應該爲sigaction結構中的sa_sigaction指定處理函數,而不該該爲sa_handler指定信號處理函數,不然,設置該標誌變得毫無心義。即便爲sa_sigaction指定了信號處理函數,若是不設置SA_SIGINFO,信號處理函數一樣不能獲得信號傳遞過來的數據,在信號處理函數中對這些信息的訪問都將致使段錯誤(Segmentation fault)。
注:不少文獻在闡述該標誌位時都認爲,若是設置了該標誌位,就必須定義三參數信號處理函數。實際不是這樣的,驗證方法很簡單:本身實現一個單一參數信號處理函數,並在程序中設置該標誌位,能夠察看程序的運行結果。實際上,能夠把該標誌位當作信號是否傳遞參數的開關,若是設置該位,則傳遞參數;不然,不傳遞參數。
6、信號集及信號集操做函數:
信號集被定義爲一種數據類型:
typedef struct { unsigned long sig[_NSIG_WORDS]; } sigset_t |
信號集用來描述信號的集合,linux所支持的全部信號能夠所有或部分的出如今信號集中,主要與信號阻塞相關函數配合使用。下面是爲信號集操做定義的相關函數:
#include <signal.h> int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t *set, int signum) int sigdelset(sigset_t *set, int signum); int sigismember(const sigset_t *set, int signum); sigemptyset(sigset_t *set)初始化由set指定的信號集,信號集裏面的全部信號被清空; sigfillset(sigset_t *set)調用該函數後,set指向的信號集中將包含linux支持的64種信號; sigaddset(sigset_t *set, int signum)在set指向的信號集中加入signum信號; sigdelset(sigset_t *set, int signum)在set指向的信號集中刪除signum信號; sigismember(const sigset_t *set, int signum)斷定信號signum是否在set指向的信號集中。 |
7、信號阻塞與信號未決:
每一個進程都有一個用來描述哪些信號遞送到進程時將被阻塞的信號集,該信號集中的全部信號在遞送到進程後都將被阻塞。下面是與信號阻塞相關的幾個函數:
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)); int sigpending(sigset_t *set)); int sigsuspend(const sigset_t *mask)); |
sigprocmask()函數可以根據參數how來實現對信號集的操做,操做主要有三種:
參數how | 進程當前信號集 |
SIG_BLOCK | 在進程當前阻塞信號集中添加set指向信號集中的信號 |
SIG_UNBLOCK | 若是進程阻塞信號集中包含set指向信號集中的信號,則解除對該信號的阻塞 |
SIG_SETMASK | 更新進程阻塞信號集爲set指向的信號集 |
sigpending(sigset_t *set))得到當前已遞送到進程,卻被阻塞的全部信號,在set指向的信號集中返回結果。
sigsuspend(const sigset_t *mask))用於在接收到某個信號以前, 臨時用mask替換進程的信號掩碼, 並暫停進程執行,直到收到信號爲止。sigsuspend 返回後將恢復調用以前的信號掩碼。信號處理函數完成後,進程將繼續執行。該系統調用始終返回-1,並將errno設置爲EINTR。
附錄1:結構itimerval:
struct itimerval { struct timeval it_interval; /* next value */ struct timeval it_value; /* current value */ }; struct timeval { long tv_sec; /* seconds */ long tv_usec; /* microseconds */ }; |
附錄2:三參數信號處理函數中第二個參數的說明性描述:
siginfo_t { int si_signo; /* 信號值,對全部信號有意義*/ int si_errno; /* errno值,對全部信號有意義*/ int si_code; /* 信號產生的緣由,對全部信號有意義*/ pid_t si_pid; /* 發送信號的進程ID,對kill(2),實時信號以及SIGCHLD有意義 */ uid_t si_uid; /* 發送信號進程的真實用戶ID,對kill(2),實時信號以及SIGCHLD有意義 */ int si_status; /* 退出狀態,對SIGCHLD有意義*/ clock_t si_utime; /* 用戶消耗的時間,對SIGCHLD有意義 */ clock_t si_stime; /* 內核消耗的時間,對SIGCHLD有意義 */ sigval_t si_value; /* 信號值,對全部實時有意義,是一個聯合數據結構, /*能夠爲一個整數(由si_int標示,也能夠爲一個指針,由si_ptr標示)*/ void * si_addr; /* 觸發fault的內存地址,對SIGILL,SIGFPE,SIGSEGV,SIGBUS 信號有意義*/ int si_band; /* 對SIGPOLL信號有意義 */ int si_fd; /* 對SIGPOLL信號有意義 */ } |
實際上,除了前三個元素外,其餘元素組織在一個聯合結構中,在聯合數據結構中,又根據不一樣的信號組織成不一樣的結構。註釋中提到的對某種信號有意義指的是,在該信號的處理函數中能夠訪問這些域來得到與信號相關的有意義的信息,只不過特定信號只對特定信息感興趣而已。
來源:http://www.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html