1. 程式人生 > >Linux程序通訊——訊號

Linux程序通訊——訊號

訊號是在軟體層面對中斷機制的一種模擬,訊號的出現使得程序直接的通訊不在是被動的,不在向之前那樣,read()操作往往需要等待write()操作結束。因為訊號是對中斷的一種模擬。既然是中斷,那麼它的發生就是不確定。就不會發生一個程序阻塞在這裡等待另一個程序執行的結果。這樣的非同步性通訊機制無疑是更加強大的。

在終端輸入kill -l可以檢視當前系統所支援的所有訊號。(我這個是Ubuntu)

 可以看到有64個訊號,其中有兩個較為特殊的訊號是SIGRTMIN和SIGRTMAX。Linux下的通訊機制是遵從POSIX標準的。34號訊號SIGRTMIN訊號之前的是早期UNIX作業系統的。它們是不可靠的訊號。它的主要問題是:程序每次處理訊號後,會設定對該訊號的預設處理動作,有時候我們不想讓他這麼處理了(按照預設處理),這時候就需要呼叫signal()函式重新安裝一次訊號。這樣會形成新的預設動作。還有更加討厭的是,訊號有可能會丟失。

Linux對不可靠訊號做了一些改進,現在的主要問題變成了“訊號會丟失”。

後來POSIX僅僅只對可靠訊號做了標準化。訊號值位於SIGRTMIN和SIGRTMAX之間的訊號都是可靠訊號。可靠訊號它不會丟失。

可靠訊號都是實時訊號,不可靠訊號都是非實時訊號。可靠訊號都支援佇列處理,不可靠訊號不支援佇列處理。在UNIX時代就定義好了前面的不可靠訊號的功能,而後來增加的可靠訊號是讓使用者自定義使用的。

訊號處理的三種方式:

  1. 忽略訊號:對訊號不做任何處理,就當做沒發生任何事情一樣。(SIGKILL和SIGSTOP這兩個不能忽略)
  2. 捕捉訊號:定義訊號處理函式,當訊號發出的時候,執行相應的操作。(這個和Qt的訊號槽差不多)
  3. 執行預設動作: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;
}

執行結果如下:

這樣就實現了傳送訊號的控制。可以想象,鍵盤,滑鼠等傳送的訊號很有可能就是被系統採取這樣的方式處理的。