linux系統程式設計之訊號(五):訊號集操作函式,訊號阻塞與未決

mickole發表於2013-07-15

一,訊號集及相關操作函式

訊號集被定義為一種資料型別:

typedef struct {

                       unsigned long sig[_NSIG_WORDS];

} sigset_t

訊號集用來描述訊號的集合,每個訊號佔用一位(64位)。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指定的訊號集,訊號集裡面的所有訊號被清空,相當於64為置0;

sigfillset(sigset_t *set)呼叫該函式後,set指向的訊號集中將包含linux支援的64種訊號,相當於64為都置1;

sigaddset(sigset_t *set, int signum)在set指向的訊號集中加入signum訊號,相當於將給定訊號所對應的位置1;

sigdelset(sigset_t *set, int signum)在set指向的訊號集中刪除signum訊號,相當於將給定訊號所對應的位置0;

sigismember(const sigset_t *set, int signum)判定訊號signum是否在set指向的訊號集中,相當於檢查給定訊號所對應的位是0還是1。

示例程式:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
void print_sigset(sigset_t *set);
int main(void)
{
    sigset_t myset;
    sigemptyset(&myset);
    sigaddset(&myset,SIGINT);
    sigaddset(&myset,SIGQUIT);
    sigaddset(&myset,SIGUSR1);
    sigaddset(&myset,SIGRTMIN);
    print_sigset(&myset);

    return 0;

}
void print_sigset(sigset_t *set)
{
    int i;
    for(i = 1; i < NSIG; ++i){
        if(sigismember(set,i))
            printf("1");
        else
            printf("0");
    }
    putchar('\n');
}

結果:

QQ截圖20130715145238

可以看到新增訊號的相應位置1.

二,訊號阻塞與未決

man幫助說明:

Signal mask and pending signals
       A signal may be blocked, which means that it will not be delivered
       until it is later unblocked.  Between the time when it is generated
       and when it is delivered a signal is said to be pending.

       Each thread in a process has an independent signal mask, which
       indicates the set of signals that the thread is currently blocking.
       A thread can manipulate its signal mask using pthread_sigmask(3).  In
       a traditional single-threaded application, sigprocmask(2) can be used
       to manipulate the signal mask.
執行訊號的處理動作稱為訊號遞達(Delivery),訊號從產生到遞達之間的狀態,稱為訊號未決(Pending)。程式可以選擇阻塞(Block)某個訊號。被阻塞的訊號產生時將保持在未決狀態,直到程式解除對此訊號的阻塞,才執行遞達的動作。注意,阻塞和忽略是不同的,只要訊號被阻塞就不會遞達,而忽略是在遞達之後可選的一種處理動作。每個程式都有一個用來描述哪些訊號遞送到程式時將被阻塞的訊號集,該訊號集中的所有訊號在遞送到程式後都將被阻塞。
訊號在核心中的表示可以看作是這樣的:
QQ截圖20130715150052
看圖說話:
block集(阻塞集、遮蔽集):一個程式所要遮蔽的訊號,在對應要遮蔽的訊號位置1
pending集(未決訊號集):如果某個訊號在程式的阻塞集中,則也在未決集中對應位置1,表示該訊號不能被遞達,不會被處理
handler(訊號處理函式集):表示每個訊號所對應的訊號處理函式,當訊號不在未決集中時,將被呼叫
 
以下是與訊號阻塞及未決相關的函式操作:

#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指向訊號集中的訊號,相當於:mask=mask|set
  • SIG_UNBLOCK 如果程式阻塞訊號集中包含set指向訊號集中的訊號,則解除對該訊號的阻塞,相當於:mask=mask|~set
  • SIG_SETMASK 更新程式阻塞訊號集為set指向的訊號集,相當於mask=set

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

sigsuspend(const sigset_t *mask))用於在接收到某個訊號之前, 臨時用mask替換程式的訊號掩碼, 並暫停程式執行,直到收到訊號為止。

sigsuspend 返回後將恢復呼叫之前的訊號掩碼。訊號處理函式完成後,程式將繼續執行。該系統呼叫始終返回-1,並將errno設定為EINTR。

示例程式:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int 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(int argc, char *argv[])
{
    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);//將訊號加入程式阻塞集中
    for (;;)
    {
        sigpending(&pset);
        printsigset(&pset);
        sleep(1);
    }
    return 0;
}

void handler(int sig)
{
    if (sig == SIGINT)
        printf("recv a sig=%d\n", sig);
    else if (sig == SIGQUIT)
    {
        sigset_t uset;
        sigemptyset(&uset);
        sigaddset(&uset, SIGINT);
        sigprocmask(SIG_UNBLOCK, &uset, NULL);
    }
}

結果:

QQ截圖20130715152818

    說明:程式首先將SIGINT訊號加入程式阻塞集(遮蔽集)中,一開始並沒有傳送SIGINT訊號,所以程式未決集中沒有處於未決態的訊號,當我們連續按下ctrl+c時,向程式傳送SIGINT訊號,由於SIGINT訊號處於程式的阻塞集中,所以傳送的SIGINT訊號不能遞達,也是就是處於未決狀態,所以當我列印未決集合時發現SIGINT所對應的位為1,現在我們按下ctrl+\,傳送SIGQUIT訊號,由於此訊號並沒被程式阻塞,所以SIGQUIT訊號直接遞達,執行對應的處理函式,在該處理函式中解除程式對SIGINT訊號的阻塞,所以之前傳送的SIGINT訊號遞達了,執行對應的處理函式,但由於SIGINT訊號是不可靠訊號,不支援排隊,所以最終只有一個訊號遞達。

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <string.h>
#include <unistd.h>

/* 版本1, 可靠訊號將被遞送多次 */
#define MYSIGNAL SIGRTMIN+5
/* 版本2, 不可靠訊號只被遞送一次 */
//#define MYSIGNAL SIGTERM

void sig_handler(int signum)
{
    psignal(signum, "catch a signal");
}

int main(int argc, char **argv)
{
    sigset_t block, pending;
    int sig, flag;

    /* 設定訊號的handler */
    signal(MYSIGNAL, sig_handler);

    /* 遮蔽此訊號 */
    sigemptyset(&block);
    sigaddset(&block, MYSIGNAL);
    printf("block signal\n");
    sigprocmask(SIG_BLOCK, &block, NULL);

    /* 發兩次訊號, 看訊號將會被觸發多少次 */
    printf("---> send a signal --->\n");
    kill(getpid(), MYSIGNAL);
    printf("---> send a signal --->\n");
    kill(getpid(), MYSIGNAL);

    /* 檢查當前的未決訊號 */
    flag = 0;
    sigpending(&pending);
    for (sig = 1; sig < NSIG; sig++) {
        if (sigismember(&pending, sig)) {
            flag = 1;
            psignal(sig, "this signal is pending");
        } 
    }
    if (flag == 0) {
        printf("no pending signal\n");
    }

    /* 解除此訊號的遮蔽, 未決訊號將被遞送 */
    printf("unblock signal\n");
    sigprocmask(SIG_UNBLOCK, &block, NULL);

    /* 再次檢查未決訊號 */
    flag = 0;
    sigpending(&pending);
    for (sig = 1; sig < NSIG; sig++) {
        if (sigismember(&pending, sig)) {
            flag = 1;
            psignal(sig, "a pending signal");
        } 
    }
    if (flag == 0) {
        printf("no pending signal\n");
    }

    return 0;
}

結果:

QQ截圖20130715154540

兩次執行結果不同:第一次連續傳送兩次不可靠訊號,最後解除阻塞時,只有一個遞達,說明不可靠訊號不支援排隊。

第二次執行時,連續兩次傳送可靠訊號,解除阻塞後,都遞達,說明可靠訊號支援排隊。

ok,這節就寫到這吧

 

 

 

相關文章