linux 信號處理 五 (示例)

【摘要】本文分析了Linux內核對於信號的實現機制和應用層的相關處理。首先介紹了軟中斷信號的本質及信號的兩種不同分類方法尤其是不可靠信號的原理。接着分析了內核對於信號的處理流程包括信號的觸發/註冊/執行及註銷等。最後介紹了應用層的相關處理,主要包括信號處理函數的安裝、信號的發送、屏蔽阻塞等,最後給了幾個簡單的應用實例。

 

【關鍵字】軟中斷信號,signal,sigaction,kill,sigqueue,settimer,sigmask,sigprocmask,sigset_t

 

1       信號本質

軟中斷信號(signal,又簡稱爲信號)用來通知進程發生了異步事件。在軟件層次上是對中斷機制的一種模擬,在原理上,一個進程收到一個信號與處理器收到一箇中斷請求可以說是一樣的。信號是進程間通信機制中唯一的異步通信機制,一個進程不必通過任何操作來等待信號的到達,事實上,進程也不知道信號到底什麼時候到達。進程之間可以互相通過系統調用kill發送軟中斷信號。內核也可以因爲內部事件而給進程發送信號,通知進程發生了某個事件。信號機制除了基本通知功能外,還可以傳遞附加信息。

 

收到信號的進程對各種信號有不同的處理方法。處理方法可以分爲三類:

第一種是類似中斷的處理程序,對於需要處理的信號,進程可以指定處理函數,由該函數來處理。

第二種方法是,忽略某個信號,對該信號不做任何處理,就象未發生過一樣。

第三種方法是,對該信號的處理保留系統的默認值,這種缺省操作,對大部分的信號的缺省操作是使得進程終止。進程通過系統調用signal來指定進程對某個信號的處理行爲。

 

2       信號的種類

可以從兩個不同的分類角度對信號進行分類:

可靠性方面:可靠信號與不可靠信號;

與時間的關係上:實時信號與非實時信號。

 

2.1    可靠信號與不可靠信號

Linux信號機制基本上是從Unix系統中繼承過來的。早期Unix系統中的信號機制比較簡單和原始,信號值小於SIGRTMIN的信號都是不可靠信號。這就是"不可靠信號"的來源。它的主要問題是信號可能丟失。

 

隨着時間的發展,實踐證明了有必要對信號的原始機制加以改進和擴充。由於原來定義的信號已有許多應用,不好再做改動,最終只好又新增加了一些信號,並在一開始就把它們定義爲可靠信號,這些信號支持排隊,不會丟失。

 

信號值位於SIGRTMIN和SIGRTMAX之間的信號都是可靠信號,可靠信號克服了信號可能丟失的問題。Linux在支持新版本的信號安裝函數sigation()以及信號發送函數sigqueue()的同時,仍然支持早期的signal()信號安裝函數,支持信號發送函數kill()。

 

信號的可靠與不可靠只與信號值有關,與信號的發送及安裝函數無關。目前linux中的signal()是通過sigation()函數實現的,因此,即使通過signal()安裝的信號,在信號處理函數的結尾也不必再調用一次信號安裝函數。同時,由signal()安裝的實時信號支持排隊,同樣不會丟失。

 

對於目前linux的兩個信號安裝函數:signal()及sigaction()來說,它們都不能把SIGRTMIN以前的信號變成可靠信號(都不支持排隊,仍有可能丟失,仍然是不可靠信號),而且對SIGRTMIN以後的信號都支持排隊。這兩個函數的最大區別在於,經過sigaction安裝的信號都能傳遞信息給信號處理函數,而經過signal安裝的信號不能向信號處理函數傳遞信息。對於信號發送函數來說也是一樣的。

 

2.2    實時信號與非實時信號

早期Unix系統只定義了32種信號,前32種信號已經有了預定義值,每個信號有了確定的用途及含義,並且每種信號都有各自的缺省動作。如按鍵盤的CTRL ^C時,會產生SIGINT信號,對該信號的默認反應就是進程終止。後32個信號表示實時信號,等同於前面闡述的可靠信號。這保證了發送的多個實時信號都被接收。

 

非實時信號都不支持排隊,都是不可靠信號;實時信號都支持排隊,都是可靠信號。

 

3       信號處理流程

 

 

對於一個完整的信號生命週期(從信號發送到相應的處理函數執行完畢)來說,可以分爲三個階段:

信號誕生

信號在進程中註冊

信號的執行和註銷

 

 

3.1    信號誕生

信號事件的發生有兩個來源:硬件來源(比如我們按下了鍵盤或者其它硬件故障);軟件來源,最常用發送信號的系統函數是kill, raise, alarm和setitimer以及sigqueue函數,軟件來源還包括一些非法運算等操作。

 

這裏按發出信號的原因簡單分類,以瞭解各種信號:

(1) 與進程終止相關的信號。當進程退出,或者子進程終止時,發出這類信號。

(2) 與進程例外事件相關的信號。如進程越界,或企圖寫一個只讀的內存區域(如程序正文區),或執行一個特權指令及其他各種硬件錯誤。

(3) 與在系統調用期間遇到不可恢復條件相關的信號。如執行系統調用exec時,原有資源已經釋放,而目前系統資源又已經耗盡。

(4) 與執行系統調用時遇到非預測錯誤條件相關的信號。如執行一個並不存在的系統調用。

(5) 在用戶態下的進程發出的信號。如進程調用系統調用kill向其他進程發送信號。

(6) 與終端交互相關的信號。如用戶關閉一個終端,或按下break鍵等情況。

(7) 跟蹤進程執行的信號。

 

Linux支持的信號列表如下。很多信號是與機器的體系結構相關的

信號值 默認處理動作 發出信號的原因

SIGHUP 1 A 終端掛起或者控制進程終止

SIGINT 2 A 鍵盤中斷(如break鍵被按下)

SIGQUIT 3 C 鍵盤的退出鍵被按下

SIGILL 4 C 非法指令

SIGABRT 6 C 由abort(3)發出的退出指令

SIGFPE 8 C 浮點異常

SIGKILL 9 AEF Kill信號

SIGSEGV 11 C 無效的內存引用

SIGPIPE 13 A 管道破裂: 寫一個沒有讀端口的管道

SIGALRM 14 A 由alarm(2)發出的信號

SIGTERM 15 A 終止信號

SIGUSR1 30,10,16 A 用戶自定義信號1

SIGUSR2 31,12,17 A 用戶自定義信號2

SIGCHLD 20,17,18 B 子進程結束信號

SIGCONT 19,18,25 進程繼續(曾被停止的進程)

SIGSTOP 17,19,23 DEF 終止進程

SIGTSTP 18,20,24 D 控制終端(tty)上按下停止鍵

SIGTTIN 21,21,26 D 後臺進程企圖從控制終端讀

SIGTTOU 22,22,27 D 後臺進程企圖從控制終端寫

 

處理動作一項中的字母含義如下

A 缺省的動作是終止進程

B 缺省的動作是忽略此信號,將該信號丟棄,不做處理

C 缺省的動作是終止進程並進行內核映像轉儲(dump core),內核映像轉儲是指將進程數據在內存的映像和進程在內核結構中的部分內容以一定格式轉儲到文件系統,並且進程退出執行,這樣做的好處是爲程序員提供了方便,使得他們可以得到進程當時執行時的數據值,允許他們確定轉儲的原因,並且可以調試他們的程序。

D 缺省的動作是停止進程,進入停止狀況以後還能重新進行下去,一般是在調試的過程中(例如ptrace系統調用)

E 信號不能被捕獲

F 信號不能被忽略

 

3.2    信號在目標進程中註冊

在進程表的表項中有一個軟中斷信號域,該域中每一位對應一個信號。內核給一個進程發送軟中斷信號的方法,是在進程所在的進程表項的信號域設置對應於該信號的位。如果信號發送給一個正在睡眠的進程,如果進程睡眠在可被中斷的優先級上,則喚醒進程;否則僅設置進程表中信號域相應的位,而不喚醒進程。如果發送給一個處於可運行狀態的進程,則只置相應的域即可。

 

進程的task_struct結構中有關於本進程中未決信號的數據成員: struct sigpending pending:

struct sigpending{

        struct sigqueue *head, *tail;

        sigset_t signal;

};

 

第三個成員是進程中所有未決信號集,第一、第二個成員分別指向一個sigqueue類型的結構鏈(稱之爲"未決信號信息鏈")的首尾,信息鏈中的每個sigqueue結構刻畫一個特定信號所攜帶的信息,並指向下一個sigqueue結構:

struct sigqueue{

        struct sigqueue *next;

        siginfo_t info;

}

 

信號在進程中註冊指的就是信號值加入到進程的未決信號集sigset_t signal(每個信號佔用一位)中,並且信號所攜帶的信息被保留到未決信號信息鏈的某個sigqueue結構中。只要信號在進程的未決信號集中,表明進程已經知道這些信號的存在,但還沒來得及處理,或者該信號被進程阻塞。

 

當一個實時信號發送給一個進程時,不管該信號是否已經在進程中註冊,都會被再註冊一次,因此,信號不會丟失,因此,實時信號又叫做"可靠信號"。這意味着同一個實時信號可以在同一個進程的未決信號信息鏈中佔有多個sigqueue結構(進程每收到一個實時信號,都會爲它分配一個結構來登記該信號信息,並把該結構添加在未決信號鏈尾,即所有誕生的實時信號都會在目標進程中註冊)。

 

當一個非實時信號發送給一個進程時,如果該信號已經在進程中註冊(通過sigset_t signal指示),則該信號將被丟棄,造成信號丟失。因此,非實時信號又叫做"不可靠信號"。這意味着同一個非實時信號在進程的未決信號信息鏈中,至多佔有一個sigqueue結構。

 

總之信號註冊與否,與發送信號的函數(如kill()或sigqueue()等)以及信號安裝函數(signal()及sigaction())無關,只與信號值有關(信號值小於SIGRTMIN的信號最多隻註冊一次,信號值在SIGRTMIN及SIGRTMAX之間的信號,只要被進程接收到就被註冊)

 

 

3.3    信號的執行和註銷

內核處理一個進程收到的軟中斷信號是在該進程的上下文中,因此,進程必須處於運行狀態。當其由於被信號喚醒或者正常調度重新獲得CPU時,在其從內核空間返回到用戶空間時會檢測是否有信號等待處理。如果存在未決信號等待處理且該信號沒有被進程阻塞,則在運行相應的信號處理函數前,進程會把信號在未決信號鏈中佔有的結構卸掉。

 

對於非實時信號來說,由於在未決信號信息鏈中最多隻佔用一個sigqueue結構,因此該結構被釋放後,應該把信號在進程未決信號集中刪除(信號註銷完畢);而對於實時信號來說,可能在未決信號信息鏈中佔用多個sigqueue結構,因此應該針對佔用sigqueue結構的數目區別對待:如果只佔用一個sigqueue結構(進程只收到該信號一次),則執行完相應的處理函數後應該把信號在進程的未決信號集中刪除(信號註銷完畢)。否則待該信號的所有sigqueue處理完畢後再在進程的未決信號集中刪除該信號。

 

當所有未被屏蔽的信號都處理完畢後,即可返回用戶空間。對於被屏蔽的信號,當取消屏蔽後,在返回到用戶空間時會再次執行上述檢查處理的一套流程。

 

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

 

處理信號有三種類型:進程接收到信號後退出;進程忽略該信號;進程收到信號後執行用戶設定用系統調用signal的函數。當進程接收到一個它忽略的信號時,進程丟棄該信號,就象沒有收到該信號似的繼續運行。如果進程收到一個要捕捉的信號,那麼進程從內核態返回用戶態時執行用戶定義的函數。而且執行用戶定義的函數的方法很巧妙,內核是在用戶棧上創建一個新的層,該層中將返回地址的值設置成用戶定義的處理函數的地址,這樣進程從內核返回彈出棧頂時就返回到用戶定義的函數處,從函數返回再彈出棧頂時,才返回原先進入內核的地方。這樣做的原因是用戶定義的處理函數不能且不允許在內核態下執行(如果用戶定義的函數在內核態下運行的話,用戶就可以獲得任何權限)。

 

4       信號的安裝

如果進程要處理某一信號,那麼就要在進程中安裝該信號。安裝信號主要用來確定信號值及進程針對該信號值的動作之間的映射關係,即進程將要處理哪個信號;該信號被傳遞給進程時,將執行何種操作。

 

linux主要有兩個函數實現信號的安裝:signal()、sigaction()。其中signal()只有兩個參數,不支持信號傳遞信息,主要是用於前32種非實時信號的安裝;而sigaction()是較新的函數(由兩個系統調用實現:sys_signal以及sys_rt_sigaction),有三個參數,支持信號傳遞信息,主要用來與 sigqueue() 系統調用配合使用,當然,sigaction()同樣支持非實時信號的安裝。sigaction()優於signal()主要體現在支持信號帶有參數。

 

4.1    signal()

#include <signal.h>

void (*signal(int signum, void (*handler))(int)))(int);

如果該函數原型不容易理解的話,可以參考下面的分解方式來理解:

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler));

第一個參數指定信號的值,第二個參數指定針對前面信號值的處理,可以忽略該信號(參數設爲SIG_IGN);可以採用系統默認方式處理信號(參數設爲SIG_DFL);也可以自己實現處理方式(參數指定一個函數地址)。

如果signal()調用成功,返回最後一次爲安裝信號signum而調用signal()時的handler值;失敗則返回SIG_ERR。

傳遞給信號處理例程的整數參數是信號值,這樣可以使得一個信號處理例程處理多個信號。

 

#include <signal.h>

#include <unistd.h>

#include <stdio.h>

void sigroutine(int dunno)

{ /* 信號處理例程,其中dunno將會得到信號的值 */

        switch (dunno) {

        case 1:

        printf("Get a signal -- SIGHUP \n");

        break;

        case 2:

        printf("Get a signal -- SIGINT \n");

        break;

        case 3:

        printf("Get a signal -- SIGQUIT \n");

        break;

        }

        return;

}

 

int main() {

        printf("process id is %d \n",getpid());

        signal(SIGHUP, sigroutine); //* 下面設置三個信號的處理方法

        signal(SIGINT, sigroutine);

        signal(SIGQUIT, sigroutine);

        for (;;) ;

}

 

其中信號SIGINT由按下Ctrl-C發出,信號SIGQUIT由按下Ctrl-發出。該程序執行的結果如下:

 

localhost:~$ ./sig_test

process id is 463

Get a signal -SIGINT //按下Ctrl-C得到的結果

Get a signal -SIGQUIT //按下Ctrl-\得到的結果

//按下Ctrl-z將進程置於後臺

 [1]+ Stopped ./sig_test

localhost:~$ bg

 [1]+ ./sig_test &

localhost:~$ kill -HUP 463 //向進程發送SIGHUP信號

localhost:~$ Get a signal – SIGHUP

kill -9 463 //向進程發送SIGKILL信號,終止進程

localhost:~$

 

4.2    sigaction()

#include <signal.h>

int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));

sigaction函數用於改變進程接收到特定信號後的行爲。該函數的第一個參數爲信號的值,可以爲除SIGKILL及SIGSTOP外的任何一個特定有效的信號(爲這兩個信號定義自己的處理函數,將導致信號安裝錯誤)。第二個參數是指向結構sigaction的一個實例的指針,在結構sigaction的實例中,指定了對特定信號的處理,可以爲空,進程會以缺省方式對信號處理;第三個參數oldact指向的對象用來保存返回的原來對相應信號的處理,可指定oldact爲NULL。如果把第二、第三個參數都設爲NULL,那麼該函數可用於檢查信號的有效性。

 

第二個參數最爲重要,其中包含了對指定信號的處理、信號所傳遞的信息、信號處理函數執行過程中應屏蔽掉哪些信號等等。

sigaction結構定義如下:

struct sigaction {

                       union{

                               __sighandler_t _sa_handler;

                               void (*_sa_sigaction)(int,struct siginfo *, void *);

                       }_u

            sigset_t sa_mask;

            unsigned long sa_flags;

}

 

1、聯合數據結構中的兩個元素_sa_handler以及*_sa_sigaction指定信號關聯函數,即用戶指定的信號處理函數。除了可以是用戶自定義的處理函數外,還可以爲SIG_DFL(採用缺省的處理方式),也可以爲SIG_IGN(忽略信號)。

 

2、由_sa_sigaction是指定的信號處理函數帶有三個參數,是爲實時信號而設的(當然同樣支持非實時信號),它指定一個3參數信號處理函數。第一個參數爲信號值,第三個參數沒有使用,第二個參數是指向siginfo_t結構的指針,結構中包含信號攜帶的數據值,參數所指向的結構如下:

siginfo_t {
        int      si_signo;  /* 信號值,對所有信號有意義*/
        int      si_errno;  /* errno值,對所有信號有意義*/
        int      si_code;   /* 信號產生的原因,對所有信號有意義*/
        pid_t    si_pid;    /* 發送信號的進程ID,對kill(2),實時信號以及SIGCHLD有意義 */
        uid_t    si_uid;    /* 發送信號進程的真實用戶ID,對kill(2),實時信號以及SIGCHLD有意義 */
        int      si_status; /* 退出狀態,對SIGCHLD有意義*/
        clock_t  si_utime;  /* 用戶消耗的時間,對SIGCHLD有意義 */
        clock_t  si_stime;  /* 內核消耗的時間,對SIGCHLD有意義 */
        sigval_t si_value;  /* 信號值,對所有實時有意義,是一個聯合數據結構,
                          /*可以爲一個整數(由si_int標示,也可以爲一個指針,由si_ptr標示)*/
	
        void *   si_addr;   /* 觸發fault的內存地址,對SIGILL,SIGFPE,SIGSEGV,SIGBUS 信號有意義*/
        int      si_band;   /* 對SIGPOLL信號有意義 */
        int      si_fd;     /* 對SIGPOLL信號有意義 */
  }

前面在討論系統調用sigqueue發送信號時,sigqueue的第三個參數就是sigval聯合數據結構,當調用sigqueue時,該數據結構中的數據就將拷貝到信號處理函數的第二個參數中。這樣,在發送信號同時,就可以讓信號傳遞一些附加信息。信號可以傳遞信息對程序開發是非常有意義的。

 

3、sa_mask指定在信號處理程序執行過程中,哪些信號應當被阻塞。缺省情況下當前信號本身被阻塞,防止信號的嵌套發送,除非指定SA_NODEFER或者SA_NOMASK標誌位。

注:請注意sa_mask指定的信號阻塞的前提條件,是在由sigaction()安裝信號的處理函數執行過程中由sa_mask指定的信號才被阻塞。

 

4、sa_flags中包含了許多標誌位,包括剛剛提到的SA_NODEFER及SA_NOMASK標誌位。另一個比較重要的標誌位是SA_SIGINFO,當設定了該標誌位時,表示信號附帶的參數可以被傳遞到信號處理函數中,因此,應該爲sigaction結構中的sa_sigaction指定處理函數,而不應該爲sa_handler指定信號處理函數,否則,設置該標誌變得毫無意義。即使爲sa_sigaction指定了信號處理函數,如果不設置SA_SIGINFO,信號處理函數同樣不能得到信號傳遞過來的數據,在信號處理函數中對這些信息的訪問都將導致段錯誤(Segmentation fault)。

 

 

5       信號的發送

發送信號的主要函數有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。

 

5.1    kill()

#include <sys/types.h>

#include <signal.h>

int kill(pid_t pid,int signo)

 

該系統調用可以用來向任何進程或進程組發送任何信號。參數pid的值爲信號的接收進程

pid>0 進程ID爲pid的進程

pid=0 同一個進程組的進程

pid<0 pid!=-1 進程組ID爲 -pid的所有進程

pid=-1 除發送進程自身外,所有進程ID大於1的進程

 

Sinno是信號值,當爲0時(即空信號),實際不發送任何信號,但照常進行錯誤檢查,因此,可用於檢查目標進程是否存在,以及當前進程是否具有向目標發送信號的權限(root權限的進程可以向任何進程發送信號,非root權限的進程只能向屬於同一個session或者同一個用戶的進程發送信號)。

 

Kill()最常用於pid>0時的信號發送。該調用執行成功時,返回值爲0;錯誤時,返回-1,並設置相應的錯誤代碼errno。下面是一些可能返回的錯誤代碼:

EINVAL:指定的信號sig無效。

ESRCH:參數pid指定的進程或進程組不存在。注意,在進程表項中存在的進程,可能是一個還沒有被wait收回,但已經終止執行的僵死進程。

EPERM: 進程沒有權力將這個信號發送到指定接收信號的進程。因爲,一個進程被允許將信號發送到進程pid時,必須擁有root權力,或者是發出調用的進程的UID 或EUID與指定接收的進程的UID或保存用戶ID(savedset-user-ID)相同。如果參數pid小於-1,即該信號發送給一個組,則該錯誤表示組中有成員進程不能接收該信號。

 

5.2    sigqueue()

#include <sys/types.h>

#include <signal.h>

int sigqueue(pid_t pid, int sig, const union sigval val)

調用成功返回 0;否則,返回 -1。

 

sigqueue()是比較新的發送信號系統調用,主要是針對實時信號提出的(當然也支持前32種),支持信號帶有參數,與函數sigaction()配合使用。

sigqueue的第一個參數是指定接收信號的進程ID,第二個參數確定即將發送的信號,第三個參數是一個聯合數據結構union sigval,指定了信號傳遞的參數,即通常所說的4字節值。

typedef union sigval {

               int  sival_int;

               void *sival_ptr;

}sigval_t;

 

sigqueue()比kill()傳遞了更多的附加信息,但sigqueue()只能向一個進程發送信號,而不能發送信號給一個進程組。如果signo=0,將會執行錯誤檢查,但實際上不發送任何信號,0值信號可用於檢查pid的有效性以及當前進程是否有權限向目標進程發送信號。

 

在調用sigqueue時,sigval_t指定的信息會拷貝到對應sig 註冊的3參數信號處理函數的siginfo_t結構中,這樣信號處理函數就可以處理這些信息了。由於sigqueue系統調用支持發送帶參數信號,所以比kill()系統調用的功能要靈活和強大得多。

 

5.3    alarm()

#include <unistd.h>

unsigned int alarm(unsigned int seconds)

系統調用alarm安排內核爲調用進程在指定的seconds秒後發出一個SIGALRM的信號。如果指定的參數seconds爲0,則不再發送 SIGALRM信號。後一次設定將取消前一次的設定。該調用返回值爲上次定時調用到發送之間剩餘的時間,或者因爲沒有前一次定時調用而返回0。

 

注意,在使用時,alarm只設定爲發送一次信號,如果要多次發送,就要多次使用alarm調用。

 

5.4    setitimer()

現在的系統中很多程序不再使用alarm調用,而是使用setitimer調用來設置定時器,用getitimer來得到定時器的狀態,這兩個調用的聲明格式如下:

int getitimer(int which, struct itimerval *value);

int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);

在使用這兩個調用的進程中加入以下頭文件:

#include <sys/time.h>

 

該系統調用給進程提供了三個定時器,它們各自有其獨有的計時域,當其中任何一個到達,就發送一個相應的信號給進程,並使得計時器重新開始。三個計時器由參數which指定,如下所示:

TIMER_REAL:按實際時間計時,計時到達將給進程發送SIGALRM信號。

ITIMER_VIRTUAL:僅當進程執行時才進行計時。計時到達將發送SIGVTALRM信號給進程。

ITIMER_PROF:當進程執行時和系統爲該進程執行動作時都計時。與ITIMER_VIR-TUAL是一對,該定時器經常用來統計進程在用戶態和內核態花費的時間。計時到達將發送SIGPROF信號給進程。

 

定時器中的參數value用來指明定時器的時間,其結構如下:

struct itimerval {

        struct timeval it_interval; /* 下一次的取值 */

        struct timeval it_value; /* 本次的設定值 */

};

 

該結構中timeval結構定義如下:

struct timeval {

        long tv_sec; /* 秒 */

        long tv_usec; /* 微秒,1秒 = 1000000 微秒*/

};

 

在setitimer 調用中,參數ovalue如果不爲空,則其中保留的是上次調用設定的值。定時器將it_value遞減到0時,產生一個信號,並將it_value的值設定爲it_interval的值,然後重新開始計時,如此往復。當it_value設定爲0時,計時器停止,或者當它計時到期,而it_interval 爲0時停止。調用成功時,返回0;錯誤時,返回-1,並設置相應的錯誤代碼errno:

EFAULT:參數value或ovalue是無效的指針。

EINVAL:參數which不是ITIMER_REAL、ITIMER_VIRT或ITIMER_PROF中的一個。

下面是關於setitimer調用的一個簡單示範,在該例子中,每隔一秒發出一個SIGALRM,每隔0.5秒發出一個SIGVTALRM信號:

 

#include <signal.h>

#include <unistd.h>

#include <stdio.h>

#include <sys/time.h>

int sec;

 

void sigroutine(int signo) {

        switch (signo) {

        case SIGALRM:

        printf("Catch a signal -- SIGALRM ");

        break;

        case SIGVTALRM:

        printf("Catch a signal -- SIGVTALRM ");

        break;

        }

        return;

}

 

int main()

{

        struct itimerval value,ovalue,value2;

        sec = 5;

 

        printf("process id is %d ",getpid());

        signal(SIGALRM, sigroutine);

        signal(SIGVTALRM, sigroutine);

 

        value.it_value.tv_sec = 1;

        value.it_value.tv_usec = 0;

        value.it_interval.tv_sec = 1;

        value.it_interval.tv_usec = 0;

        setitimer(ITIMER_REAL, &value, &ovalue);

 

        value2.it_value.tv_sec = 0;

        value2.it_value.tv_usec = 500000;

        value2.it_interval.tv_sec = 0;

        value2.it_interval.tv_usec = 500000;

        setitimer(ITIMER_VIRTUAL, &value2, &ovalue);

 

        for (;;) ;

}

 

該例子的屏幕拷貝如下:

localhost:~$ ./timer_test

process id is 579

Catch a signal – SIGVTALRM

Catch a signal – SIGALRM

Catch a signal – SIGVTALRM

Catch a signal – SIGVTALRM

Catch a signal – SIGALRM

Catch a signal –GVTALRM

 

5.5    abort()

#include <stdlib.h>

void abort(void);

向進程發送SIGABORT信號,默認情況下進程會異常退出,當然可定義自己的信號處理函數。即使SIGABORT被進程設置爲阻塞信號,調用abort()後,SIGABORT仍然能被進程接收。該函數無返回值。

 

5.6    raise()

#include <signal.h>

int raise(int signo)

向進程本身發送信號,參數爲即將發送的信號值。調用成功返回 0;否則,返回 -1。

 

6       信號集及信號集操作函數:

信號集被定義爲一種數據類型:

typedef struct {

                       unsigned long sig[_NSIG_WORDS];

} sigset_t

信號集用來描述信號的集合,每個信號佔用一位。Linux所支持的所有信號可以全部或部分的出現在信號集中,主要與信號阻塞相關函數配合使用。下面是爲信號集操作定義的相關函數:

 

#include <signal.h>

int sigemptyset(sigset_t *set);

int sigfillset(sigset_t *set);

int sigaddset(sigset_t *set, int signum)

int sigdelset(sigset_t *set, int signum);

int sigismember(const sigset_t *set, int signum);

sigemptyset(sigset_t *set)初始化由set指定的信號集,信號集裏面的所有信號被清空;

sigfillset(sigset_t *set)調用該函數後,set指向的信號集中將包含linux支持的64種信號;

sigaddset(sigset_t *set, int signum)在set指向的信號集中加入signum信號;

sigdelset(sigset_t *set, int signum)在set指向的信號集中刪除signum信號;

sigismember(const sigset_t *set, int signum)判定信號signum是否在set指向的信號集中。

 

7       信號阻塞與信號未決:

每個進程都有一個用來描述哪些信號遞送到進程時將被阻塞的信號集,該信號集中的所有信號在遞送到進程後都將被阻塞。下面是與信號阻塞相關的幾個函數:

#include <signal.h>

int  sigprocmask(int  how,  const  sigset_t *set, sigset_t *oldset));

int sigpending(sigset_t *set));

int sigsuspend(const sigset_t *mask));

 

sigprocmask()函數能夠根據參數how來實現對信號集的操作,操作主要有三種:

SIG_BLOCK 在進程當前阻塞信號集中添加set指向信號集中的信號

SIG_UNBLOCK 如果進程阻塞信號集中包含set指向信號集中的信號,則解除對該信號的阻塞

SIG_SETMASK 更新進程阻塞信號集爲set指向的信號集

 

sigpending(sigset_t *set))獲得當前已遞送到進程,卻被阻塞的所有信號,在set指向的信號集中返回結果。

 

sigsuspend(const sigset_t *mask))用於在接收到某個信號之前, 臨時用mask替換進程的信號掩碼, 並暫停進程執行,直到收到信號爲止。sigsuspend 返回後將恢復調用之前的信號掩碼。信號處理函數完成後,進程將繼續執行。該系統調用始終返回-1,並將errno設置爲EINTR。

 

 

8       信號應用實例

linux下的信號應用並沒有想象的那麼恐怖,程序員所要做的最多隻有三件事情:

安裝信號(推薦使用sigaction());

實現三參數信號處理函數,handler(int signal,struct siginfo *info, void *);

發送信號,推薦使用sigqueue()。

實際上,對有些信號來說,只要安裝信號就足夠了(信號處理方式採用缺省或忽略)。其他可能要做的無非是與信號集相關的幾種操作。

 

實例一:信號發送及處理

實現一個信號接收程序sigreceive(其中信號安裝由sigaction())。

#include <signal.h>

#include <sys/types.h>

#include <unistd.h>

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

int main(int argc,char**argv)

{

        struct sigaction act;  

        int sig;

        sig=atoi(argv[1]);

       

        sigemptyset(&act.sa_mask);

        act.sa_flags=SA_SIGINFO;

        act.sa_sigaction=new_op;

       

        if(sigaction(sig,&act,NULL) < 0)

        {

                printf("install sigal error\n");

        }

       

        while(1)

        {

                sleep(2);

                printf("wait for the signal\n");

        }

}

 

void new_op(int signum,siginfo_t *info,void *myact)

{

        printf("receive signal %d", signum);

        sleep(5);

}

說明,命令行參數爲信號值,後臺運行sigreceive signo &,可獲得該進程的ID,假設爲pid,然後再另一終端上運行kill -s signo pid驗證信號的發送接收及處理。同時,可驗證信號的排隊問題。

 

實例二:信號傳遞附加信息

主要包括兩個實例:

向進程本身發送信號,並傳遞指針參數

#include <signal.h>

#include <sys/types.h>

#include <unistd.h>

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

int main(int argc,char**argv)

{

        struct sigaction act;  

        union sigval mysigval;

        int i;

        int sig;

        pid_t pid;         

        char data[10];

        memset(data,0,sizeof(data));

        for(i=0;i < 5;i++)

                data[i]='2';

        mysigval.sival_ptr=data;

       

        sig=atoi(argv[1]);

        pid=getpid();

       

        sigemptyset(&act.sa_mask);

        act.sa_sigaction=new_op;//三參數信號處理函數

        act.sa_flags=SA_SIGINFO;//信息傳遞開關,允許傳說參數信息給new_op

        if(sigaction(sig,&act,NULL) < 0)

        {

                printf("install sigal error\n");

        }

        while(1)

        {

                sleep(2);

                printf("wait for the signal\n");

                sigqueue(pid,sig,mysigval);//向本進程發送信號,並傳遞附加信息

        }

}

void new_op(int signum,siginfo_t *info,void *myact)//三參數信號處理函數的實現

{

        int i;

        for(i=0;i<10;i++)

        {

                printf("%c\n ",(*( (char*)((*info).si_ptr)+i)));

        }

        printf("handle signal %d over;",signum);

}

 

這個例子中,信號實現了附加信息的傳遞,信號究竟如何對這些信息進行處理則取決於具體的應用。

 

不同進程間傳遞整型參數:

把1中的信號發送和接收放在兩個程序中,並且在發送過程中傳遞整型參數。

信號接收程序:

#include <signal.h>

#include <sys/types.h>

#include <unistd.h>

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

int main(int argc,char**argv)

{

        struct sigaction act;

        int sig;

        pid_t pid;         

       

        pid=getpid();

        sig=atoi(argv[1]);     

       

        sigemptyset(&act.sa_mask);

        act.sa_sigaction=new_op;

        act.sa_flags=SA_SIGINFO;

        if(sigaction(sig,&act,NULL)<0)

        {

                printf("install sigal error\n");

        }

        while(1)

        {

                sleep(2);

                printf("wait for the signal\n");

        }

}

void new_op(int signum,siginfo_t *info,void *myact)

{

        printf("the int value is %d \n",info->si_int);

}

 

 

信號發送程序:

命令行第二個參數爲信號值,第三個參數爲接收進程ID。

 

#include <signal.h>

#include <sys/time.h>

#include <unistd.h>

#include <sys/types.h>

main(int argc,char**argv)

{

        pid_t pid;

        int signum;

        union sigval mysigval;

        signum=atoi(argv[1]);

        pid=(pid_t)atoi(argv[2]);

        mysigval.sival_int=8;//不代表具體含義,只用於說明問題

        if(sigqueue(pid,signum,mysigval)==-1)

                printf("send error\n");

        sleep(2);

}

 

 

注:實例2的兩個例子側重點在於用信號來傳遞信息,目前關於在linux下通過信號傳遞信息的實例非常少,倒是Unix下有一些,但傳遞的基本上都是關於傳遞一個整數

 

實例三:信號阻塞及信號集操作

#include "signal.h"

#include "unistd.h"

static void my_op(int);

main()

{

        sigset_t new_mask,old_mask,pending_mask;

        struct sigaction act;

        sigemptyset(&act.sa_mask);

        act.sa_flags=SA_SIGINFO;

        act.sa_sigaction=(void*)my_op;

        if(sigaction(SIGRTMIN+10,&act,NULL))

                printf("install signal SIGRTMIN+10 error\n");

        sigemptyset(&new_mask);

        sigaddset(&new_mask,SIGRTMIN+10);

        if(sigprocmask(SIG_BLOCK, &new_mask,&old_mask))

                printf("block signal SIGRTMIN+10 error\n");

        sleep(10);

        printf("now begin to get pending mask and unblock SIGRTMIN+10\n");

        if(sigpending(&pending_mask)<0)

                printf("get pending mask error\n");

        if(sigismember(&pending_mask,SIGRTMIN+10))

                printf("signal SIGRTMIN+10 is pending\n");

        if(sigprocmask(SIG_SETMASK,&old_mask,NULL)<0)

                printf("unblock signal error\n");

        printf("signal unblocked\n");

        sleep(10);

}

 

static void my_op(int signum)

{

        printf("receive signal %d \n",signum);

}

 

編譯該程序,並以後臺方式運行。在另一終端向該進程發送信號(運行kill -s 42 pid,SIGRTMIN+10爲42),查看結果可以看出幾個關鍵函數的運行機制,信號集相關操作比較簡單。

 

9       參考鳴謝:

linux信號處理機制(詳解),http://www.zxbc.cn/html/20080712/61613.html

Linux環境進程間通信(二): 信號(上),鄭彥興 ([email protected])

signal、sigaction、kill等手冊,最直接而可靠的參考資料。

http://www.linuxjournal.com/modules.php?op=modload&name=NS-help&file=man提供了許多系統調用、庫函數等的在線指南。

http://www.opengroup.org/onlinepubs/007904975/可以在這裏對許多關鍵函數(包括系統調用)進行查詢,非常好的一個網址

進程間通信信號(上) http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html

進程間通信信號(下)http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index2.html

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