Linux系統程式設計(24)——訊號的生命週期

尹成發表於2014-07-26

 

訊號生命週期為從訊號傳送到訊號處理函式的執行完畢。

對於一個完整的訊號生命週期(從訊號傳送到相應的處理函式執行完畢)來說,可以分為三個重要的階段,這三個階段由四個重要事件來刻畫:訊號誕生;訊號在程式中註冊完畢;訊號在程式中的登出完畢;訊號處理函式執行完畢。相鄰兩個事件的時間間隔構成訊號生命週期的一個階段。

下面闡述四個事件的實際意義:

1、訊號"誕生"。訊號的誕生指的是觸發訊號的事件發生(如檢測到硬體異常、定時器超時以及呼叫訊號傳送函式kill()或sigqueue()等)。

2、訊號在目標程式中"註冊";程式的task_struct結構中有關於本程式中未決訊號的資料成員:

struct sigpending pending:
struct sigpending{
         structsigqueue *head, **tail;
         sigset_tsignal;
};


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

struct sigqueue{
         structsigqueue *next;
         siginfo_tinfo;
}


 

訊號在程式中註冊指的就是訊號值加入到程式的未決訊號集中(sigpending結構的第二個成員sigset_t signal),並且訊號所攜帶的資訊被保留到未決訊號資訊鏈的某個sigqueue結構中。只要訊號在程式的未決訊號集中,表明程式已經知道這些訊號的存在,但還沒來得及處理,或者該訊號被程式阻塞。

注意:

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

當一個非實時訊號傳送給一個程式時,如果該訊號已經在程式中註冊,則該訊號將被丟棄,造成訊號丟失。因此,非實時訊號又叫做"不可靠訊號"。這意味著同一個非實時訊號在程式的未決訊號資訊鏈中,至多佔有一個sigqueue結構(一個非實時訊號誕生後,(1)、如果發現相同的訊號已經在目標結構中註冊,則不再註冊,對於程式來說,相當於不知道本次訊號發生,訊號丟失;(2)、如果程式的未決訊號中沒有相同訊號,則在程式中註冊自己)。

3、訊號在程式中的登出。在目標程式執行過程中,會檢測是否有訊號等待處理(每次從系統空間返回到使用者空間時都做這樣的檢查)。如果存在未決訊號等待處理且該訊號沒有被程式阻塞,則在執行相應的訊號處理函式前,程式會把訊號在未決訊號鏈中佔有的結構卸掉。是否將訊號從程式未決訊號集中刪除對於實時與非實時訊號是不同的。對於非實時訊號來說,由於在未決訊號資訊鏈中最多隻佔用一個sigqueue結構,因此該結構被釋放後,應該把訊號在程式未決訊號集中刪除(訊號登出完畢);而對於實時訊號來說,可能在未決訊號資訊鏈中佔用多個sigqueue結構,因此應該針對佔用sigqueue結構的數目區別對待:如果只佔用一個sigqueue結構(程式只收到該訊號一次),則應該把訊號在程式的未決訊號集中刪除(訊號登出完畢)。否則,不應該在程式的未決訊號集中刪除該訊號(訊號登出完畢)。

程式在執行訊號相應處理函式之前,首先要把訊號在程式中登出。

4、訊號生命終止。程式登出訊號後,立即執行相應的訊號處理函式,執行完畢後,訊號的本次傳送對程式的影響徹底結束。

注意:

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

在訊號被登出到相應的訊號處理函式執行完畢這段時間內,如果程式又收到同一訊號多次,則對實時訊號來說,每一次都會在程式中註冊;而對於非實時訊號來說,無論收到多少次訊號,都會視為只收到一個訊號,只在程式中註冊一次。

 


linux下的訊號應用並沒有想象的那麼恐怖,我們所要做的最多隻有三件事情:

1、安裝訊號(推薦使用sigaction());

2、實現三引數訊號處理函式,handler(intsignal,struct siginfo *info, void *);

3、傳送訊號,推薦使用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)
{
         structsigaction act;        
         intsig;
         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("installsigal error\n");
         }
        
         while(1)
         {
                   sleep(2);
                   printf("waitfor the signal\n");
         }
}
void new_op(int signum,siginfo_t *info,void*myact)
{
         printf("receivesignal %d", signum);
         sleep(5);
}


說明,命令列引數為訊號值,後臺執行sigreceive signo &,可獲得該程式的ID,假設為pid,然後再另一終端上執行kill -s signo pid驗證訊號的傳送接收及處理。同時,可驗證訊號的排隊問題。

注:可以用sigqueue實現一個命令列訊號傳送程式sigqueuesend。

下面我們實現訊號傳遞附加資訊。向程式本身傳送訊號,並傳遞指標引數:

#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
         structsigaction act;        
         unionsigval mysigval;
         inti;
         intsig;
         pid_tpid;         
         chardata[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;//資訊傳遞開關
         if(sigaction(sig,&act,NULL)< 0)
         {
                   printf("installsigal error\n");
         }
         while(1)
         {
                   sleep(2);
                   printf("waitfor the signal\n");
                   sigqueue(pid,sig,mysigval);//向本程式傳送訊號,並傳遞附加資訊
         }
}
void new_op(int signum,siginfo_t *info,void*myact)//三引數訊號處理函式的實現
{
         inti;
         for(i=0;i<10;i++)
         {
                   printf("%c\n",(*( (char*)((*info).si_ptr)+i)));
         }
         printf("handlesignal %d over;",signum);
}


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

 

 

 

相關文章