1. 程式人生 > >LINUX中的訊息傳遞函式分析

LINUX中的訊息傳遞函式分析

                                       LINUX中的訊息傳遞函式分析

sigset_t

  號集及訊號集操作函式:訊號集被定義為一種資料型別:

  typedef struct {

  unsigned long sig[_NSIG_WORDS];

  } sigset_t

未決和阻塞標誌可以用相同的資料型別sigset_t來儲存,sigset_t稱為訊號集,這個型別可以表示每個訊號的“有效”或“無效”狀態,在阻塞訊號集中“有效”和“無效”的含義是該訊號是否被阻塞,而在未決訊號集中“有效”和“無效”的含義是該訊號是否處於未決狀態。阻塞訊號集也叫做當前程序的訊號遮蔽字(Signal Mask),這裡的“遮蔽”應該理解為阻塞而不是忽略

訊號集用來描述訊號的集合,linux所支援的所有訊號可以全部或部分的出現在訊號集中,主要與訊號阻塞相關函式配合使用。下面是為訊號集操作定義的相關函式:

  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);

  

標頭檔案

  #include <signal.h>

  sigemptyset(sigset_t *set)初始化由set指定的訊號集,訊號集裡面的所有訊號被清空;

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

  sigaddset(sigset_t *set, int signum)在set指向的訊號集中加入signum訊號;

  sigdelset(sigset_t *set, int signum)在set指向的訊號集中刪除signum訊號;

  sigismember(const sigset_t *set, int signum)判定訊號signum是否在set指向的訊號集中。

訊號產生方式

  1. 通過終端按鍵產生。 如ctrl+c 終止程序
  2. 通過系統函式向程序傳送訊號。如kill()函式,給指定程序傳送訊號。
  3. 有軟體條件產生訊號。如alarm()函式,設定一個鬧鐘訊號。
  4. 硬體異常。如記憶體越界,除0異常。

利用kill()函式實現自己的kill 命令

// 傳送訊號給程序
#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid,  int sig);

 

先寫一個死迴圈程式test並在後臺跑起來

//test.c
#include <stdio.h>
int main()
{
    while(1);
    return 0;
}

這是利用kill 函式實現的mykill:

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

int main( int argc, char** argv)
{
    if(argc != 1)
    {
        printf("parameter error.\n");
        exit(1);
    }

    // 獲取pid
    pid_t pid = atoi(argv[1]);

    kill(pid, SIGKILL);

    return 0;
}

testrun

然後執行jobs ,可以看到後臺作業test已經存在。

jobs

此時執行mykill 並 輸入test程式的pid

jobs

第一次執行jobs發現test狀態已經從Running 變為 Killed,再次執行就會看到已經被幹掉。

認識alarm() 函式

#include <unistd.h>

unsigned int alarm( unsigned int seconds);

alarm函式可以設定一個鬧鐘,告訴核心在seconds秒之後給當前程序傳送SIGALRM訊號,該訊號的預設處理動作是終止當前程序。 函式返回值是0,或者是上次鬧鐘剩餘時間。

比如先設定鬧鐘10s, 然後呼叫alarm(0) 表示取消鬧鐘, 如果返回0, 說明鬧鐘是在10後響的,如果大於0,則說明該鬧鐘提前響了。

下面是一個使用alarm函式的小栗子, 測試一秒對可以執行多少次++,並在每次++後列印值:

#include <stdio.h>
#include <unistd.h>


int main()
{
    int count = 0;

    alarm(1);
    while(1)
    {
        count++;
        printf("count is  %d .\n", count);
    }
    return 0;
}

訊號的處理方式

一個程序在收到一個訊號後,有三種方式處理方式:
1. 忽略此訊號
2. 執行預設動作
3. 提供一個訊號處理函式,要求核心在處理該訊號時切換到使用者態,這種方式也叫捕捉訊號。

一個捕捉訊號的小例子:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>


void sigact(int  num)
{
    printf("\n %d號 訊號 被我吃了. \n", num);
}


int main()
{
    printf("catch start ... \n");
    signal(SIGINT, sigact); // 捕捉 SIGINT 訊號,提供自定義動作
    while(1)
    {
        sleep(1);
        printf("你殺不掉我 hhh \n");
    }
    return 0;
}

catch_2

在程式碼中,我們捕捉了SIGINT 訊號,也就是2號訊號(kill -l 可以看到全部訊號列表), 作業系統收到ctrl + c ,傳送該訊號給當前前臺作業。發現執行後它每個一秒列印一條“你殺不掉我 hhh”, 當我們按ctrl+c 也無濟於事,因為我們為該程序提供了2號訊號的自定義函式“訊號被我吃掉了”。最後我們只能以ctrl + \幹掉它。

訊號在記憶體中的表示

以上討論了訊號產生的各種原因,而實際執行訊號的動作稱為遞達(Delivery),訊號從產生到遞達之間的狀態,稱為訊號未決(Pending)。程序可以設定阻塞(Block)某個訊號。

如果一個程序阻塞了某個訊號,那麼在它收到被阻塞的訊號時,該訊號會處於未決狀態。直到對該訊號解除阻塞,才會執行抵達的動作。

需要注意的是,訊號阻塞和訊號忽略是不同的。訊號忽略是在該訊號被遞達後執行的動作,而阻塞說明該該訊號在解除阻塞之前不可能遞達。

我們知道在系統中執行的每一個程序都有一個 PCB, 而一個程序對應的訊號資訊也會被作業系統記錄在該程序的 PCB 上。在task_struct 結構體會有對應的欄位來記錄程序當前是否有待處理的訊號, 還有記錄當前要阻塞的訊號,以及訊號對應的處理函式。下面用一張圖來說明一下關係:

SIGNAL表示

我們可以這樣理解:以看做在PCB中有三張表格,分別是block表,pending表,handler表。

block表中1 表示該訊號被阻塞,當有訊號產生時不會被抵達,會處於未決狀態。
而pending表記錄未被處理的訊號。handler 對應每個訊號的處理方式,有預設和忽略,以及一個函式指標,指向我們提供的處理函式。

一大波訊號集處理函式襲來

通過上面的表格可以發現,阻塞和未決狀態,每個訊號只需要對應一個bit位即可解決1表示有效,0表示無效,所以系統為我們提供了訊號量集 sigset_t 來儲存阻塞和未決的狀態。

#include <signal.h>

int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);

以上兩個函式用來將訊號機全部置0或全部置1,在使用之前,務必呼叫對應初始化函式,讓訊號機處於確定狀態。


int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);

上面兩個函式用來給,指定訊號集,新增或刪除signum訊號。


int sigismember(sigset_t *set, int );

用來判斷訊號集中是否有該訊號,有則返回1,無返回0,執行失敗返回-1。

sigprocmask函式

用來讀取或者更改程序的訊號遮蔽集。

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

如果 set 為空 oldset 不為空, 則會將程序原來的訊號遮蔽集傳出;
如果 set 不為空,oldset 為空, 則會根據 how 引數的指示修改程序的訊號遮蔽集;
如果兩個指標都不為空, 則先備份原來的訊號遮蔽字到 oldset, 然後根據 how引數修改。

how引數有如下函式

含義
SIG_BLOCK 將當前程序遮蔽字mask 增加我們希望通過引數set的遮蔽訊號,相當於mask與set執行按位或
SIG_UNBLOCK 將當前程序遮蔽字mask 刪除我們希望通過引數set解除遮蔽的訊號
SIG_SETMASK 設定當前訊號遮蔽字為set,相當於 mask = set

sigpending

用來讀取當前程序的未決訊號集。

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

下面運用上面介紹的訊號集函式寫一個小實驗。

程式執行時,每秒列印一此未決狀態訊號集,初始全為0,當輸出ctrl-c時, 由於我們阻塞了SIGINT訊號, 會使該訊號處於未決狀態。

#include <stdio.h>
#include <signal.h>


// 列印訊號集
void printsigset(const sigset_t *set)
{
    int i = 0;
    for(; i<32; ++i)
    {
        if(sigismember(set, i) == 1)
            printf("1");
        else
            printf("0");

    }
    printf("\n");
}

int main()
{

    sigset_t s;
    sigemptyset(&s); // 初始化
    sigaddset(&s, SIGINT);
    sigprocmask(SIG_BLOCK, &s, NULL );

    while(1)
    {
        sigpending(&s);
        printsigset(&s);
        sleep(1);
    }


    return 0;
}

printpendingset

此篇文章是對大佬們的講解整理的,感覺還不錯!