linux系統編程之訊號(三):訊號的阻塞與未決

來源:互聯網
上載者:User

一、訊號在核心中的表示

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


每個訊號都有兩個標誌位分別表示阻塞和未決,還有一個函數指標表示處理動作。訊號產生時,核心在進程式控制制塊中設定該訊號的未決標誌,直到訊號遞達才清除該標誌。在的例子中,
1. SIGHUP訊號未阻塞也未產生過,當它遞達時執行預設處理動作。

2. SIGINT訊號產生過,但正在被阻塞,所以暫時不能遞達。雖然它的處理動作是忽略,但在沒有解除阻塞之前不能忽略這個訊號,因為進程仍有機會改變處理動作之後再解除阻塞。

3. SIGQUIT訊號未產生過,一旦產生SIGQUIT訊號將被阻塞,它的處理動作是使用者自訂函數sighandler。

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


二、訊號集處理函數

sigset_t類型(64bit)對於每種訊號用一個bit表示“有效”或“無效”狀態,至於這個類型內部如何儲存這些bit則依賴於系統實現,從使用者的角度是不必關心的,使用者只能調用以下函數來操作sigset_t變數,而不應該對它的內部資料做任何解釋,比如用printf直接列印sigset_t變數是沒有意義的。


#include <signal.h>

int sigemptyset(sigset_t *set);

int sigfillset(sigset_t *set);

int sigaddset(sigset_t *set, int signo);

int sigdelset(sigset_t *set, int signo);

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


函數sigemptyset初始化set所指向的訊號集,使其中所有訊號的對應bit清零,表示該訊號集不包含任何有效訊號。函數sigfillset初始化set所指向的訊號集,使其中所有訊號的對應bit置位,表示該訊號集的有效訊號包括系統支援的所有訊號。注意,在使用sigset_t類型的變數之前,一定要調用sigemptyset或sigfillset做初始化,使訊號集處於確定的狀態。初始化sigset_t變數之後就可以在調用sigaddset和sigdelset在該訊號集中添加或刪除某種有效訊號。這四個函數都是成功返回0,出錯返回-1。sigismember是一個布爾函數,用於判斷一個訊號集的有效訊號中是否包含某種訊號,若包含則返回1,不包含則返回0,出錯返回-1。

三、sigprocmask 和 sigpending 函數


1、調用函數sigprocmask可以讀取或更改進程的訊號屏蔽字。


#include <signal.h>

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

傳回值:若成功則為0,若出錯則為-1
如果oset是非null 指標,則讀取進程的當前訊號屏蔽字通過oset參數傳出。如果set是非null 指標,則更改進程的訊號屏蔽字,參數how指示如何更改。如果oset和set都是非null 指標,則先將原來的訊號屏蔽字備份到oset裡,然後根據set和how參數更改訊號屏蔽字。假設當前的訊號屏蔽字為mask,下表說明了how參數的可選值。


2、sigpending讀取當前進程的未決訊號集,通過set參數傳出。調用成功則返回0,出錯則返回-1。

#include <signal.h>

int sigpending(sigset_t *set);


樣本程式:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/*************************************************************************
    > File Name: process_.c
    > Author: Simba
    > Mail: dameng34@163.com
    > Created Time: Sat 23 Feb 2013 02:34:02 PM CST
 ************************************************************************/
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.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 flag = 0;

int main(int argc, char *argv[])
{
    if (signal(SIGINT, handler) == SIG_ERR)
        ERR_EXIT("signal error");
    if (signal(SIGQUIT, handler) == SIG_ERR)
        ERR_EXIT("signal error");

    sigset_t pset; // 64bit
    sigset_t bset;
    sigemptyset(&bset);
    sigaddset(&bset, SIGINT);
    sigprocmask(SIG_BLOCK, &bset, NULL);

    for (; ;)
    {
        sigpending(&pset);
        printsigset(&pset);
        sleep(1);
        if (flag == 1)
            sigprocmask(SIG_UNBLOCK, &bset, NULL);
    }

    return 0;
}

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

}

如果將程式中的37,57,58,75關於flag變數的語句注釋掉,則輸出如下:

simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$ ./sigprocmask 
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000

...................................................................................

^C0100000000000000000000000000000000000000000000000000000000000000
0100000000000000000000000000000000000000000000000000000000000000

...................................................................................................................................................

^\rev a sig=3
recv a sig=2
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000

............................................................................................................................................

^C0100000000000000000000000000000000000000000000000000000000000000
0100000000000000000000000000000000000000000000000000000000000000

...........................................................................................................................................


在程式的一開始將SIGINT訊號添加進阻塞訊號集(即訊號屏蔽字),死迴圈中一直在列印進程的訊號未決集,當我們按下ctrl+c,因為訊號被阻塞,故處於未決狀態,所以輸出的第二位為1(SIGINT是2號訊號),接著當我們按下ctrl+\,即發送SIGQUIT訊號,我們在handler中解除了對SIGINT的阻塞,故2號訊號被遞達,列印兩行recv語句,此時訊號未決集又變成全0。比較讓人疑惑的是我們貌似已經解除了對SIGINT的屏蔽,但當我們再次ctrl+c
時,訊號還是處於未決狀態。後來我寫了個測試程式,發現解除阻塞時只是將未決標誌pending位清0,而block位一直為1,但還是覺得很不解,難道一個進程運行期間只要阻塞了一個訊號,只能每次靠清除pending位讓其遞達,即治標不治本?後來覺得會不會是因為在handler裡進行解除才會這樣呢?於是設定了一個標誌位flag,即把前面說的4行代碼補上,則前面的輸出是一樣的,但在主函數中再次解除阻塞後,按下ctrl+c,讓人驚喜的是2號訊號順利遞達,如下:

^Crecv a sig=2
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000

...........................................................................................................................................

我查遍了sigprocmask 的 man手冊,也沒發現說明這一點,但實際測試是這樣的,即如果在訊號處理函數中對某個訊號進行解除阻塞時,則只是將pending位清0,讓此訊號遞達一次,但不會將block位清0,即再次產生此訊號時還是會被阻塞,處於未決狀態。

現在使用ctrl+c , ctrl+\ 都終止不了程式了,可以另開個終端kill -9 pid 殺死進程。


參考:《APUE》、《linux c 編程一站式學習》

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.