Linux進程間通信(1)- 信號(signal)機制

1 概述
Linux和類Linux系統下進程間通信(Inter-Process Communication, IPC)有很多種方式,包括套接字(socket),共享內存(shared memory),管道(pipe),消息隊列(message queue)等[1],各自有各自的一些應用場景和用途,這次就來總結一下通過信號(signal)的機制。

信號,是Linux中向進程發送的消息,接收到該信號的進程會相應地採取一些行動,即通過軟中斷的方式來響應這個信號,觸發一些事先指定或特定的事件。進程之間可以互相通過系統調用kill來發送信號,內核也可以因爲內部事件而給進程發送信號,通知進程發生了某件事件[2]。

2 signal的系統api使用
2.1 響應信號

程序可以用signal庫函數來處理信號,它的定義如下:

#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);

這個定義表示,signal是一個帶有sig和func兩個參數的函數,其中func是一個函數指針,指向的函數帶有1個int類型參數且無返回值,signal函數返回值也是一個帶有1個int類型參數且無返回值的函數指針。signal函數作用是綁定信號值爲sig的信號的響應時間爲func指向的函數,即當捕獲到sig信號時,調用func指向的函數(可稱爲信號處理函數),另外func也可以用下面兩個特殊值之一來代替信號處理函數:

    SIG_IGN    忽略信號
    SIG_DFL    恢復默認行爲

編寫一個示例程序C代碼:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

void handler(int sig) {
    printf("this signal no. is %d\n", sig);
    printf("Restore to default.\n");
    signal(sig, SIG_DFL);
}

int main(int argc, char **argv) {
    int sleep_sec = 1;
    int count = 1;
    signal(SIGINT , handler);
    while(1) {
        printf("waiting signal %d secs.\n", count);
        sleep(sleep_sec);
        count += 1;
    }
    return 0;
}

編譯運行:


可以發現,當我們按Ctrl+C時,操作系統會給該程序發送信號值爲2的信號,其實是SIGINT信號,在main函數中,設定捕獲SIGINT信號時,去執行handler函數。正常情況下,程序進行不斷的sleep和printf循環,當收到SIGINT信號時,會發生軟件中斷,保存現場,然後轉而去執行信號處理函數,這裏也就是handler,執行完後,回到現場,繼續執行中斷時的後續代碼。

在handler信號處理函數裏,我們使用了SIG_DFL的值來進行重綁定,於是下一次我們通過鍵盤按Ctrl+C時,它就中斷去執行默認的信號處理函數而不是handler了,於是就退出程序了。

signal函數返回的是先前對指定信號進行處理的信號處理函數的函數指針,如果未定義信號處理函數,則返回SIG_ERR並設置errno爲一個正數值,如果給出的是一個無效的信號,或者嘗試處理的信號是不可捕獲或不可忽略的信號(如SIGKILL),errno將被設置爲EINVAL。

這裏需要注意兩點:

不同版本的UNIX/LINUX系統,對信號處理方式上有些不同,比如有的是執行完信號處理函數後,自動的將對應的信號的處理函數恢復到默認,而有的不是,所以這裏我們希望恢復到默認,最好的方法就是自己寫出恢復默認的代碼;
信號的值,會因爲系統的不同而不同,比如我們這個系統的SIGINT的值是2,可能其他的就不一定
信號的名稱是在頭文件signal.h中定義的,它們以SIG開頭,比如下表:

信號名稱 說明
SIGABORT  *進程異常終止
SIGALRM 超時警告
SIGFPE *浮點運算異常
SIGHUP 連接掛斷
SIGILL  *非法指令
SIGINT     終端中斷
SIGKILL     終止進程(此信號不能被捕獲或忽略)
SIGPIPE 向無讀進程的管道寫數據
SIGQUIT 終端退出
SIGSEGV *無效內存段訪問
SIGTERM  終止
SIGUSER1  用戶定義信號1
SIGUSER2  用戶定義信號2

其中帶*的信號,系統對該信號的響應視具體實現而定。如果進程接收到上表信號中的任何一個,但事先沒有安排捕獲它,進程將會立刻終止。通常,系統將會生成core文件,放在當前目錄下,該文件是進程在內存中的映像,對程序調試很有用處。當然,系統還有很多其他的信號,比如後文所述,可以通過 kill -l 來查看可發送的信號。

2.2 發送信號
進程可以通過調用kill函數向包括它本身在內的其他進程發送一個信號。如果程序沒有權限,kill函數會調用失敗,失敗的常見原因是目標進程由另一個用戶所擁有,這個函數跟shell同名命令kill的功能完全一樣,定義如下:

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);

kill函數中,pid表示要發送信號到達的目標進程的進程id,sig爲發送的信號值。

成功時,返回0。失敗時,返回-1,並設置 errno變量,errno值包括以下情況:

    errno = EINVAL     給定的信號無效
    errno = EPERM      發送進程權限不夠
    errno = ESRCH      目標進程不存在

Linux信號機制向我們提供了一個有用的鬧鐘功能,進程可以通過調用alarm函數在經過預定時間後發送一個SIGALRM信號:

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

alarm函數用來在seconds秒之後安排發送一個SIGALRM信號,但由於處理的延時和時間調度的不確定性,實際鬧鐘時間比預先安排的要稍微拖後一點兒。如果把參數設置爲0,則取消所有已設置的鬧鐘請求。

還有另外一個有用的信號函數:

#include <unistd.h>

int pause(void);

它的作用很簡單,就是把程序的執行掛起直到有一個信號出現爲止,纔會繼續運行它下面的代碼。 
這個函數很有用,因爲有時我們需要等待某個信號的發生,使用它便意味着程序不需要總是在執行,浪費CPU資源,對系統性能造成極大的影響。

對於信號機制的使用值得一提的是:如果信號出現在系統調用的執行過程中,可能有些系統調用會失敗,大多數情況是一些比較慢得調用,比如從終端讀數據,如果在這個系統調用等待數據時出現一個信號,它就會返回一個錯誤。工程實踐中使用時,要十分注意和周全考慮。

其實上面介紹的傳統UNIX編程中的signal和其相關函數,X/Open和UNIX規範推薦了一個更新更健壯的信號編程接口: sigaction,定義如下:

#include <signal.h>

int sigaction(int sig, const struct sigaction* act, struct sigaction *oact);

3 Linux系統命令使用
想要給一個進程發送信號,而該進程並不是當前的前臺進程,就需要使用kill命令[4],它需要一個可選的信號代碼,和一個進程ID,例如給PID爲520的進程發送掛斷信號(SIGHUP),使用如下命令:

    命令格式:kill [參數] [進程號]
    例如:
    kill -HUP 520

如果沒有指定信號代碼或值,則默認情況下,採用編號爲15的TERM信號。TERM信號將終止所有不能捕獲該信號的進程。對於那些可以捕獲該信號的進程就要用編號爲9的kill信號,強行“殺掉”該進程。

    命令參數:
    -l  信號,如果不加信號的編號參數,則使用“-l”參數會列出全部的信號名稱
    -a  當處理當前進程時,不限制命令名和進程號的對應關係
    -p  指定kill 命令只打印相關進程的進程號,而不發送任何信號
    -s  指定發送信號
    -u  指定用戶

比如可以使用-l參數列出所有支持的信號和他們的值:


kill命令有一個變體,即killall,它可以給運行着某一命令的所有進程發送信號,一般的Linux系統都會有這個命令,如果不知道某進程的pid,或者想給執行相同命名的許多不同的進程發送信號,這條命令就很有用了,一重常用的做法是,通知inetd程序重新讀取它的配置選項[1]:

    killall -HUP inetd

參考文獻
[1] Linux程序設計(第4版)

[2] Linux信號(signal)機制分析, http://www.cnblogs.com/hoys/archive/2012/08/19/2646377.html

[3] 每天一個linux命令(42):kill命令,http://www.cnblogs.com/peida/archive/2012/12/20/2825837.html

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