信号阻塞linux系统编程之信号(三):信号的阻塞与未决

之前一直在研究信号阻塞之类的问题,当初正好有机会和大家共享一下.

    一、信号在内核中的表现

    现实执行信号的处理动作称为信号递达(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长短空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set长短空指针,则更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都长短空指针,则先将本来的信号屏蔽字备份到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 杀死进程。

文章结束给大家分享下程序员的一些笑话语录: 某程序员对书法十分感兴趣,退休后决定在这方面有所建树。花重金购买了上等的文房四宝。一日突生雅兴,一番磨墨拟纸,并点上了上好的檀香,颇有王羲之风 范,又具颜真卿气势,定神片刻,泼墨挥毫,郑重地写下一行字:hello world.

原文地址:https://www.cnblogs.com/jiangu66/p/3087265.html