linux信號集與信號屏蔽字

信號傳遞過程

  • 信號源產生信號,由內核向進程發送信號
  • 進程選擇是否阻塞進程,若阻塞,則信號進入阻塞信號列表,只有當解除阻塞後,進程才接收該信號,若一直不接觸,內核則將該信號從阻塞列表中移除並丟棄;若不阻塞,則進程接收信號
  • 進程接收信號後,進程可屏蔽該信號,或者執行用戶編寫的處理函數,或者執行默認動作

    以上便是linux進程對信號的處理過程,如果你足夠細心,你會提出疑問:我如何才能讓進程對一個信號進行處理呢?這也就是我們這篇博客要解決的問題

一些名詞

  • 信號集
    信號集顧名思義,就是信號的集合。在linux中,它的類型是sigset_t,大小是64bits
    此時你應該又有疑問了:爲何是64bits?原因很簡單,因爲目前linux流行版本一共有64個信號(不同版本信號格式不同),我們一個bit來表示一種信號,一共只需要64bits就行啦!!!
    那麼信號集的作用到底是什麼呢?信號集一共有64bits,自然,第一位就是1號信號(SIGHUP),第二位就是2號信號(SIGINT)….以此類推(我們假設位號是從1開始,而不是從0開始)。那麼,假如1號位如果是1,則表示1號信號已經註冊到該信號集中;如果是0,就是爲註冊到該信號集中咯!爲什麼要把信號註冊到信號集中呢?當然是爲了批量管理信號!!!具體的用法,看到下面你就知道啦

  • 抵達(delivery) && 未決(pending)
    抵達:執行信號的處理動作叫抵達。也就是說信號被進程接收啦!抵達包括:忽略執行默認動作執行處理函數
    未決:信號從產生到抵達直接的狀態。說白了未決就是未抵達。自然,一個信號被阻塞啦,那麼該信號就是處於未決狀態。

  • 信號屏蔽狀態字(block) && 信號未決狀態字(pending)
    在進程控制塊(PCB)中的結構體中,有三個比較重要的變量,分別是:信號屏蔽狀態字信號未決狀態字是否忽略標誌
    信號屏蔽狀態字(block):64bits,每一位代表該進程對對應號碼的信號是否屏蔽:1是屏蔽,0是不屏蔽
    信號未決狀態字(pending):64bits,每一位代表該進程對對應號碼的信號的狀態:1是未決,0是不抵達
    是否忽略標誌:這裏我們不討論

需要注意的是:這些變量之間也存在一些關係,比如:進程將信號屏蔽字的2號爲置爲1,也就是說屏蔽SIGINT信號,那麼但你向進程發送該信號時(ctrl+c),該信號必然處於未決狀態。那麼,信號未決狀態字的2號位自然也就是1啦。

信號集操作函數

POSIX.1 定義了一個數據類型sigset_t,用於表示信號集。另外,頭文件 signal.h 提供了下列五個處理信號集的函數

  • sigemptyset 初始化set所指向的信號集,清除裏面所有已經註冊的信號,即將所有位置0
    int sigemptyset(sigset_t *set);
    返回值:若成功則返回0,若出錯則返回-1

  • sigfillset 初始化由 set 指向的信號集,使其包含所有信號。即將所有位置1
    int sigfillset(sigset_t *set);
    返回值:若成功則返回0,若出錯則返回-1

  • sigaddset 將一個信號 signo 添加到現有信號集 set 中。即將該信號對應的位置1
    int sigaddset(sigset_t *set, int signo);
    返回值:若成功則返回0,若出錯則返回-1

  • sigdelset 將一個信號 signo 從信號集 set 中刪除。即將該信號對應的位置0
    int sigdelset(sigset_t *set, int signo);
    返回值:若成功則返回0,若出錯則返回-1

  • sigismember 判斷指定信號 signo 是否在信號集 set 中。
    int sigismember(const sigset_t *set, int signo);
    返回值:若真則返回1,若假則返回0,若出錯則返回-1

這些函數的具體運用會在下面的代碼中

sigprocmask && 設置進程的信號屏蔽字

  • sigprocmask 函數可以檢測或者設置進程的信號屏蔽字。
    #include <signal.h>
    int sigprocmask(int how, const sigset_t * set, sigset_t * oset);
    返回值:若成功則返回0,若出錯則返回-1

  • 參數說明
    set:如果set是非空指針,則更改進程的信號屏蔽字
    oset:如果oset是非空指針,則讀取進程的當前信號屏蔽字通過oset參數傳出
    how:參數how指示如何更改
    注意如果set爲空,那how參數顯然也沒有意義啦,信號集都沒有,我哪知道要改哪個信號的屏蔽狀態字!!!

  • how 參數可選值

how 說明
SIG_BLOCK 該進程新的信號屏蔽字是其當前信號屏蔽字和 set 指向信號集的並集。set 包含了我們希望阻塞的信號。
SIG_UNBLOCK 該進程的信號屏蔽字是當前信號屏蔽字和 set 所指向信號集補給的交集。set 包含了我們希望解除阻塞的信號。
SIG_SETMASK 設置當前信號屏蔽字設置爲 set 所指向的信號集。

上面的東西是不是不是很懂,那麼我們通俗點講

how 說明
SIG_BLOCK 把set信號集裏面的信號全部設置爲阻塞
SIG_UNBLOCK 把set信號集裏面的信號全部解除阻塞
SIG_SETMASK 把set信號集裏面的信號全部設置爲阻塞或者解除阻塞(當前信號屏蔽字可能是阻塞,,也可能是接觸阻塞)
  • 下面我們來看一個具體的例子
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>

void handler()
{
    printf("SIG_INT respond\n");
    return;
}

int main()
{
    char tmp = 'a';
    sigset_t bset; //用來設置阻塞的信號集

    sigemptyset(&bset); //清空信號集
    sigaddset(&bset, SIG_INT); //將SIG_INT信號添加到信號集中

    if(signal(SIG_INT, handler) == SiG_ERR)//註冊安裝處理函數
        perror("signal err:");

    sigprocmask(SIG_BLOCK, &bset, NULL); //阻塞SIG_INT信號

    while(tmp != 'q' )
    {
        tmp = getchar();
    }
    sigprocmask(SIG_UNBLOCK, &bset, NULL);//解鎖阻塞

    pause();
    return 0;
}


編譯運行此函數,按一下步驟輸入:
程序運行到getchar(), 輸入ctrl+c,此時並未看見執行信號處理函數
輸入'q',退出循環,再按ctrl+c,看見控制端打印SIG_INT respond

sigpending 獲取進程未決的信號集
  • sigpending 獲取當前進程所有未決的信號。通過其 set 參數返回未決的信號集。
    #include <signal.h>
    int sigpending(sigset_t *set);
    返回值:若成功則返回0,若出錯則返回-1

通俗地將,就是:一旦調用sigpending函數,那麼set信號集中,處於未決狀態的信號對應的位,被置爲1

  • 看個例子
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>

void printsigset(sigset_t *set) 
{
    int i;
    for (i=1; i<NSIG; ++i)
    {
        if (sigismember(set, i))//置1的位,說明對應信號在信號集 set中。返回真,則打印1。如01000000000000....說明2號信號是處於未決狀態
            putchar('1');
        else
            putchar('0');
    }
    printf("\n");
}


int main()
{
    char tmp = 'a';
    sigset_t bset; //用來設置阻塞的信號集
    sigset_t pset; //用來打印未決信號集

    sigemptyset(&bset); //清空信號集
    sigaddset(&bset, SIG_INT); //將SIG_INT信號添加到信號集中

    sigprocmask(SIG_BLOCK, &bset, NULL); //阻塞SIG_INT信號

    for(;;;)
    {
        //獲取未決 字信息
        sigpending(&pset);//若一信號是未決狀態,則將set對應位置1

        //打印信號未決 sigset_t字
        printsigset(&pset);
        sleep(1);
    }
    pause();
    return 0;
}


程序開始執行:打印000000000000......
輸入ctrl+c,因爲SIG_INT被阻塞,所以該信號處於未決狀態
故輸出01000000000000000......

需要注意的是:如果在信號處理函數中對某個信號進行解除阻塞時,則只是將pending位清0,讓此信號遞達一次(同個實時信號產生多次進行排隊都會抵達),但不會將block位清0,即再次產生此信號時還是會被阻塞,處於未決狀態。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章