1. 程式人生 > >Linux訊號程式設計實踐(三) 訊號在核心中的表示(sigaction&sigqueue)

Linux訊號程式設計實踐(三) 訊號在核心中的表示(sigaction&sigqueue)

訊號在核心中的表示

    實際執行訊號的處理動作稱為訊號遞達(Delivery),訊號從產生到遞達之間的狀態,稱為訊號未決(Pending)。程序可以選擇阻塞(Block)某個訊號。被阻塞的訊號產生時將保持在未決狀態,直到程序解除對此訊號的阻塞,才執行遞達的動作。注意,阻塞和忽略是不同的,只要訊號被阻塞就不會遞達,而忽略是在遞達之後可選的一種處理動作。訊號在核心中的表示可以看作是這樣的:


1)block集(阻塞集、遮蔽集):一個程序所要遮蔽的訊號,在對應要遮蔽的訊號位置1

2)pending集(未決訊號集):如果某個訊號在程序的阻塞集中,則也在未決集中對應位置1,表示該訊號不能被遞達,不會被處理

3)handler(訊號處理函式集):表示每個訊號所對應的訊號處理函式,當訊號不在未決集中時,將被呼叫。

4)block狀態字、pending狀態字均64位(bit);

5)block狀態字使用者可以讀寫,pending狀態字使用者只能讀;這是訊號設計機制

那麼我們該如何對訊號的遮蔽字狀態進行改變和讀取呢?接下來我們介紹一組訊號集操作函式

#include <signal.h>  
int sigemptyset(sigset_t *set); //把訊號集清零;(64bit/8=8位元組)  
int sigfillset(sigset_t *set);  //把訊號集64bit全部置為1  
int sigaddset(sigset_t *set, int signo);    //根據signo,把訊號集中的對應位置成1  
int sigdelset(sigset_t *set, int signo);    //根據signo,把訊號集中的對應位置成0  
int sigismember(const sigset_t *set, int signo);    //判斷signo是否在訊號集中  

sigprocmask    功能:讀取或者更改程序的訊號遮蔽字(Block)

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

  返回值:若成功則為0,若出錯則為-1

  讀取:如果oset是非空指標,則讀取程序的當前訊號遮蔽字通過oset引數傳出。

  更改:如果set是非空指標,則更改程序的訊號遮蔽字,引數how指示如何更改。如果oset和set都是非空指標,則先將原來的訊號遮蔽字備份到oset裡,然後根據set和how引數更改訊號遮蔽字。假設當前的訊號遮蔽字為mask,下表說明了how引數的可選值。

sigpending獲取訊號未決狀態字(pending)資訊,儲存至set態,NSIG訊號的最大值=64。

#include <signal.h>  
int sigpending(sigset_t *set);  

sigismember函式

    用來測試引數signum 代表的訊號是否已加入至引數set訊號集裡。如果訊號集裡已有該訊號則返回1,否則返回0。如果有錯誤則返回-1。出錯的情況及其錯誤程式碼見下:

EFAULT 引數set指標地址無法存取
EINVAL 引數signum 非合法的訊號編號

int sigismember(const sigset_t *set,int signum);
我們註冊一個SIGINT訊號,打印出pending的狀態,結果如下:
void handler(int sig)
{
        printf("recv a sig=%d\n",sig);
}
void printsigset(sigset_t *set)
{
        int i;
        for(i=1;i<NSIG;i++)
        {
                if(sigismember(set,i))
                        putchar('1'); //打印出未決態
                else
                        putchar('0');
        }
        printf("\n");
}
int main()
{
        sigset_t pset;
        if(signal(SIGINT,handler)==SIG_ERR)
                ERR_EXIT("signal error!");
        while(1)
        {
                sigpending(&pset);
                printsigset(&pset);
                sleep(1);
        }
        return 0;
 
}

訊號沒有阻塞,不會發生未決狀態,直接遞達。

     在接下來的例子中,我們先遮蔽SIGINT訊號, 但是如果該程序接收到了SIGQUIT訊號, 則將對SIGINT訊號的遮蔽節解除,當然,我們需要先註冊SIGINT和SIGQUIT訊號。

/*開始阻塞訊號的程式,產生未決狀態*/
void handler(int sig)
{
        if(sig==SIGINT)
                printf("recv a sig=%d\n",sig);
        else if(sig==SIGQUIT) //解除SIGINT的遮蔽
        {
                sigset_t uset;
                sigemptyset(&uset);
                sigaddset(&uset,SIGINT);
                sigprocmask(SIG_UNBLOCK,&uset,NULL);
 
        }
//        printf("recv a sig=%d\n",sig);
}
void printsigset(sigset_t *set)
{
        int i;
        for(i=1;i<NSIG;i++)
        {
                if(sigismember(set,i))
                        putchar('1');
                else
                        putchar('0');
        }
        printf("\n");
}
int main()
{
        sigset_t pset;
        sigset_t bset;
        sigemptyset(&bset);
        sigaddset(&bset,SIGINT);
        if(signal(SIGINT,handler)==SIG_ERR)
                ERR_EXIT("signal error!");
        if(signal(SIGQUIT,handler)==SIG_ERR)
                ERR_EXIT("signal error!");
        sigprocmask(SIG_BLOCK,&bset,NULL);//遮蔽SIGINT訊號
        while(1)
        {
                sigpending(&pset);
                printsigset(&pset);
                sleep(1);
        }
        return 0;
 
}

    當我們按下ctrl+c產生訊號時,訊號被阻塞,處於未決狀態。接收到SIGQUIT訊號時,解除阻塞,進入遞達狀態,但是隻會對訊號做出一次反應,即使你按了很多次ctrl+c,原因就在於,SIGINT是不可靠訊號,不支援排隊,只保留了一個。

    如果我們採用實時訊號的話,例如SIGRTMIN,那麼對訊號來說是支援排隊的,不會發生丟失的情況,在解除阻塞後,會對每個訊號做出處理。

Sigaction

前面我們講過了使用signal安裝不可靠訊號,雖然signal不如sigaction功能豐富,但是也可以安裝可靠訊號;

#include <signal.h>  
int sigaction(int signum, const struct sigaction *act,  
                     struct sigaction *oldact);  

功能:

   sigaction函式用於改變程序接收到特定訊號後的行為。


簡而言之引數就是(訊號,指標,原行為)

關於sigaction結構體

第二個引數最為重要,其中包含了對指定訊號的處理、訊號所傳遞的資訊、訊號處理函式執行過程中應遮蔽掉哪些函式等

struct sigaction {  
//訊號處理程式 不接受額外資料(比較過時)  
    void (*sa_handler)(int);  
  
//訊號處理程式能接受額外資料,和sigqueue配合使用(支援訊號排隊,訊號傳送其他資訊),推薦使用  
    void (*sa_sigaction)(int, siginfo_t *, void *);           
  
sigset_t sa_mask;   //遮蔽  
    int sa_flags;       //表示訊號的行為:SA_SIGINFO表示能接受資料  
    void (*sa_restorer)(void); //廢棄不用了  
};  

      sa_handler的原型是一個引數為int,返回型別為void的函式指標。引數即為訊號值,所以訊號不能傳遞除訊號值之外的任何資訊;

  sa_sigaction的原型是一個帶三個引數,型別分別為int,struct siginfo *,void *,返回型別為void的函式指標。第一個引數為訊號值;第二個引數是一個指向struct siginfo結構的指標,此結構中包含訊號攜帶的資料值;第三個引數沒有使用。

  sa_mask指定在訊號處理程式執行過程中,哪些訊號應當被阻塞。預設當前訊號本身被阻塞

  sa_flags包含了許多標誌位,比較重要的一個是SA_SIGINFO,當設定了該標誌位時,表示訊號附帶的引數可以傳遞到訊號處理函式中。即使sa_sigaction指定訊號處理函式,如果不設定SA_SIGINFO,訊號處理函式同樣不能得到訊號傳遞過來的資料,在訊號處理函式中對這些資訊的訪問都將導致段錯誤。

  sa_restorer已過時,POSIX不支援它,不應再使用。

注意:回撥函式sa_handler和sa_sigaction只能選一個

  因此,當你的訊號需要接收附加資訊的時候,你必須給sa_sigaction賦訊號處理函式指標,同時還要給sa_flags賦SA_SIGINFO,

例項1:利用sigaction實現了signal函式的功能
__sighandler_t my_signal(int sig,__sighandler_t handler)
{
        struct sigaction act;
        struct sigaction oldact;
        act.sa_handler=handler;
        sigemptyset(&act.sa_mask);
        act.sa_flags=0;
        if(sigaction(sig,&act,&oldact)<0)
                return SIG_ERR;
        return oldact.sa_handler;
}
void handler(int sig)
{
        printf("recv a sig=%d\n",sig);
}
int main()
{
  /*      struct sigaction act;
        act.sa_handler=handler;
        sigemptyset(&act.sa_mask);
        act.sa_flags=0;
        if(sigaction(SIGINT,&act,NULL)<0)
                ERR_EXIT("sigaction error\n");
*/
        my_signal(SIGINT,handler);
 
        while(1)
                pause();
        return 0;
 
}
sa_mask選項

 在執行handler 的時候, 如果此時程序收到了sa_mask所包含的訊號, 則這些訊號將不會被響應, 直到handler函式執行完畢。

 sigprocmask使其即使發生了也不能遞達,但是sa_mask 僅是在處理handler是遮蔽外來的訊號;兩者的區別還是要好好搞一搞的。

void handler(int sig)
{
        printf("recv a sig=%d\n",sig);
        sleep(5);
}
int main()
{
        struct sigaction act;
        act.sa_handler=handler;
        sigemptyset(&act.sa_mask);
        sigaddset(&act.sa_mask,SIGQUIT);//遮蔽SIGQUIT訊號
        act.sa_flags=0;
        if(sigaction(SIGINT,&act,NULL)<0)
                ERR_EXIT("sigaction error\n");
 
        while(1)
                pause();
        return 0;
 
}

在響應SIGINT訊號即handler處理時,SIGQUIT暫時被遮蔽,但是一旦handler函式處理完後,立即對SIGQUIT進行響應。

siginfo_t結構:

siginfo_t{  
    int      si_signo;    /* Signal number */  
    int      si_errno;    /* An errno value */  
    int      si_code;     /* Signal code */  
    int      si_trapno;   /* Trap number that caused 
                                        hardware-generated signal 
                                        (unused on most architectures) */  
    pid_t    si_pid;      /* Sending process ID */  
    uid_t    si_uid;      /* Real user ID of sending process */  
    int      si_status;   /* Exit value or signal */  
    clock_t  si_utime;    /* User time consumed */  
    clock_t  si_stime;    /* System time consumed */  
    sigval_t si_value;    /* Signal value */  
    int      si_int;      /* POSIX.1b signal */  
    void    *si_ptr;      /* POSIX.1b signal */  
    int      si_overrun;  /* Timer overrun count; POSIX.1b timers */  
    int      si_timerid;  /* Timer ID; POSIX.1b timers */  
    void    *si_addr;     /* Memory location which caused fault */  
    long     si_band;     /* Band event (was int in 
                                        glibc 2.3.2 and earlier) */  
    int      si_fd;       /* File descriptor */  
    short    si_addr_lsb; /* Least significant bit of address 
                                        (since Linux 2.6.32) */  
}  

sigqueue

#include <signal.h>  
int sigqueue(pid_t pid, int sig, const union sigval value);  

功能

   sigqueue是新的傳送訊號系統呼叫,主要是針對實時訊號提出的支援訊號帶有引數,與函式sigaction()配合使用

   和kill函式相比多了一個引數:const union sigval value(int kill(pid_t pid, int sig)),因此sigqueue()可以比kill()傳遞更多的資訊,但sigqueue()只能向一個程序傳送訊號,而不能傳送訊號給一個程序組。

引數

   引數1是指定接收訊號的程序id,引數2確定即將傳送的訊號;

   引數3是一個聯合資料結構union sigval,指定了訊號傳遞的引數,即通常所說的4位元組值。

  注意:要想在程序間通訊的話,sa_flags要置為SA_SIGINFO

sigval聯合體

typedef union sigval{  
    int sival_int;  
    void *sival_ptr;  
} sigval_t;  
接下來我們模擬一下程序間通訊的例項:

先執行hello開啟接收,然後使用send傳送訊號;通過這種方式可以達到程序見通訊的目的。

Hello.c
void handler(int sig,siginfo_t *info,void *ctx)
{
 
        printf("recv a sig=%d data=%d\n",sig,info->si_value.sival_int);
         
}
int main()
{
        struct sigaction act;
        act.sa_sigaction=handler;
        sigemptyset(&act.sa_mask);
        act.sa_flags=SA_SIGINFO;
        if(sigaction(SIGINT,&act,NULL)<0)
                ERR_EXIT("sigaction error\n");
 
        while(1)
                pause();
        return 0;
 
}

Send
int main(int argc,char *argv[])
{
        if(argc!=2)
        {
                fprintf(stderr,"Usage %s pid\n",argv[0]);
                exit(EXIT_FAILURE);
        }
        pid_t pid=atoi(argv[1]);
        union sigval v;
        v.sival_int=100;
        sigqueue(pid,SIGINT,v);
        return 0;
}