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.

執行結果如下:

可以看到,子進程收到SIGABRT信號後,終止了。沒有向屏幕打印I am son.關於信號的詳解,看這裏:https://blog.csdn.net/zy010101/article/details/83932113

上面的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;
}

運行結果如下:

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章