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

一、信号在内核中的表示

实际执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞(Block)某个信号,SIGKILL 和 SIGSTOP 不能被阻塞。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。信号在内核中的表示可以看作是这样的:

每个信号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中, 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);

示例程序:

/*************************************************************************
    > 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 编程一站式学习》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏熊二哥

企业模式和设计模式快速入门

相信大家对GOF的23个设计模式和Martin Fowler的企业应用架构模式都有过了解,这部分的内容和知识非常驳杂,不过真正常用的模式并不多,比如单例模式、策...

1807
来自专栏技术分享

.NET应用架构设计—用户端的防腐层作用及设计

阅读目录: 1.背景介绍 2.SOA架构下的显示端架构腐化 3.有效使用防腐层来隔离碎片服务导致显示端逻辑腐烂 4.剥离服务调用的技术组件让其依赖接口 ...

23610
来自专栏MasiMaro 的技术博文

WinSock 重叠IO模型

title: WinSock 重叠IO模型 tags: [WinSock 模型, 网络编程, 重叠IO模型] date: 2018-06-29 20:26:...

692
来自专栏用户2442861的专栏

JavaScript Promise

在callback的模型里边,我们假设需要执行一个异步队列,代码看起来可能像这样:

552
来自专栏JAVA高级架构

Java设计模式-责任链模式

作者:Jet啟思 链接:https://juejin.im/post/5a126b146fb9a0450c490201 今天来说说程序员小猿和产品就关于需求发生...

33611
来自专栏青玉伏案

设计模式(二):自己动手使用“观察者模式”实现通知机制

在之前发布Objective-C系列博客的时候,其中提到过OC的通知机制,请参考《Objective-C中的老板是这样发通知的(Notification)》这篇...

2076
来自专栏我是攻城师

浅谈ElasticSearch的嵌套存储模型

3376
来自专栏企鹅号快讯

《数据库系统概念》12-文件的组织

一个数据库被映射到多个不同的文件,这些文件由底层的操作系统来维护。每个文件分成定长的存储单元,称为块(bolck),块是存储分配和数据传输的基本单元。数据库默认...

2519
来自专栏ROBOTEDU

控制程序运行指令

控制程序运行指令 一 子程序 1. 概述 在零件程序分为“主程序”和“子程序”时,就出现了“子程序”的概念。子程序指由主程序调用的零件程序。在目前的SINUME...

2724
来自专栏CSDN技术头条

史上最难的一道Java面试题:分析篇

无意中了解到如下题目,觉得蛮好。 题目如下: ? 该程序的输出结果? 在java中,多线程的程序最难理解、调试,很多时候执行结果并不像我们想象的那样执行。所以在...

1717

扫码关注云+社区