Linux系統程式設計之訊號中斷處理(下)

千鋒教育官方發表於2019-09-05


訊號集與訊號阻塞集

訊號集

為了方便對多個訊號進行處理,一個使用者程式常常需要對多個訊號做出處理,在 Linux 系統中引入了訊號集(訊號的集合)。這個訊號集有點類似於我們的 QQ 群,一個個的訊號相當於 QQ 群裡的一個個好友。

訊號集是用來表示多個訊號的資料型別(sigset_t ),其定義路徑為: /usr/include/i386-linux-gnu/bits/sigset.h

訊號集相關的操作主要有如下幾個函式:

 

#include <signal.h>  

int sigemptyset(sigset_t *set);  

int sigfillset(sigset_t *set);  

int sigismember(const sigset_t *set, int signum);  

int sigaddset(sigset_t *set, int signum);  

int sigdelset(sigset_t *set, int signum);

 

透過例子來檢視他的使用方法:

 

#include <signal.h>

#include <stdio.h>

 

int main(int argc, char *argv[])

{

sigset_t set; // 定義一個訊號集變數

int ret = 0;

 

sigemptyset(&set); // 清空訊號集的內容

// 判斷 SIGINT 是否在訊號集 set 裡

// 在返回 1, 不在返回 0

ret = sigismember(&set, SIGINT);

if(ret == 0){

printf("SIGINT is not a member of set \nret = %d\n", ret);

}

sigaddset(&set, SIGINT); // 把 SIGINT 新增到訊號集 set

sigaddset(&set, SIGQUIT);// 把 SIGQUIT 新增到訊號集 set

// 判斷 SIGINT 是否在訊號集 set 裡

// 在返回 1, 不在返回 0

ret = sigismember(&set, SIGINT);

if(ret == 1){

printf("SIGINT is a member of set \nret = %d\n", ret);

}

sigdelset(&set, SIGQUIT); // 把 SIGQUIT 從訊號集 set 移除

// 判斷 SIGQUIT 是否在訊號集 set 裡

// 在返回 1, 不在返回 0

ret = sigismember(&set, SIGQUIT);

if(ret == 0){

printf("SIGQUIT is not a member of set \nret = %d\n", ret);

}

return 0;

}

 

執行結果:

 

訊號阻塞集( 遮蔽集、掩碼 )

訊號阻塞集也稱訊號遮蔽集、訊號掩碼。每個程式都有一個阻塞集,建立子程式時子程式將繼承父程式的阻塞集。訊號阻塞集用來描述哪些訊號遞送到該程式的時候被阻塞(在訊號發生時記住它,直到程式準備好時再將訊號通知程式)。

所謂阻塞並不是禁止傳送訊號, 而是暫緩訊號的傳送。若將被阻塞的訊號從訊號阻塞集中刪除,且對應的訊號在被阻塞時發生了,程式將會收到相應的訊號。

 

我們可以透過 sigprocmask() 修改當前的訊號掩碼來改變訊號的阻塞情況。

 

所需標頭檔案:

#include <signal.h>

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

功能:

檢查或修改訊號阻塞集,根據 how 指定的方法對程式的阻塞集合進行修改,新的訊號阻塞集由 set 指定,而原先的訊號阻塞集合由 oldset 儲存。

 

引數:

how: 訊號阻塞集合的修改方法,有 3 種情況:

SIG_BLOCK :向訊號阻塞集合中新增 set 訊號集,新的訊號掩碼是 set 和舊訊號掩碼的並集。

SIG_UNBLOCK :從訊號阻塞集合中刪除 set 訊號集,從當前訊號掩碼中去除 set 中的訊號。

SIG_SETMASK :將訊號阻塞集合設為 set 訊號集,相當於原來訊號阻塞集的內容清空,然後按照 set 中的訊號重新設定訊號阻塞集。

set: 要操作的訊號集地址

set NULL ,則不改變訊號阻塞集合,函式只把當前訊號阻塞集合儲存到 oldset 中。

 

oldset: 儲存原先訊號阻塞集地址

返回值

成功:0

失敗:-1 ,失敗時錯誤程式碼只可能是 EINVAL ,表示引數 how 不合法。

注意:不能阻塞 SIGKILL SIGSTOP 等訊號,但是當 set 引數包含這些訊號時 sigprocmask() 不返回錯誤,只是忽略它們。另外,阻塞 SIGFPE 這樣的訊號可能導致不可挽回的結果,因為這些訊號是由程式錯誤產生的,忽略它們只能導致程式無法執行而被終止。

 

示例程式碼如下:

 

 

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <signal.h>

 

int main(int argc, char *argv[])

{

sigset_t set; // 訊號集合

int i = 0;

sigemptyset(&set); // 清空訊號集合

sigaddset(&set, SIGINT); // SIGINT 加入 set 集合

while(1)

{

// set 集合加入阻塞集,在沒有移除前,SIGINT 會被阻塞

sigprocmask(SIG_BLOCK, &set, NULL);

for(i=0; i<5; i++)

{

printf("SIGINT signal is blocked\n");

sleep(1);

}

// set 集合從阻塞集中移除

// 假如 SIGINT 訊號在被阻塞時發生了

// 此刻,SIGINT 訊號立馬生效,中斷當前程式

sigprocmask(SIG_UNBLOCK, &set, NULL);

for(i=0; i<5; i++)

{

printf("SIGINT signal unblocked\n");

sleep(1);

}

}

return 0;

}

 

執行結果:

 

 

可靠訊號的操作

UNIX 系統繼承過來的訊號( SIGHUP SIGSYS ,前 32 個)都是不可靠訊號,不支援排隊(多次傳送相同的訊號 , 程式可能只能收到一次,可能會丟失)。

SIGRTMIN SIGRTMAX 的訊號支援排隊(發多少次 , 就可以收到多少次 , 不會丟失),故稱為可靠訊號。

可靠訊號就是實時訊號,非可靠訊號就是非實時訊號。

signal() 函式只能提供簡單的訊號安裝操作,使用 signal() 函式處理訊號比較簡單,只要把要處理的訊號和處理函式列出即可。

signal() 函式主要用於前面 32 種不可靠、非實時訊號的處理,並且不支援訊號傳遞資訊。

Linux 提供了功能更強大的 sigaction() 函式,此函式可以用來檢查和更改訊號處理操作,可以支援可靠、實時訊號的處理,並且支援訊號傳遞資訊。

 

下面我們一起學習其相關函式的使用。

所需標頭檔案:

#include <signal.h>

int sigqueue(pid_t pid, int sig, const union sigval value);

 

功能:給指定程式傳送訊號。

 

引數:

pid:  程式號。

sig: 訊號的編號,這裡可以填數字編號,也可以填訊號的宏定義,可以透過命令 kill -l ("l" 為字母 ) 進行相應檢視。

value:  透過訊號傳遞的引數。

union sigval 型別如下:

union sigval  

{  

    int   sival_int;  

    void *sival_ptr;  

};  

返回值:

成功:0

失敗:-1

int sigaction(int signum,const struct sigaction *act, struct sigaction *oldact );

 

功能:

檢查或修改指定訊號的設定(或同時執行這兩種操作)。

引數:

signum :要操作的訊號。

act   要設定的對訊號的新處理方式(設定)。

oldact :原來對訊號的處理方式(設定)。

如果 act 指標非空,則要改變指定訊號的處理方式(設定),如果 oldact 指標非空,則系統將此前指定訊號的處理方式(設定)存入 oldact

返回值:

成功:0

失敗:-1

訊號設定結構體:

struct sigaction  

{  

    /*舊的訊號處理函式指標*/  

    void (*sa_handler)(int signum) ;  

      

    /*新的訊號處理函式指標*/  

    void (*sa_sigaction)(int signum, siginfo_t *info, void *context);  

      

    sigset_t sa_mask;/*訊號阻塞集*/  

      

    int sa_flags;/*訊號處理的方式*/  

};  

 

 

sa_handler sa_sigaction :訊號處理函式指標,和 signal() 裡的函式指標用法一樣,應根據情況給 sa_sigaction sa_handler 兩者之一賦值,其取值如下:

SIG_IGN :忽略該訊號

SIG_DFL :執行系統預設動作

 

處理函式名:自定義訊號處理函式

sa_mask :訊號阻塞集

sa_flags :用於指定訊號處理的行為,它可以是一下值的 按位或 組合:

SA_RESTART :使被訊號打斷的系統呼叫自動重新發起(已經廢棄)

SA_NOCLDSTOP :使父程式在它的子程式暫停或繼續執行時不會收到 SIGCHLD 訊號。

SA_NOCLDWAIT :使父程式在它的子程式退出時不會收到 SIGCHLD 訊號,這時子程式如果退出也不會成為殭屍程式。

SA_NODEFER :使對訊號的遮蔽無效,即在訊號處理函式執行期間仍能發出這個訊號。

SA_RESETHAND :訊號處理之後重新設定為預設的處理方式。

SA_SIGINFO :使用 sa_sigaction 成員而不是 sa_handler 作為訊號處理函式。

 

 

 

訊號處理函式:

void (*sa_sigaction)( int signum, siginfo_t *info, void *context );

引數說明:

signum :訊號的編號。

info :記錄訊號傳送程式資訊的結構體,程式資訊結構體路徑: /usr/include/i386-linux-gnu/bits/siginfo.h ,其結構體詳情請點此連結。

context :可以賦給指向 ucontext_t 型別的一個物件的指標,以引用在傳遞訊號時被中斷的接收程式或執行緒的上下文

 

下面我們做這麼一個例子,一個程式在傳送訊號,一個程式在接收訊號的傳送。

傳送訊號示例程式碼:

#include <stdio.h>  

#include <signal.h>  

#include <sys/types.h>  

#include <unistd.h>  

  

/******************************************************* 

*功能:     發 SIGINT 訊號及訊號攜帶的值給指定的程式 

*引數:        argv[1]:程式號 

            argv[2]:待傳送的值(預設為100) 

*返回值:   0 

********************************************************/  

int main(int argc, char *argv[])  

{  

    if(argc >= 2)  

    {  

        pid_t pid,pid_self;  

        union sigval tmp;  

  

        pid = atoi(argv[1]); // 程式號  

        if( argc >= 3 )  

        {  

            tmp.sival_int = atoi(argv[2]);  

        }  

        else  

        {  

            tmp.sival_int = 100;  

        }  

          

        // 給程式 pid,傳送 SIGINT 訊號,並把 tmp 傳遞過去  

        sigqueue(pid, SIGINT, tmp);  

          

        pid_self = getpid(); // 程式號  

        printf("pid = %d, pid_self = %d\n", pid, pid_self);  

          

    }  

      

    return 0;  

}  

 

接收訊號示例程式碼如下:

[cpp] view plaincopy

 

#include <signal.h>  

#include <stdio.h>  

  

// 訊號處理回電函式  

void signal_handler(int signum, siginfo_t *info, void *ptr)  

{  

    printf("signum = %d\n", signum); // 訊號編號  

    printf("info->si_pid = %d\n", info->si_pid); // 對方的程式號  

    printf("info->si_sigval = %d\n", info->si_value.sival_int); // 對方傳遞過來的資訊  

}  

  

int main(int argc, char *argv[])  

{  

    struct sigaction act, oact;  

      

    act.sa_sigaction = signal_handler; //指定訊號處理回撥函式  

    sigemptyset(&act.sa_mask); // 阻塞集為空  

    act.sa_flags = SA_SIGINFO; // 指定呼叫 signal_handler  

      

    // 註冊訊號 SIGINT  

    sigaction(SIGINT, &act, &oact);  

      

    while(1)  

    {  

        printf("pid is %d\n", getpid()); // 程式號  

          

        pause(); // 捕獲訊號,此函式會阻塞  

    }  

      

    return 0;  

}  

 

兩個終端分別編譯程式碼,一個程式接收,一個程式傳送,執行結果如下:

 

 

最後:

關注回覆“物聯網”即可獲取物聯網全套影片教程

 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69914734/viewspace-2656155/,如需轉載,請註明出處,否則將追究法律責任。

相關文章