【Linux】进程信号保存

前言

上篇博客我们了解了进程信号的概念和信号如何产生。
本篇我们将学习进程信号如何保存。

在这里插入图片描述

一. 阻塞信号

首先我们需要一些预备知识

  1. 实际执行信号的处理动作称为信号递达(Delivery)。
  2. 信号从产生到递达之间的状态,称为信号未决(Pending)。
  3. 进程可以选择阻塞(Block)某个信号。
  4. 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
  5. 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是递达之后可选的一种处理动作。

并且pcb的task_struct内部其实维护有三张表
在这里插入图片描述

  1. pending 表位图结构。 比特位的位置,表示哪一个信号;比特位的内容,代表是否收到该信号
  2. block 表位图结构。比特位的位置,表示哪一个信号;比特位的内容,代表对应的信号是否需要被阻塞
  3. handler 表函数指针数组,内部的函数指针,是有一个int的参数,返回值是void 的函数指针
    该数组的下标,代表信号编号,数组的特定下标的内容,表示该信号的递达动作

二. 递达动作

在上一篇我们说进程对一个信号的处理有三种:默认动作,自定义动作,忽略
我们讲了自定义动作,其实就是我们自己编写,返回值是void,有一个int参数的函数,并用signal函数对特定信号进行捕捉。
而默认动作和忽略其实是在这样的
在这里插入图片描述
默认动作是SIG_DFL,忽略是SIG_IGN
二者其实都是将整型强转成函数指针类型,其中SIG_DFL就是Default action终止进程

三. 信号集

未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“ 有效 ”或“ 无效 ”状态,在阻塞信号集中“ 有效 ”和“ 无效 ”的含义是该信号是否被阻塞,而在未决信号集中“ 有效 ”和“ 无效 ”的含义是该信号是否处于未决状态。
阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。

操作系统给我们提供了信号集类型
在这里插入图片描述

sigset其实也是位图,是长度更长的位图,因为一个unsigned long int 是4字节,也就是32位,而使用数组结构,就可以有更多的比特位了。
假如要访问第127位,是这样操作的
-val[ 127 / (sizeof( unsigned long int ) * 8 ) ],表示访问第3个unsigned long int
再访问127 % (sizeof( unsigned long int ) *8)

但是,我们不需要这样操作,操作系统给我们提供了相应操作的函数

四. 信号集操作函数

#include <signal.h> 头文件
int sigemptyset (sigset_t *set); 将所有比特位都置为0
int sigfillset (sigset_t *set); 将所有比特位都置为1
int sigaddset (sigset_t *set, int signo); 添加第signo位信号
int sigdelset (sigset_t *set, int signo); 删除第signo位信号
int sigismember(const sigset_t *set, int signo); 判断signo信号是否则在该位图里

sigprocmask()

可以读取或更改进程的信号屏蔽字(阻塞信号集)
在这里插入图片描述

oset是输出型参数,返回旧的sigset_t,修改后可直接恢复
如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。
如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,how参数有如下几个可选值

  1. SIG_BLOCK:set包含了我们希望添加到当前信号屏蔽字的信号,相当于mask=mask | set
  2. SIG_UNBLOCK:set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask&~set
  3. SIG_SETMASK:设置当前信号屏蔽字为set所指向的值,相当于mask=set

以下实验我们对2号信号进行阻塞,并且可以获取原先的block表和改变后的block表

在这里插入图片描述

我们给set添加2号信号,然后再将使用SIG_SETMASK将set直接赋值给当前进程的block表,这样就成功阻塞了2号信号。从结果看到,我们在程序运行时,即使使用ctrl+c也无法终止进程。
然后前5次打印oset,我们是打印原先的block表,在第6次循环时,我们再获取当前进程的block表,可以发现,2号位置为1,表明2号信号被阻塞了。


sigpending()
在这里插入图片描述

sigpending()函数可以获取当前进程的pending表

sigset_t * set:输出型参数,会将当前进程的pending表赋值给set

接下来我们再做一个实验,也是阻塞2号信号,同时我们获取pending表,然后发送2号信号,观察变化

#include<iostream>
#include<cassert>
#include<signal.h>
#include<unistd.h>

using namespace std;

//打印pending表
void printPending(const sigset_t &pending)
{
    
    
    for(int signo=1;signo<=31;signo++)
    {
    
    
        //查询pending表内是否有signo号信号
        if(sigismember(&pending,signo))
        {
    
    
            cout<<1;
        }
        else
        {
    
    
            cout<<0;
        }
    }
    cout<<endl;
}

int main()
{
    
    
    //信号集
    //set是更改的信号集,oset获取旧的信号集
    sigset_t set,oset;
    //将两个信号集都置0
    sigemptyset(&set);
    sigemptyset(&oset);

    //添加阻塞的信号,如2号信号
    sigaddset(&set,2);

    //阻塞信号
    sigprocmask(SIG_BLOCK,&set,&oset);

    //获取pending表
    while(true)
    {
    
    
        sigset_t pending;
        sigemptyset(&pending);
        //获取pending表
        int n=sigpending(&pending);
        assert(n==0);
        (void)n;//使用一下n变量,防止release版本assert被忽略,而引发的报错
        //打印pending表
        printPending(pending);
        //休眠一下
        sleep(1);
    }

    return 0;
}

结果如下:
在这里插入图片描述

可以看到,pending 表最开始是全0,也就是一个信号都没有收到,而当我们按下了ctrl+c,也就是发送了2号信号,可以看到pending表第2位变成了1,又因为我们对2号信号进行了阻塞,所以进程即使收到2号信号,pending表2号位置变成1,但是还是没有终止进程,因为我们阻塞了2号信号,进程并不会执行相应的动作。并且因为2号信号没有处理,所以pending表中2号位置一直为1。

同时,我们也可以阻塞一段时间后,再使用sigprocmask恢复原先的block表
在这里插入图片描述

结束语

本篇内容到此就结束了,感谢你的阅读!

如果有补充或者纠正的地方,欢迎评论区补充,纠错。如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/m0_72563041/article/details/130489078
今日推荐