Linux信号【保存-处理】

news2024/9/20 12:39:23

目录

前言:

1、再次认识信号

1.1、概念

1.2、感性理解

1.3、在内核中的表示

1.4、sigset_t 信号集

2、信号集操作函数

2.1、增删改查

2.2、sigprocmask

2.3、sigpending

3.信号的处理机制

3.1处理情况 

3.2合适时机

4用户态与内核态

4.1、概念

4.2、重谈进程地址空间

4.3、信号的处理过程

5.信号捕捉

5.1、内核如何实现信号的捕捉?

5.2sigaction 

6 小结



前言:

信号从产生到执行,并不会被立即处理,这就意味着需要一种 “方式” 记录信号是否产生,对于 31 个普通信号来说,一个 int 整型就足以表示所有普通信号的产生信息了;信号还有可能被 “阻塞”,对于这种多状态、多结果的事物,操作系统会将其进行描述、组织、管理,这一过程称为 信号保存 阶段

1、再次认识信号


补充 信号传递 的相关概念

1.1、概念


信号 传递过程:信号产生 -> 信号未决 -> 信号递达

信号产生(Produce):由四种不同的方式发出信号
信号未决(Pending):信号从 产生 到 执行 的中间状态
信号递达(Delivery):进程收到信号后,对信号的处理动作
在这三种过程之前,均有可能出现 信号阻塞 的情况

信号阻塞(Block):使信号传递 “停滞”,无论是否产生,都无法进行处理

 

信号递达后的三种处理方式:

  1. SIG_DFL 默认处理动作,大多数信号最终都是终止进程
  2. SIG_IGN 忽略动作,即进程收到信号后,不做任何处理动作
  3. handler 用户自定义的信号执行动作

 

注意:

  • 信号阻塞 是一种手段,可以发生在 信号处理 前的任意时段
  • 信号阻塞 与 忽略动作 不一样,虽然二者的效果差不多:什么都不干,但前者是 干不了,后者则是 不干了,需要注意区分
1.2、感性理解

将 信号传递 的过程比作 网上购物

可以抽象出以下概念:

  • 信号产生:在某某购物平台上下达了订单
  • 信号未决:订单下达后,快递的运输过程
  • 信号递达:快递到达驿站后,你对于快递的处理动作
  • 信号阻塞:快递运输过程中堵车了

只要你下单了,你的手机上肯定会有 物流信息(未决信息已记录),当 快递送达后(信号递达),物流记录 不再更新

而 堵车 是一件不可预料的事情,也就是说:在下单后,快递可能一会儿送达(没有阻塞),可能五天送达(阻塞 -> 解除阻塞),有可能永不送达,因为快递可能永远堵车(阻塞)

堵车也有可能在你下单前发生(信号产生前阻塞)

至于 信号递达后的处理动作 如何理解呢?

快递送达后,正常拆快递(默认动作)
快递送达后,啥也不干,就是玩(忽略)
快递送达后,直接把快递退回去(用户自定义)
当然,用户自定义的情况可以有很多种,也有可能是直接把快递扔了

综上,网购的整个过程可以看作 信号传递过程,本文探讨的是 信号保存阶段,即 物流信息

 

1.3、在内核中的表示

对于传递中的信号来说,需要存在三种状态表达:

  1. 信号是否阻塞
  2. 信号是否未决
  3. 信号递达时的执行动作

 在内核中,每个进程都需要维护这三张与信号状态有关的表:block 表、pending 表、handler 表

 

所谓的 block 表 和 pending 表 其实就是 位图结构

一个 整型 int 就可以表示 31 个普通信号(实时信号这里不讨论)

比如 1 号信号就是位图中的 0 位置处,0 表示 未被阻塞/未产生未决,1 则表示 阻塞/未决
对于信号的状态修改,其实就是修改 位图 中对应位置的值(0/1)
对于多次产生的信号,只会记录一次信息(实时信号则会将冗余的信号通过队列组织)

 

如何记录信号已产生 -> 未决表中对应比特位置置为 1 ?

假设已经获取到了信号的 pending 表
只需要进行位运算即可:pending |= (1 << (signo - 1))
其中的 signo 表示信号编号,-1 是因为信号编号从 1 开始,需要进行偏移
如果想要取消 未决 状态也很简单:pending &= (~(1 << (signo - 1)))
至于 阻塞 block 表,与 pending 表 一模一样

 

对于上图的解读:

  • SIGHUP 信号未被阻塞,未产生,一旦产生了该信号,pending 表对应的位置置为 1,当信号递达后,执行动作为默认
  • SIGINT 信号被阻塞,已产生,pending 表中有记录,此时信号处于阻塞状态,无法递达,一旦解除阻塞状态,信号递达后,执行动作为忽略该信号
  • SIGQUIT 信号被阻塞,未产生,即使产生了,也无法递达,除非解除阻塞状态,执行动作为自定义

阻塞 block 与 未决 pending 之间并没很强的关联性,阻塞不过是信号未决的延缓剂

  • 信号在 产生 之前,可以将其 阻塞,信号在 产生 之后(未决),依然可以将其 阻塞

 

至于 handler 表是一个 函数指针表,格式为:返回值为空,参数为 int 的函数

可以看看 默认动作 SIG_DEL 和 忽略动作 SIG_IGN 的定义

 

/* Type of a signal handler.  */
typedef void (*__sighandler_t) (int);

/* Fake signal functions.  */
#define SIG_ERR	((__sighandler_t) -1)		/* Error return.  */
#define SIG_DFL	((__sighandler_t) 0)		/* Default action.  */
#define SIG_IGN	((__sighandler_t) 1)		/* Ignore signal.  */

默认动作就是将 0 强转为函数指针类型,忽略动作则是将 1 强转为函数指针类型,分别对应 handler 函数指针数组表中的 01 下标位置;除此之外,还有一个 错误 SIG_ERR 表示执行动作为 出错

简单对这三张表作一个总结,task_struct 中存在:

block 表(位图结构)比特位的位置,表示哪一个信号;比特位的内容代表 是否 对应信号被阻塞
pending 表(位图结构)比特位的位置,表示哪一个信号;比特位的内容代表 是否 收到该信号
handler 表(函数指针数组)该数组的下标,表示信号编号;数组的特定下标的内容,表示该信号递达后的执行动作

1.4、sigset_t 信号集

无论是 block 表 还是 pending 表,都是一个位图结构,依靠 除、余 完成操作,为了确保不同平台中位图操作的兼容性,将信号操作所需要的 位图 结构封装成了一个结构体类型,其中是一个 无符号长整型数组

/* A `sigset_t' has a bit for each signal.  */

# define _SIGSET_NWORDS	(1024 / (8 * sizeof (unsigned long int)))
typedef struct
  {
    unsigned long int __val[_SIGSET_NWORDS];
  } __sigset_t;

#endif

注:_SIGSET_NWORDS 大小为 32,所以这是一个可以包含 32 个 无符号长整型 的数组,而每个 无符号长整型 大小为 4 字节,即 32 比特,至多可以使用 1024 个比特位

sigset_t 是信号集,其中既可以表示 block 表信息,也可以表示 pending 表信息,可以通过信号集操作函数进行获取对应的信号集信息;信号集 的主要功能是表示每个信号的 “有效” 或 “无效” 状态

block 表 通过信号集称为 阻塞信号集或信号屏蔽字(屏蔽表示阻塞),pending 表 通过信号集中称为 未决信号集

如何根据 sigset_t 位图结构进行比特位的操作?

假设现在要获取第 127 个比特位
首先定位数组下标(对哪个数组操作):127 / (8 * sizeof (unsigned long int)) = 3
求余获取比特位(对哪个比特位操作):127 % (8 * sizeof (unsigned long int)) = 31
对比特位进行操作即可
假设待操作对象为 XXX
置 1:XXX._val[3] |= (1 << 31)
置 0:XXX._val[3] &= (~(1 << 31))
所以可以仅凭 sigset_t 信号集,对 1024 个比特位进行任意操作,关于 位图 结构的实现后续介绍


2、信号集操作函数


对于 信号 的 产生或阻塞 其实就是对 block 和 pending 两张表的 增删改查

2.1、增删改查


对于 位图 的 增删改查 是这样操作的:

增:| 操作,将比特位置为 1
删:& 操作,将比特位置为 0
改:| 或 & 操作,灵活变动
查:判断指定比特位是否为 1 即可
比特作为基本单位,不推荐让我们直接进行操作,操作系统也不同意,于是提供了一批 系统接口,用于对 信号集 进行操作

#include <signal.h>

int sigemptyset(sigset_t *set);	//初始化信号集
int sigfillset(sigset_t *set);	//初识化信号集
int sigaddset(sigset_t *set, int signum);	//增
int sigdelset(sigset_t *set, int signum);	//删
int sigismember(const sigset_t *set, int signum);	//查  

 

这些函数都是 成功返回 0,失败返回 -1

至于参数,非常简单,无非就是 待操作的信号集变量、待操作的比特位

注意: 在创建 信号集 sigset_t 类型后,需要使用 sigemptyset 或 sigfillset 函数进行初始化,确保 信号集 是合法可用的

2.2、sigprocmask

sigprocmask 函数可用用来对 block 表 进行操作

 

#include <signal.h>

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

 

返回值:成功返回 0,失败返回 -1 并将错误码设置

参数1:对 屏蔽信号集 的操作

SIG_BLOCK 希望添加至当前进程 block 表 中阻塞信号,从 set 信号集中获取,相当于 mask |= set
SIG_UNBLOCK 解除阻塞状态,也是从 set 信号集中获取,相当于 mask &= (~set)
SIG_SETMASK 设置当前进程的 block 表为 set 信号集中的 block 表,相当于 mask = set

参数2:就是一个信号集,主要从此信号集中获取屏蔽信号信息

参数3:也是一个信号集,保存进程中原来的 block 表(相当于给你操作后,反悔的机会)

这个函数就是 参数 1 比较有讲究,主打的就是一个 从 set 信号集 中获取阻塞信号相关信息,然后对进程中的 block 表进行操作,并且有三种不同的操作方式

演示程序1:将 2 号信号阻塞,尝试通过 键盘键入 发出 2 信号

 

#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;

int main()
{
    //创建信号集
    sigset_t set, oset;

    //初始化信号集
    sigemptyset(&set);
    sigemptyset(&oset);

    //阻塞2号信号
    sigaddset(&set, 2);	//2 号信号被记录

    //设置当前进程的 block 表
    sigprocmask(SIG_BLOCK, &set, &oset);

    //死循环
    while(true)
    {
        cout << "我是一个进程,我正在运行" << endl;
        sleep(1);
    }

    return 0;
}

显然,当 2 号信号被阻塞后,是 无法被递达 的,进程也就无法终止了

演示程序2:在程序运行五秒后,解除阻塞状态

 

#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;

int main()
{
    // 创建信号集
    sigset_t set, oset;

    // 初始化信号集
    sigemptyset(&set);
    sigemptyset(&oset);

    // 阻塞2号信号
    sigaddset(&set, 2);	//2 号信号被记录

    // 设置当前进程的 屏蔽信号集
    sigprocmask(SIG_BLOCK, &set, &oset);

    // 死循环
    int n = 0;
    while (true)
    {
        if (n == 5)
        {
            // 采用 SIG_SETMASK 的方式,覆盖进程的 block 表
            sigprocmask(SIG_SETMASK, &oset, nullptr); // 不接收进程的 block 表
        }

        cout << "我是一个进程,我正在运行" << endl;
        n++;
        sleep(1);
    }

    return 0;
}

现象:在 2 号信号发出、程序运行五秒解除阻塞后,信号才被递达,进程被终止

如何证明信号已递达?

当 n == 5 时,解除阻塞状态,程序立马结束
并只打印了 五条 语句,证明在第六秒时,程序就被终止了
至于如何进一步证明,需要借助 未决信号表


2.3、sigpending

 


这个函数很简单,获取当前进程中的 未决信号集

#include <signal.h>

int sigpending(sigset_t *set);

返回值:成功返回 0,失败返回 -1 并将错误码设置

参数:待获取的 未决信号集

如何根据 未决信号集 打印 pending 表

  • 使用函数 sigismember 判断当前信号集中是否存在该信号,如果存在,输出 1,否则输出 0
  • 如此重复,将 31 个信号全部判断打印输出即可
#include <iostream>
#include <cassert>
#include <unistd.h>
#include <signal.h>
using namespace std;

static void DisplayPending(const sigset_t pending)
{
    //打印 pending 表
    cout << "当前进程的 pending 表为: ";
    int i = 1;
    while(i < 32)
    {
        if(sigismember(&pending, i))
            cout << "1";
        else
            cout << "0";
        
        i++;
    }
    cout << endl;
}

int main()
{
    // 创建信号集
    sigset_t set, oset;

    // 初始化信号集
    sigemptyset(&set);
    sigemptyset(&oset);

    // 阻塞2号信号
    sigaddset(&set, 2);	//记录 2 号信号

    // 设置当前进程的 屏蔽信号集
    sigprocmask(SIG_BLOCK, &set, &oset);

    // 死循环
    int n = 0;
    while (true)
    {
        if (n == 5)
        {
            // 采用 SIG_SETMASK 的方式,覆盖进程的 block 表
            sigprocmask(SIG_SETMASK, &oset, nullptr);   // 不接收进程的 block 表
        }

        //获取进程的 未决信号集
        sigset_t pending;
        sigemptyset(&pending);

        int ret = sigpending(&pending);
        assert(ret == 0);
        (void)ret;    //欺骗编译器,避免 release 模式中出错

        DisplayPending(pending);

        n++;
        sleep(1);
    }

    return 0;
}

结果:当 2 号信号发出后,当前进程的 pending 表中的 2 号信号位被置为 1,表示该信号属于 未决 状态,并且在五秒之后,阻塞结束,信号递达,进程终止

疑问:当阻塞解除后,信号递达,应该看见 pending 表中对应位置的值由 1 变为 0,但为什么没有看到?

很简单,因为当前 2 号信号的执行动作为终止进程,进程都终止了,当然看不到
解决方法:给 2 号信号先注册一个自定义动作(别急着退出进程)

#include <iostream>
#include <cassert>
#include <unistd.h>
#include <signal.h>
using namespace std;

static void handler(int signo)
{
    cout << signo << " 号信号确实递达了" << endl;
    //最终不退出进程
}

static void DisplayPending(const sigset_t pending)
{
    // 打印 pending 表
    cout << "当前进程的 pending 表为: ";
    int i = 1;
    while (i < 32)
    {
        if (sigismember(&pending, i))
            cout << "1";
        else
            cout << "0";

        i++;
    }
    cout << endl;
}

int main()
{
    // 更改 2 号信号的执行动作
    signal(2, handler);

    // 创建信号集
    sigset_t set, oset;

    // 初始化信号集
    sigemptyset(&set);
    sigemptyset(&oset);

    // 阻塞2号信号
    sigaddset(&set, 2);	//记录 2 号信号

    // 设置当前进程的 屏蔽信号集
    sigprocmask(SIG_BLOCK, &set, &oset);

    // 死循环
    int n = 0;
    while (true)
    {
        if (n == 5)
        {
            // 采用 SIG_SETMASK 的方式,覆盖进程的 block 表
            sigprocmask(SIG_SETMASK, &oset, nullptr); // 不接收进程的 block 表
        }

        // 获取进程的 未决信号集
        sigset_t pending;
        sigemptyset(&pending);

        int ret = sigpending(&pending);
        assert(ret == 0);
        (void)ret; // 欺骗编译器,避免 release 模式中出错

        DisplayPending(pending);

        n++;
        sleep(1);
    }

    return 0;
}

显然,这就是我们想要的最终结果

先将信号 阻塞,信号发出后,无法 递达,始终属于 未决 状态,当阻塞解除后,信号可以 递达,信号处理之后,未决 表中不再保存信号相关信息,因为已经处理了

综上,信号在发出后,在处理前,都是保存在 未决表 中的

注意:

针对信号的 增删改查 都需要通过 系统调用 来完成,不能擅自使用位运算
sigprocmask、sigpending 这两个函数的参数都是 信号集,前者是 屏蔽信号集,后者是 未决信号集
在对 信号集 进行增删改查前,一定要先初始化
信号在被解除 阻塞状态 后,很快就会 递达 了
关于信号何时递达、以及递达后的处理动作,在下一篇文章中揭晓
以上关于 信号、信号集 的操作都是在进程中进行的,不影响操作系统


3.信号的处理机制

3.1处理情况 

 普通情况

所谓的普通情况就是指 信号没有被阻塞,直接产生,记录未决信息后,再进行处理

在这种情况下,信号是不会被立即递达的,也就无法立即处理,需要等待合适的时机

特殊情况

 

当信号被 阻塞 后,信号 产生 时,记录未决信息,此时信号被阻塞了,也不会进行处理

当阻塞解除后,信号会被立即递达,此时信号会被立即处理

特殊情况 很好理解,就好比往气球里吹气,当气球炸了,空气会被立即释放,因为空气是被气球 阻塞 的,当气球炸了之后(阻塞 解除),空气立马往外跑,这不就是 立即递达、立即处理 吗?

3.2合适时机

信号的产生是 异步 的

也就是说,信号可能随时产生,当信号产生时,进程可能在处理更重要的事,此时贸然处理信号显然不够明智

 比如进程正在执行一个重要的 IO,突然一个终止信号发出,IO 立即终止,对进程、磁盘都不好

因此信号在 产生 后,需要等进程将 更重要 的事忙完后(合适的时机),才进行 处理

合适的时机:进程从 内核态 返回 用户态 时,会在操作系统的指导下,对信号进行检测及处理

至于处理动作,分为:默认动作、忽略、用户自定义

搞清楚 “合适” 的时机 后,接下来需要学习 用户态 和 内核态 相关知识

4用户态与内核态

 对于 用户态、内核态 的理解及引出的 进程地址空间 和 信号处理过程 相关知识是本文的重难点

4.1、概念

先来看看什么是 用户态和内核态

用户态执行用户所写的代码时,就属于 用户态

内核态执行操作系统的代码时,就属于 内核态

自己写的代码被执行很好理解,操作系统的代码是什么?

  • 操作系统也是由大量代码构成的
  • 在对进程进行调度、执行系统调用、异常、中断、陷阱等,都需要借助操作系统之手
  • 此时执行的就是操作系统的代码

 

也就是说,用户态 与 内核态 是两种不同的状态,必然存在相互转换的情况

用户态 切换为 内核态:

当进程时间片到了之后,进行进程切换动作
调用系统调用接口,比如 open、close、read、write 等
产生异常、中断、陷阱等
内核态 切换为 用户态:

进程切换完毕后,运行相应的进程
系统调用结束后
异常、中断、陷阱等处理完毕
信号的处理时机就是 内核态 切换为 用户态,也就是 当把更重要的事做完后,进程才会在操作系统的指导下,对信号进行检测、处理

下面来结合 进程地址空间 深入理解 操作系统的代码 及 状态切换 的相关内容(拓展知识)

4.2、重谈进程地址空间


首先简单回顾下 进程地址空间 的相关知识:

进程地址空间 是虚拟的,依靠 页表+MMU机制 与真实的地址空间建立映射关系
每个进程都有自己的 进程地址空间,不同 进程地址空间 中地址可能冲突,但实际上地址是独立的
进程地址空间 可以让进程以统一的视角看待自己的代码和数据

不难发现,在 进程地址空间 中,存在 1 GB 的 内核空间,每个进程都有,而这 1 GB 的空间中存储的就是 操作系统 相关 代码 和 数据,并且这块区域采用 内核级页表 与 真实地址空间 进行映射

为什么要区分 用户态 与 内核态 ?

  • 内核空间中存储的可是操作系统的代码和数据,权限非常高,绝不允许随便一个进程对其造成影响
  • 区域的合理划分也是为了更好的进行管理

所谓的 执行操作系统的代码及系统调用,就是在使用这 1 GB 的内核空间 

进程间具有独立性,比如存在用户空间中的代码和数据是不同的,难道多个进程需要存储多份 操作系统的代码和数据 吗?

当然不用,内核空间比较特殊,所有进程最终映射的都是同一块区域,也就是说,进程只是将 操作系统代码和数据 映射入自己的 进程地址空间 而已
而 内核级页表 不同于 用户级页表,专注于对 操作系统代码和数据 进行映射,是很特殊的

当我们执行诸如 open 这类的 系统调用 时,会跑到 内核空间 中调用对应的函数

而 跑到内核空间 就是 用户态 切换为 内核态 了(用户空间切换至内核空间)

这个 跑到 是如何实现的呢?
在 CPU 中,存在一个 CR3 寄存器,这个 寄存器 的作用就是用来表征当前处于 用户态 还是 内核态

当寄存器中的值为 3 时:表示正在执行用户的代码,也就是处于 用户态
当寄存器中的值为 0 时:表示正在执行操作系统的代码,也就是处于 内核态
通过一个 寄存器,表征当前所处的 状态,修改其中的 值,就可以表示不同的 状态,这是很聪明的做法

重谈 进程地址空间 后,得到以下结论

所有进程的用户空间 [0, 3] GB 是不一样的,并且每个进程都要有自己的 用户级页表 进行不同的映射
所有进程的内核空间 [3, 4] GB 是一样的,每个进程都可以看到同一张内核级页表,从而进行统一的映射,看到同一个 操作系统
操作系统运行 的本质其实就是在该进程的 内核空间内运行的(最终映射的都是同一块区域)
系统调用 的本质其实就是在调用库中对应的方法后,通过内核空间中的地址进行跳转调用

 

那么进程又是如何被调度的呢?

操作系统的本质
- 操作系统也是软件啊,并且是一个死循环式等待指令的软件
- 存在一个硬件:操作系统时钟硬件,每隔一段时间向操作系统发送时钟中断
进程被调度,就意味着它的时间片到了,操作系统会通过时钟中断,检测到是哪一个进程的时间片到了,然后通过系统调用函数 schedule() 保存进程的上下文数据,然后选择合适的进程去运行

4.3、信号的处理过程

当在 内核态 完成某种任务后,需要切回 用户态,此时就可以对信号进行 检测 并 处理 了

情况1:信号被阻塞,信号产生/未产生

 

信号都被阻塞了,也就不需要处理信号,此时不用管,直接切回 用户态 就行了

下面的情况都是基于 信号未被阻塞 且 信号已产生 的前提

情况2:当前信号的执行动作为 默认

 大多数信号的默认执行动作都是 终止 进程,此时只需要把对应的进程干掉,然后切回 用户态 就行了

情况3:当前信号的执行动作为 忽略 

 当信号执行动作为 忽略 时,不做出任何动作,直接返回 用户态

情况4:当前信号的执行动作为 用户自定义 

这种情况就比较麻烦了,用户自定义的动作位于 用户态 中,也就是说,需要先切回 用户态,把动作完成了,重新坠入 内核态,最后才能带着进程的上下文相关数据,返回 用户态

在 内核态 中,也可以直接执行 自定义动作,为什么还要切回 用户态 执行自定义动作?

因为在 内核态 可以访问操作系统的代码和数据,自定义动作 可能干出危害操作系统的事
在 用户态 中可以减少影响,并且可以做到溯源
为什么不在执行完 自定义动作 直接后返回进程?

因为 自定义动作 和 待返回的进程 属于不同的堆栈,是无法返回的
并且进程的上下文数据还在内核态中,所以需要先坠入内核态,才能正确返回用户态

注意: 用户自定义的动作,需要先切换至 用户态 中执行,执行结束后,还需要坠入 内核态

通过一张图快速记录信号的 处理 过程

5.信号捕捉

 接下来谈谈 信号 是如何被 捕捉 的

5.1、内核如何实现信号的捕捉?

如果信号的执行动作为 用户自定义动作,当信号 递达 时调用 用户自定义动作,这一动作称为 信号捕捉

用户自定义动作 是位于 用户空间 中的

 

当 内核态 中任务完成,准备返回 用户态 时,检测到信号 递达,并且此时为 用户自定义动作,需要先切入 用户态 ,完成 用户自定义动作 的执行;因为 用户自定义动作 和 待返回的函数 属于不同的 堆栈 空间,它们之间也不存在 调用与被调用 的关系,是两个 独立的执行流,需要先坠入 内核态 (通过 sigreturn() 坠入),再返回 用户态 (通过 sys_sigreturn() 返回)

上述过程可以总结为下图

5.2sigaction 

 sigaction 也可以 用户自定义动作,比 signal 功能更丰富

#include <signal.h>

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

struct sigaction 
{
	void     (*sa_handler)(int);	//自定义动作
	void     (*sa_sigaction)(int, siginfo_t *, void *);	//实时信号相关,不用管
	sigset_t   sa_mask;	//待屏蔽的信号集
	int        sa_flags;	//一些选项,一般设为 0
	void     (*sa_restorer)(void);	//实时信号相关,不用管
};

 

返回值:成功返回 0,失败返回 -1 并将错误码设置

参数1:待操作的信号

参数2:sigaction 结构体,具体成员如上所示

参数3:保存修改前进程的 sigaction 结构体信息

这个函数的主要看点是 sigaction 结构体

struct sigaction 
{
	void     (*sa_handler)(int);	//自定义动作
	void     (*sa_sigaction)(int, siginfo_t *, void *);	//实时信号相关,不用管
	sigset_t   sa_mask;	//待屏蔽的信号集
	int        sa_flags;	//一些选项,一般设为 0
	void     (*sa_restorer)(void);	//实时信号相关,不用管
};

其中部分字段不需要管,因为那些是与 实时信号 相关的,我们这里不讨论

重点可以看看 sa_mask 字段

sa_mask:当信号在执行 用户自定义动作 时,可以将部分信号进行屏蔽,直到 用户自定义动作 执行完成

也就是说,我们可以提前设置一批 待阻塞 的 屏蔽信号集,当执行 signum 中的 用户自定义动作 时,这些 屏蔽信号集 中的 信号 将会被 屏蔽(避免干扰 用户自定义动作 的执行),直到 用户自定义动作 执行完成

可以简单用一下 sigaction 函数

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

using namespace std;

static void DisplayPending(const sigset_t pending)
{
    // 打印 pending 表
    cout << "当前进程的 pending 表为: ";
    int i = 1;
    while (i < 32)
    {
        if (sigismember(&pending, i))
            cout << "1";
        else
            cout << "0";

        i++;
    }
    cout << endl;
}

static void handler(int signo)
{
    cout << signo << " 号信号确实递达了" << endl;
    // 最终不退出进程

    int n = 10;
    while (n--)
    {
        // 获取进程的 未决信号集
        sigset_t pending;
        sigemptyset(&pending);

        int ret = sigpending(&pending);
        assert(ret == 0);
        (void)ret; // 欺骗编译器,避免 release 模式中出错

        DisplayPending(pending);
        sleep(1);
    }
}

int main()
{
    cout << "当前进程: " << getpid() << endl;
    
    //使用 sigaction 函数
    struct sigaction act, oldact;

    //初始化结构体
    memset(&act, 0, sizeof(act));
    memset(&oldact, 0, sizeof(oldact));

    //初始化 自定义动作
    act.sa_handler = handler;

    //初始化 屏蔽信号集
    sigaddset(&act.sa_mask, 3);
    sigaddset(&act.sa_mask, 4);
    sigaddset(&act.sa_mask, 5);

    //给 2号 信号注册自定义动作
    sigaction(2, &act, &oldact);

    // 死循环
    while (true);

    return 0;
}

 

当 2 号信号的循环结束(10 秒),3、4、5 信号的 阻塞 状态解除,立即被 递达,进程就被干掉了

注意: 屏蔽信号集 sa_mask 中已屏蔽的信号,在 用户自定义动作 执行完成后,会自动解除 阻塞 状态

6 小结

 

截至目前,信号 处理的所有过程已经全部学习完毕了

信号产生阶段:有四种产生方式,包括 键盘键入、系统调用、软件条件、硬件异常

信号保存阶段:内核中存在三张表,blcok 表、pending 表以及 handler 表,信号在产生之后,存储在 pending 表中

信号处理阶段:信号在 内核态 切换回 用户态 时,才会被处理

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1480641.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蓝桥杯练习系统(算法训练)ALGO-995 24点

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 24点游戏是一个非常有意思的游戏&#xff0c;很流行&#xff0c;玩法很简单&#xff1a;给你4张牌&#xff0c;每张牌上有数…

【python】json转成成yaml中文编码异常显示成:\u5317\u4EAC\u8DEF123\u53F7

姊妹篇&#xff1a;【python】json转成成yaml json数据 {"name": "张三","age": 30,"isMarried": false,"children": [{"name": "小王","age": 5},{"name": "小李",&qu…

mysql查询用户操作日志

mysql查询用户操作日志 说明一、概述二、配置 说明 更新时间&#xff1a;2024/03/01 16:39 本文仅为记录学习轨迹&#xff0c;如有侵权,联系删除 一、概述 先简单介绍一下general log&#xff0c;这个日志主要的作用是记录MySQL所有的SQL语句&#xff0c;不管是查询语句&am…

每日一题 2369

2369. 检查数组是否存在有效划分 题目描述&#xff1a; 给你一个下标从 0 开始的整数数组 nums &#xff0c;你必须将数组划分为一个或多个 连续 子数组。 如果获得的这些子数组中每个都能满足下述条件 之一 &#xff0c;则可以称其为数组的一种 有效 划分&#xff1a; 子数…

Python实现PPT演示文稿中视频的添加、替换及提取

无论是在教室、会议室还是虚拟会议中&#xff0c;PowerPoint 演示文稿都已成为一种无处不在的工具&#xff0c;用于提供具有影响力的可视化内容。PowerPoint 提供了一系列增强演示的功能&#xff0c;在其中加入视频的功能可以大大提升整体体验。视频可以传达复杂的概念、演示产…

unity后期

unity|后处理篇 前言一、Post-Processing 1、 Post-Processing的使用2、Post-Processing后处理效果 抗锯齿①、Ambient Occlusion 环境光遮蔽②、Auto Exposure 自动曝光③、Bloom 辉光/泛光④、Chromatic Aberration | 色差⑤、Color Grading 色调/颜色分级⑥、Depth Of Fiel…

每次提出一个bug都让测试重现,描述得那么清楚,自己操作下不会吗?

一说到测试和开发的关系&#xff0c;你一定会想到一个词“冤家”。 开发的工作就是按照PM的设计将产品最终造出来&#xff0c;而测试则是在开发已完成的工作里纠错。so&#xff0c;测试的工作会让开发很不爽&#xff0c;人之常情&#xff0c;谁都不喜欢自己的劳动成果被别人挑…

解析馆藏文物预防性保护:监测平台与数据传输系统概述

1&#xff09;文物预防性保护监测平台概述 文物预防性保护监测与调控系统是文物环境监测必不可少的关键组成部分之一,在项目实施中,将充分利用前沿物联网技术&#xff0c;如无线网络、低功耗设计、高精度传感器来实现文物保存环境的实时监测与数据分析。此外&#xff0c;还将通…

uniapp 安装安卓、IOS模拟器并调试

一、安装Android模拟器并调试 1.下载并安装Android Studio。 2.创建简单project。 3.安装模拟器。 完成安卓模拟器的安装。 4.启动模拟器。 5.hbuilderx选择模拟器、运行。 点击刷新按钮后出现模拟器&#xff0c;勾选并运行。 6.调试。 在 HBuilderX 中&#xff0c;项目启…

GCN 翻译 - 1

ABSTRACT 我们提出了一种可扩展的在以图结构为基础的数据上的半监督学习&#xff0c;这种方法直接作用在图数据上&#xff0c;可以看做是卷积神经网络的变种。我们选择了图谱理论里面的一阶近似作为我们的卷积结构。我们的模型能够随着图的规模线性伸缩&#xff0c;并且隐藏层…

将镜像上传到私有镜像仓库Harbor

首先你需要安装Harbor服务&#xff1a; https://blog.csdn.net/qq_50247813/article/details/136388229 客户端已经安装docker&#xff1a; https://docs.docker.com/engine/install/centos/ 在docker客户端登录 Harbor 我的Harbor 服务器地址&#xff1a; 192.168.44.161 账号…

抖店无货源违规频发,不能入驻?这个是真的吗?

我是电商珠珠 还没有踏入抖店这个电商行业的新手&#xff0c;单从别人的口中&#xff0c;听说了抖店无货源特别容易违规&#xff0c;还会被扣除全部的保证金&#xff0c;得不偿失之类的话。有的还专门劝诫新手不要做抖店&#xff0c;做了就会亏本之类的话&#xff0c;这搞得人…

首个美国大学宣布与OpenAI建立合作伙伴关系!全美本科AI强校大盘点!

美国亚利桑那州立大学&#xff08;Arizona State University &#xff09;在其官网宣布与OpenAI建立合作伙伴关系&#xff0c;该校也由此成为第一家与OpenAI合作的高等教育机构。 这一合作将ChatGPT Enterprise的先进功能引入亚利桑那州立大学&#xff0c;使教职员工能够探索生…

【Java】UWB高精度工业定位系统项目源代码

目录 UWB技术原理 优势 1. 高精度&#xff1a; 2. 抗干扰能力强&#xff1a; 3. 定位范围广&#xff1a; 4. 实时性强&#xff1a; 应用前景 定位系统源码功能介绍 实时定位&#xff1a; 轨迹回放&#xff1a; 区域管理&#xff1a; 巡检管理: 数据可视化分析&…

YOLOv9改进|加入AKConv模块!

专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&#xff0c;主力高效涨点&#xff01;&#xff01;&#xff01; 一、改进点介绍 AKConv是一种具有任意数量的参数和任意采样形状的可变卷积核&#xff0c;对不规则特征有更好的提取效果。 论文速览&#xff1a;&am…

《TCP/IP详解 卷一》第9章 广播和组播

目录 9.1 引言 9.2 广播 9.2.1 使用广播地址 9.2.2 发送广播数据报 9.3 组播 9.3.1 将组播IP地址转换为组播MAC地址 9.3.2 例子 9.3.3 发送组播数据报 9.3.4 接收组播数据报 9.3.5 主机地址过滤 9.4 IGMP协议和MLD协议 9.4.1 组成员的IGMP和MLD处理 9.4.2 组播路由…

uniapp微信小程序开发踩坑日记:修改组件默认样式

使用uniapp官方组件的时候&#xff0c;我们常常要修改组件的默认样式&#xff0c;但是网上的很多修改组件默认样式的方法都是不生效的&#xff08;因为我都试过了&#xff09; 下面给大家介绍vue构建的uniapp小程序中能够生效的修改组件默认样式的方法 1、在编译后的代码文件…

华为HCIP Datacom H12-821 卷3

1.单选题 四台路由器运行 IS-IS 且已经建立邻接关系&#xff0c;区域号和路由器的等级如图中标记&#xff0c;则 R4到达 10.0.2.2/32 的的 Cost 值为多少? A、40 B、10 C、20 D、30 正确答案&#xff1a; D 解析&#xff1a; 由于没有配置路由渗透&#xff0c;所以R4会选择…

potplayer安装

官网 解压运行即可

WinApp自动化测试之辅助工具介绍

前篇文章中&#xff0c;我们简单介绍了部分WinApp自动化测试脚本常规操作&#xff0c;今天我们来讲剩余的部分。 文件批量上传 文件批量上传和文件单个上传原理是相同的&#xff0c;单个上传直接传入文件路径即可&#xff0c;批量上传需要进入批量上传的文件所在目录&#xf…