天天看點

關于Linux中信号的了解1       信号本質2       信号的種類3       信号處理流程4       信号的安裝5       信号的發送6       信号集及信号集操作函數:7       信号阻塞與信号未決:8       信号應用執行個體

最近接觸到linux中的信号的概念,開始了解的很蒙,因為之前一直使用RTOS,而RTOS中有信号量,開始總把這兩個混在一起,以為是類似的東西,通過學習發現這是兩個不一樣的東西。

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       信号處理流程

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

1 信号誕生

2 信号在程序中注冊

3 信号的執行和登出

3.1    信号誕生

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

這裡按發出信号的原因簡單分類,以了解各種信号:

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

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

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

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

(5) 在使用者态下的程序發出的信号。如程序調用系統調用kill向其他程序發送信号。

(6) 與終端互動相關的信号。如使用者關閉一個終端,或按下break鍵等情況。

(7) 跟蹤程序執行的信号。

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。

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

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; 

                  int      si_code;  

                               union{                              

                                       //確定配置設定足夠大的存儲空間

                                       int _pad[SI_PAD_SIZE];

                                       //對SIGKILL有意義的結構

                                       struct{

                                                      ...

                                                 }...

                                               ... ...

                                               ... ...                               

                                       //對SIGILL, SIGFPE, SIGSEGV, SIGBUS有意義的結構

                                  struct{

                                                      ...

                                                 }...

                                               ... ...

                                         }

}

前面在讨論系統調用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;

};

在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),檢視結果可以看出幾個關鍵函數的運作機制,信号集相關操作比較簡單。

繼續閱讀