Linux程序通訊——訊號
訊號是在軟體層面對中斷機制的一種模擬,訊號的出現使得程序直接的通訊不在是被動的,不在向之前那樣,read()操作往往需要等待write()操作結束。因為訊號是對中斷的一種模擬。既然是中斷,那麼它的發生就是不確定。就不會發生一個程序阻塞在這裡等待另一個程序執行的結果。這樣的非同步性通訊機制無疑是更加強大的。
在終端輸入kill -l可以檢視當前系統所支援的所有訊號。(我這個是Ubuntu)
可以看到有64個訊號,其中有兩個較為特殊的訊號是SIGRTMIN和SIGRTMAX。Linux下的通訊機制是遵從POSIX標準的。34號訊號SIGRTMIN訊號之前的是早期UNIX作業系統的。它們是不可靠的訊號。它的主要問題是:程序每次處理訊號後,會設定對該訊號的預設處理動作,有時候我們不想讓他這麼處理了(按照預設處理),這時候就需要呼叫signal()函式重新安裝一次訊號。這樣會形成新的預設動作。還有更加討厭的是,訊號有可能會丟失。
Linux對不可靠訊號做了一些改進,現在的主要問題變成了“訊號會丟失”。
後來POSIX僅僅只對可靠訊號做了標準化。訊號值位於SIGRTMIN和SIGRTMAX之間的訊號都是可靠訊號。可靠訊號它不會丟失。
可靠訊號都是實時訊號,不可靠訊號都是非實時訊號。可靠訊號都支援佇列處理,不可靠訊號不支援佇列處理。在UNIX時代就定義好了前面的不可靠訊號的功能,而後來增加的可靠訊號是讓使用者自定義使用的。
訊號處理的三種方式:
- 忽略訊號:對訊號不做任何處理,就當做沒發生任何事情一樣。(SIGKILL和SIGSTOP這兩個不能忽略)
- 捕捉訊號:定義訊號處理函式,當訊號發出的時候,執行相應的操作。(這個和Qt的訊號槽差不多)
- 執行預設動作:Linux對每一個訊號都規定了預設操作(可靠訊號的預設操作是程序終止)。
傳送訊號
傳送訊號的函式有kill(),raise(),sigqueue(),alarm(),setittimer(),abort()。常用的是kill()。它們依賴的標頭檔案是#include<signal.h>和#include<sys/types.h>
函式原型:int kill(pid_t pid,int sig);
函式功能:用來將sig所指定的訊號傳送到pid所指定的程序。
pid有下面幾種情形,分別對應於不同情況下應用。
- pid > 0:把訊號傳遞到程序ID為pid的程序
- pid == 0:把訊號傳送給當前程序所在組的所有程序
- pid == -1:將訊號以廣播的形式傳送給系統內所有程序
- pid < -1: 講訊號傳遞給程序組識別碼為pid絕對值的所有程序
函式執行成功返回0,否則返回-1.
測試程式碼如下:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<signal.h>
#include<wait.h>
#include<unistd.h>
int main()
{
pid_t pid;
int statu;
pid = fork();
if(0 == pid)
{
printf("son\n");
sleep(5);
printf("I am son!\n");
exit(0);
}
if(0 < pid)
{
sleep(2);
printf("father\n");
kill(pid,SIGABRT); //SIGABRT是終止子程序
wait(NULL);
printf("My son GG\n");
exit(0);
}
return 0;
}
讓子程序先執行,打印出son。然後讓子程序掛起。輪到父程序執行,父程序執行到kill()函式的時候給子程序發了個SIGABRT訊號,讓子程序終止了。然後wait()回收子程序,列印My son GG.
執行結果如下:
上面的kill函式傳送的訊號是不可靠訊號,它執行預設操作。即:終止程序。如果我們需要自定義訊號處理方式,那麼就需要安裝訊號。Linux安裝訊號主要由signal()和sigaction()完成。signal是在可靠訊號系統呼叫的基礎上實現的,是庫函式。
signal()的原型很複雜,我們還是從signal.h這個標頭檔案來看一下吧!
extern __sighandler_t signal (int __sig, __sighandler_t __handler)
__THROW;
可以看到signal有兩個引數,一個是訊號值,另一個我們再來看看
typedef void (*__sighandler_t) (int);
另一個是這樣的一個函式指標變數,那麼說明__handler代表了一個函式的入口地址(實際就是函式)。另外,這個函式指標指向的函式需要一個int型別的引數。signal函式的返回值也是一個函式指標。
注意:__handler如果不是函式指標,它只能是SIG_IGN或者是SIG_DFL.
SIG_IGN:忽略引數指定的訊號。(忽略該訊號)
SIG_DFL:將引數指定的訊號重新設定為核心預設的處理方式。
返回值:signal函式本身在成功時返回NULL,它的引數__handler則會返回處理訊號的函式的地址(函式指標)。失敗返回:SIG_ERR.(看不明白函式指標的同學,請閱讀《C缺陷與陷阱》,《C與指標》好像是這兩本講的很清楚,我記不太清了)
所以這就要求自定義的訊號處理函式的函式原型是這樣的:
void 函式名(int 引數名);即:函式必須有一個int型別的引數。
signal()函式只是定義了將指定訊號傳送到指定程序。還需要一個用於捕捉訊號的函式。在Linux下pause()函式用於捕捉訊號,如果沒有訊號發生,pause函式將會一直等待。直到有訊號發生。
函式原型:int pause();
當pause函式捕捉到訊號的時候返回-1(注意不是捕捉到的訊號的值)。
測試程式如下:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
#include<signal.h>
#include<wait.h>
//自定義的訊號處理函式
void My_Fun(int sig)
{
if(SIGRTMIN == sig)
{
printf("MIN!\n");
}
if(SIGRTMAX == sig)
{
printf("MAX!\n");
}
}
int main()
{
//註冊訊號處理函式
signal(SIGRTMIN,My_Fun);
signal(SIGRTMAX,My_Fun);
//掛起10s
sleep(3);
//發出訊號
kill(getpid(),SIGRTMIN); //getpid()函式用於獲取當前程序的pid.
kill(getpid(),SIGRTMAX);
return 0;
}
輸出結果如下:
這樣就完成了自定義訊號的使用。使用自定義訊號有兩個關鍵點。一是必須註冊自定義訊號的處理函式,二是必須傳送自定義訊號。怎麼樣傳送自定義訊號由你自己來定義,這為程式設計帶來了極大的便利。比如上面我們只是直接了當的傳送兩個訊號。你也可以使當滿足一定條件的時候才傳送訊號。比如下面這樣。
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
#include<signal.h>
#include<wait.h>
//自定義的訊號處理函式
void My_Fun(int sig)
{
if(SIGRTMIN == sig)
{
printf("MIN!\n");
}
if(SIGRTMAX == sig)
{
printf("MAX!\n");
}
}
int main()
{
//註冊訊號處理函式
signal(SIGRTMIN,My_Fun);
signal(SIGRTMAX,My_Fun);
//發出訊號
char c;
while(1)
{
scanf("%c",&c);
getchar(); //吸收回車
if('a' == c)
{
kill(getpid(),SIGRTMIN); //getpid()函式用於獲取當前程序的pid.
}
else
{
kill(getpid(),SIGRTMAX);
}
}
return 0;
}
執行結果如下:
這樣就實現了傳送訊號的控制。可以想象,鍵盤,滑鼠等傳送的訊號很有可能就是被系統採取這樣的方式處理的。