Linux進程間通訊——信號

1、認識信號

信號(Signals)是Unix、類Unix以及其餘POSIX兼容的操做系統中進程間通信的一種有限制的方式。它是一種異步的通知機制,用來提醒進程一個事件已經發生。當一個信號發送給一個進程,操做系統中斷了進程正常的控制流程,此時,任何非原子操做都將被中斷。若是進程定義了信號的處理函數,那麼它將被執行,不然就執行默認的處理函數。html

信號是進程間通訊機制中惟一的異步通訊機制,能夠看做是異步通知,通知接收信號的進程有哪些事情發生了。也能夠簡單理解爲信號是某種形式上的軟中斷。linux

2、信號來源

通常狀況下,信號的來源可分爲如下三種:git

  • 硬件方式:除數爲零、無效的存儲訪問等硬件異常產生信號。這些事件一般由硬件(如:CPU)檢測到,並將其通知給Linux操做系統內核,而後內核生成相應的信號,並把信號發送給該事件發生時正在進行的程序。
  • 軟件方式:用戶在終端下調用kill命令向進程發送任務信號、進程調用killsigqueue函數發送信號、當檢測到某種軟件條件已經具有時發出信號,如由alarmsettimer設置的定時器超時時將生成SIGALRM信號等多種情景都可產生信號。
  • 鍵盤輸入:當用戶在終端上按下某鍵時,將產生信號。如按下組合鍵Ctrl+C將產生一個SIGINT信號,Ctrl+\產生一個SIGQUIT信號等。

3、信號種類

可運行kill -l查看Linux支持的信號列表:github

sl@Li:~/Works$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX
複製代碼

能夠看到Linux中系統一共支持64種信號,其中1到31號信號爲普通訊號(也程爲不可靠信號),34到64爲實時信號(可靠信號)。shell

可靠信號與不可靠信號的區別:微信

  • 這裏的不可靠主要是不支持信號隊列,就是當多個信號發生在進程中的時候(收到信號的速度超過進程處理的速度的時候),這些沒來的及處理的信號就會被丟掉,僅僅留下一個信號。
  • 可靠信號是多個信號發送到進程的時候(收到信號的速度超過進程處理信號的速度的時候),這些沒來的及處理的信號就會排入進程的隊列。等進程有機會來處理的時候,依次再處理,信號不丟失。

如下列出幾個經常使用的信號:異步

信號 描述
SIGHUP 當用戶退出終端時,由該終端開啓的全部進程都退接收到這個信號,默認動做爲終止進程。
SIGINT 程序終止(interrupt)信號, 在用戶鍵入INTR字符(一般是Ctrl+C)時發出,用於通知前臺進程組終止進程。
SIGQUIT SIGINT相似, 但由QUIT字符(一般是Ctrl+\)來控制. 進程在因收到SIGQUIT退出時會產生core文件, 在這個意義上相似於一個程序錯誤信號。
SIGKILL 用來當即結束程序的運行. 本信號不能被阻塞、處理和忽略
SIGTERM 程序結束(terminate)信號, 與SIGKILL不一樣的是該信號能夠被阻塞和處理。一般用來要求程序本身正常退出。
SIGSTOP 中止(stopped)進程的執行. 注意它和terminate以及interrupt的區別:該進程還未結束, 只是暫停執行. 本信號不能被阻塞, 處理或忽略.

4、可重入函數

要注意信號處理函數的可重入問題,由於信號處理函數執行過程當中有可能被其餘信號再次中斷,這時程序會跳到另外一個信號的處理函數中,處理完成後再次返回當前處理函數,編寫本身定義的信號處理函數的時候必定要注意這一點。信號能夠理解爲「軟中斷」,這樣可重入函數就很好理解了。函數

5、信號的捕獲

內核處理一個進程收到的信號的時機是在一個進程從內核態返回用戶態時。 因此,當一個進程在內核態下運行時,軟中斷信號並不當即起做用,要等到將返回用戶態時才處理。進程只有處理完信號纔會返回用戶態,進程在用戶態下不會有未處理完的信號。ui

內核處理一個進程收到的軟中斷信號是在該進程的上下文中,所以,進程必須處於運行狀態。當進程接收到一個它忽略的信號時,進程丟棄該信號,就像沒有收到該信號似的繼續運行。spa

若是進程收到一個要捕捉的信號,那麼進程從內核態返回用戶態時執行用戶定義的函數。並且執行用戶定義的函數的方法很巧妙,內核在用戶棧上建立一個新的層,該層中將返回地址的值設置成用戶定義的處理函數的地址,這樣進程從內核返回彈出棧頂時就返回到用戶定義的函數處,從函數返回再彈出棧頂時,才返回原先進入內核的地方。 這樣作的緣由是用戶定義的處理函數不能且不容許在內核態下執行(若是用戶定義的函數在內核態下運行的話,用戶就能夠得到任何權限)。

下面引用一個例子說明這個過程:

  1. 用戶程序註冊了SIGQUIT信號的處理函數sighandler
  2. 當前正在執行main函數,這時發生中斷或異常切換到內核態。
  3. 在中斷處理完畢後要返回用戶態的main函數以前檢查到有信號SIGQUIT遞達。
  4. 內核決定返回用戶態後不是恢復main函數的上下文繼續執行,而是執行sighandler函數,sighandlermain函數使用不一樣的堆棧空間,它們之間不存在調用和被調用的關係,是兩個獨立的控制流程。
  5. sighandler函數返回後自動執行特殊的系統調用sigreturn再次進入內核態。
  6. 若是沒有新的信號要遞達,此次再返回用戶態就是恢復main函數的上下文繼續執行了。

6、代碼示例1

下面的代碼收到程序退出信號後會執行用戶定義的信號處理函數來替代系統默認的處理程序。

#include<stdlib.h>
#include<stdio.h>
#include<signal.h>
#include<sys/types.h>
#include<unistd.h>

void sig_handle(int sig) {
    printf("received signal: %d, quit.\n", sig);
    exit(0);
}

int main () {
    signal(SIGINT, sig_handle);
    signal(SIGKILL, sig_handle);
    signal(SIGSEGV, sig_handle);
    signal(SIGTERM, sig_handle);

    int i = 0;
    while (1) {
        printf("%d\n", ++i);
        sleep(2);
    }

    printf("main quit.");

    return 0;
}
複製代碼

運行結果:

1
2
received signal: 15, quit.
複製代碼

7、代碼示例2

這段代碼功能與上面的例子差很少,信號也能夠傳參,可是更多的是通知功能,可傳遞的信息很是有限,若是須要傳遞大量的信息,能夠考慮其餘進程間通訊方式。

#include<signal.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>

void new_op(int, siginfo_t*, void*);

int main() {
    if (NULL == freopen("sigproc.log", "w", stdout)) {
        fprintf(stderr, "error redirecting stdout\n");
    }

    struct sigaction act;

    sigemptyset(&act.sa_mask);  //sa_mask指定在信號處理程序執行過程當中,哪些信號應當被阻塞。缺省狀況下當前信號自己被阻塞,防止信號的嵌套發送
    sigaddset(&act.sa_mask, SIGTERM);
    sigaddset(&act.sa_mask, SIGINT);
    act.sa_flags = SA_SIGINFO;  //SA_SIGINFO,當設定了該標誌位時,表示信號附帶的參數能夠被傳遞到信號處理函數中
    act.sa_sigaction = new_op;

    if (sigaction(SIGINT, &act, NULL) < 0) {
        printf("install sigal error\n");
    }

    if (sigaction(SIGTERM, &act, NULL) < 0) {
        printf("install sigal error\n");
    }

    if (sigaction(SIGHUP, &act, NULL) < 0) {
        printf("install sigal error\n");
    }

    int i = 0;
    while (1) {
        printf("%d\n", ++i);
        sleep(1);
    }

    printf("end.");

    return 0;
}

void new_op(int signum, siginfo_t *info, void *myact) {
    printf("receive signal %d\n", signum);
    for (int i = 0; i < 5; ++i) {
        printf("signal processing: %d\n", i);
        sleep(1);
    }
    printf("process quit.");

    exit(0);
}

複製代碼

運行結果:

1
2
3
receive signal 15
signal processing: 0
signal processing: 1
signal processing: 2
signal processing: 3
signal processing: 4
process quit.
複製代碼

參考文檔:
信號
Linux環境進程間通訊(二)信號(上)
Linux環境進程間通訊(二)信號(下)

最後,歡迎關注微信公衆號,Let's go!

相關文章
相關標籤/搜索