歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> Linux信號編程實踐(三)信號在內核中的表示(sigaction&sigqueue)

Linux信號編程實踐(三)信號在內核中的表示(sigaction&sigqueue)

日期:2017/3/1 12:25:08   编辑:關於Linux

信號在內核中的表示

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

\

1)block集(阻塞集、屏蔽集):一個進程所要屏蔽的信號,在對應要屏蔽的信號位置1

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

3)handler(信號處理函數集):表示每個信號所對應的信號處理函數,當信號不在未決集中時,將被調用。

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

5)block狀態字用戶可以讀寫,pending狀態字用戶只能讀;這是信號設計機制。

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

#include   
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   
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\

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

在接下來的例子中,我們先屏蔽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\

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

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

Sigaction

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

#include   
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   
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;
}
Copyright © Linux教程網 All Rights Reserved