【Linux】进程信号 --- 信号保存

news2024/11/26 3:36:30

在这里插入图片描述

👦个人主页:Weraphael
✍🏻作者简介:目前正在学习c++和算法
✈️专栏:Linux
🐋 希望大家多多支持,咱一起进步!😁
如果文章有啥瑕疵,希望大佬指点一二
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍


目录

  • 一、再次认识信号
      • 1.1 为什么要进行信号保存
      • 1.2 信号如何被保存
      • 1.3 信号其他相关常见概念
  • 二、在内核中的表示
  • 三、操作系统中位图的数据类型
      • 3.1 sigset_t --- 信号集类型
      • 3.2 信号集操作函数
  • 四、系统调用接口 --- sigprocmask
  • 五、系统调用接口 --- sigpending
  • 六、总结
  • 七、相关代码

一、再次认识信号

1.1 为什么要进行信号保存

在入门篇提到过:进程收到信号之后,可能不会立即被处理,因为进程可能正在做重要的事,需要等到合适的时间再处理。就比方说:你收到外卖小哥的外卖到了的提醒消息(进程收到信号),但你正在打LOL(更重要的事),晚点下去拿(保存信号),当打完之后再下去(处理信号)。因此,从信号产生到信号处理这段期间,需要将信号保存起来,那如何保存呢?位图!(具体看1.2

  • 对于普通信号,它用的是位图,只要收到就会先保存,但是如果这个信号还没处理,又来个信号,那么就只记得最近一次的信号
  • 而对于实时信号。只要发送了,就要立即处理,哪怕此时进程在忙。它的用的是队列。
  • 关于进程信号,我们重点关心普通信号即可。

1.2 信号如何被保存

信号列表

对应的1~31号信号我们称为普通信号,是不是一个 int整型(32bit 就足以表示所有普通信号的产生信息了。

对于普通的信号处理而言,进程主要关心自己是否有信号以及收到了哪个具体的信号。并且,这个信号是由操作系统发送给进程的进程控制块(task_struct。所以结构体task_struct内一定维护类似于int signal字段。

如果给进程发的是一号信号,那么则将bit位的第一位给置为1(注意这里有第0位,表示没有收到信号),后面以此类推。所以描述一个信号,用比特位的位置来表示,即 普通信号是用位图来管理信号

总结:

  1. 比特位的内容是0还是1,表明是否收到信号。
  2. 比特位的位置(第几个),表示信号的编号。
  3. 所谓的“发信号”,本质就是操作系统(管理者)去修改task_struct的信号位图对应的比特位。也就是写信号

说明:在后面我们会说,信号是被保存在pending表中的

1.3 信号其他相关常见概念

在这里插入图片描述

  • 信号产生(Produce:由四种不同的方式发出信号。(详情见)
  • 信号未决(Pending:信号从产生到处理的中间状态。(信号保存)
  • 信号递达(Delivery:进程收到信号后,对信号的处理动作。(信号处理)

除此之外,进程还允许阻塞(屏蔽)某些信号,我们称之为 信号阻塞这意味着进程暂时不接收阻塞的信号,使其保持在未决信号集合中,只有在解除阻塞后,信号才会被递送到信号处理程序中。注意:信号阻塞是一种手段,可以发生在 信号处理 前的任意时段

注意区分阻塞和忽略(信号处理动作):忽略是真的什么都不做,相当于“已读不回”;而阻塞是信号还没到处理阶段,它可能会被处理,可能不会被处理,相当于“未读”,是一种状态。

二、在内核中的表示

在这里插入图片描述

在操作系统中,有三张表分别是block表、pending表、handler表。共同构成了操作系统内核管理信号的机制。

  • block表:也称信号阻塞表(位图)。它主要用于记录信号有没有被阻塞。如果某信号(比特位的位置)被设置成1,表示信号被阻塞;如果某信号被设置成0,表示信号没有被阻塞。
  • pending表:也称未决信号表(位图)。它主要用于记录已经向进程发送但尚未被处理的信号(信号保存)。如果信号(比特位的位置)对应的比特位是1,则表示该信号是pending的,即信号产生但还未被处理。当进程的信号处理函数还没有准备好处理信号时,信号会保持在pending表中。一旦信号的处理函数准备好,内核会从pending表中选择一个信号交付给进程。注意:如果这个信号还没处理,又来个信号,那么就只记得最近一次的信号
  • handler表:也称信号处理程序表(函数指针数组)该表存储着信号[1,31]的系统默认处理动作的函数指针(函数地址);如果用户自定设定了方法(如singal函数自定义处理方式),则会将该方法的地址填入到信号处理程序表中。当进程接收到一个信号时,内核会查找该信号对应的处理函数,并执行该处理函数来响应信号事件。
  • 信号它的一切操作都是围绕这三张表!!!

处理信号有三种方式:忽略SIG_IGN、系统默认动作SIG_DEL、用户自定义。用户自定义在【信号产生】已经用很多次了,这次来见见忽略和系统默认动作。

  • Linux操作系统对于忽略和默认动作的定义

在这里插入图片描述

默认动作就是将0强转为函数指针类型,忽略动作则是将1强转为函数指针类型,分别对应handler 表中的01下标位置

我们可以使用代码来看看效果

  • 忽略动作SIG_IGN

在这里插入图片描述

【程序结果】

在这里插入图片描述

  • 系统默认动作SIG_DEL

在这里插入图片描述

【程序结果】

在这里插入图片描述

三、操作系统中位图的数据类型

3.1 sigset_t — 信号集类型

无论是block表还是pending表,它们都是位图结构,同时也是内核的数据结构。由于操作系统不相信任何用户,它不允许用户直接修改这两张表。所以要修改这些表,操作系统一定提供了一系列的系统调用接口。

在内核中,操作系统将信号操作所需要的位图结构封装成了一个结构体类型__sigset_t,在用户层,我们可以直接使用sigset_t类型。

在这里插入图片描述

sigset_t称为信号集类型,这个类型可以表示每个信号的状态。

  • 在阻塞信号集block表中的含义是该信号是否被阻塞。
  • 而在未决信号集pending表中的含义是该信号是否处于未决状态。

至于这个类型内部如何存储这些比特位则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释,比如直接打印sigset_t变量是没有意义的!

3.2 信号集操作函数

#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所指向的信号集,将所有信号的对应比特位清零,表示该信号集不包含任何有效信号。
  • sigfillset函数:初始化set所指向的信号集,将所有信号的对应bit设置成1,表示该信号集的有效信号包括系统支持的所有信号。
  • sigaddset函数:向指定的信号集中添加特定的信号,设置1
  • sigdelset函数,向指定的信号集中去掉特定的信号,设置0
  • 以上四个函数都是成功返回0,出错返回-1
  • sigismember函数:判断一个信号集的有效信号中是否包含某种信号。若包含则返回1,不包含则返回0

注意:在使用sigset_ t类型的变量之前,一定要调用sigemptysetsigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddsetsigdelset在该信号集中添加或删除某种有效信号。

四、系统调用接口 — sigprocmask

sigprocmask函数用来对block表进行操作(阻塞信号)。函数原型如下:

#include <signal>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

参数解释:

  • how参数:指定操作的类型,可以是以下值之一:

    • SIG_BLOCK:将 set中的信号添加到当前进程的block表中(mask|=set)。
    • SIG_UNBLOCK:从当前进程的block表中移除set中的信号(mask&=~set)。
    • SIG_SETMASK:设置当前进程的block表为 set 中的值(mask==set)。
  • set 参数:就是一个信号集,主要从此信号集中获取屏蔽信号信息

  • oldset 参数:指向 sigset_t 类型的指针,用于存储之前的block表。如果不需要获取旧的block表,可以将 oldset 设为 nullptr

  • 返回值:若成功则为0,若出错则为-1

五、系统调用接口 — sigpending

sigpending函数用来获取当前进程中的未决信号集pengding表,为了做检查。函数原型如下:

#include <signal.h>
int sigpending(sigset_t *set);

参数说明:

  • 参数:待获取的未决信号集
  • 返回值:成功返回0,失败返回 -1并将错误码设置

如何根据打印 pending

  1. 使用函数sigismember判断当前信号集中是否存在该信号,如果存在,输出1,否则输出0
  2. 如此重复,将 31 个信号全部判断打印输出即可

【代码】

代码逻辑:循环打印pending表,阻塞2号信号。若进程没有收到2号信号,那么位图一定是全0;当进程收到2号信号时,位图的低2位比特位由01

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

int main()
{
    // 在用户层定义sigset_t变量
    sigset_t sset;
    // 初始化信号集,将所有信号的对应比特位清零
    sigemptyset(&sset);
    // 将信号集sset添加特定的信号,设置1
    sigaddset(&sset, 2);
    // sigprocmask函数用来对block表进行操作(阻塞信号)
    sigset_t oldset;
    sigemptyset(&oldset);
    sigprocmask(SIG_SETMASK, &sset, &oldset);

    // 重复打印当前进程的pending表。
    // 虽然我们阻塞了2号信号,但是只有没产生信号,pending表一定是全0
    sigset_t pending_t; // 获取pending表
    while (true)
    {
        // sigpending函数用来获取当前进程中的未决信号集pending表
        int n = sigpending(&pending_t);
        if (n < 0) continue; 
        // 打印
        // 判断当前信号集中是否存在该信号,如果存在,输出1,否则输出0
        for (int i = 31; i >= 1; i--)
        {
            if (sigismember(&pending_t, i))
            {
                cout << "1";
            }
            else
            {
                cout << "0";
            }
        }
        cout << endl << endl;
             
        sleep(1);
    }
    return 0;
}

【程序结果】

在这里插入图片描述

我们再看下面的代码,这段代码在上面的基础上加了解除2号信号阻塞,那么当收到2号信号后,由于解除了阻塞,对应的比特位由1变为0

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

int main()
{
    // 在用户层定义sigset_t变量
    sigset_t sset;
    // 初始化信号集,将所有信号的对应比特位清零
    sigemptyset(&sset);
    // 将信号集sset添加特定的信号,设置1
    sigaddset(&sset, 2);
    // sigprocmask函数用来对block表进行操作(阻塞信号)
    sigset_t oldset;
    sigemptyset(&oldset);
    sigprocmask(SIG_SETMASK, &sset, &oldset);

    // 重复打印当前进程的pending表。
    // 虽然我们阻塞了2号信号,但是只有没产生信号,pending表一定是全0
    sigset_t pending_t; // 获取pending表
    int cnt = 0;
    while (true)
    {
        // sigpending函数用来获取当前进程中的未决信号集pending表
        int n = sigpending(&pending_t);
        if (n < 0) continue; 
        // 打印
        // 判断当前信号集中是否存在该信号,如果存在,输出1,否则输出0
        for (int i = 31; i >= 1; i--)
        {
            if (sigismember(&pending_t, i))
            {
                cout << "1";
            }
            else
            {
                cout << "0";
            }
        }
        cout << endl << endl;
             
        sleep(1);
        // 解除阻塞
        cnt++;
        if (cnt == 6)
        {
        	cout << "已解除2号信号阻塞" << endl;
            sigprocmask(SIG_SETMASK, &oldset, nullptr);
        }
    }
    return 0;
}

【程序结果】

在这里插入图片描述

通过以上结果:2 号信号产生后,当前进程的 pending 表中的 2 号信号位被置为 1,表示该信号属于未决状态,并且在六秒之后,阻塞结束,信号递达,进程终止。

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

这是因为当阻塞解除后信号递达,而2号信号的默认执行动作为终止进程,进程都终止了,当然看不到。

解决方法:自定义2号信号的处理动作动作(别急着退出进程)

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

void handler(int signum)
{
    cout << "解除" << signum << "号信号的阻塞 " << endl;
    // 最终不退出进程 没加exit
}

int main()
{
    // 捕捉2号信号
    signal(2, handler);

    // 在用户层定义sigset_t变量
    sigset_t sset;
    // 初始化信号集,将所有信号的对应比特位清零
    sigemptyset(&sset);
    // 将信号集sset添加特定的信号,设置1
    sigaddset(&sset, 2);
    // sigprocmask函数用来对block表进行操作(阻塞信号)
    sigset_t oldset;
    sigemptyset(&oldset);
    sigprocmask(SIG_SETMASK, &sset, &oldset);

    // 重复打印当前进程的pending表。
    // 虽然我们阻塞了2号信号,但是只有没产生信号,pending表一定是全0
    sigset_t pending_t; // 获取pending表
    int cnt = 0;
    while (true)
    {
        // sigpending函数用来获取当前进程中的未决信号集pending表
        int n = sigpending(&pending_t);
        if (n < 0)
            continue;
        // 打印
        // 判断当前信号集中是否存在该信号,如果存在,输出1,否则输出0
        for (int i = 31; i >= 1; i--)
        {
            if (sigismember(&pending_t, i))
            {
                cout << "1";
            }
            else
            {
                cout << "0";
            }
        }
        cout << endl
             << endl;

        sleep(1);
        // 解除阻塞
        cnt++;
        if (cnt == 4)
        {
            sigprocmask(SIG_SETMASK, &oldset, nullptr);
        }
    }
    return 0;
}

【程序结果】

在这里插入图片描述

那如果将所有的信号全部屏蔽掉,那是不是信号就不会被递达(处理)了?

我们能想到的,操作系统的设计者肯定考虑到了,肯定有一些信号是无法被屏蔽的

9号和19号不可被屏蔽,也不可被捕捉。我们可以来验证

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

int main()
{
	cout << "my pid is " << getpid() << endl;
	sleep(3);
	
    sigset_t sset, oldset;
    sigemptyset(&sset);
    sigemptyset(&oldset);
    for (int i = 1; i <= 31; i++)
    {
        sigaddset(&sset, i);
    }
    // 将所有的信号屏蔽
    sigprocmask(SIG_SETMASK, &sset, &oldset);
    sigset_t pending_t;
    while (true)
    {
        int n = sigpending(&pending_t);
        if (n < 0)
            continue;
        for (int signo = 31; signo >= 1; signo--)
        {
            cout << sigismember(&pending_t, signo);
        }
        cout << endl
             << endl;
        sleep(1);
    }

    return 0;
}

代码如上,大家可以通过kill -num <pid>命令自行去试。

六、总结

在这里插入图片描述

七、相关代码

本篇博客的相关代码:点击跳转

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

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

相关文章

Linux中进程间通信--匿名管道和命名管道

本篇将会进入 Linux 进程中进程间通信&#xff0c;本篇简要的介绍了 Linux 中进程为什么需要通信&#xff0c;进程间通信的常用方式。然后详细的介绍了 Linux 进程间的管道通信方式&#xff0c;管道通信分为匿名管道和命名管道&#xff0c;本篇分别介绍了其实现的原理&#xff…

4.Java Web开发模式(javaBean+servlet+MVC)

Java Web开发模式 一、Java Web开发模式 1.javaBean简介 JavaBeans是Java中一种特殊的类&#xff0c;可以将多个对象封装到一个对象&#xff08;bean&#xff09;中。特点是可序列化&#xff0c;提供无参构造器&#xff0c;提供getter方法和setter方法访问对象的属性。名称中…

顺序 IO 和 随机IO

顺序 IO 和 随机IO 顺序IO 和 随机IO 是计算机存储系统领域中的概念&#xff0c;主要涉及数据的读取和写入方式。这些术语通常在讨论硬盘驱动器&#xff08;HDDs&#xff09;、固态驱动器&#xff08;SSD&#xff09;以及其他存储设备的性能时使用。 顺序IO&#xff08;Sequen…

TeamViewer关闭访问密码或固定一组密码不变

TeamViewer的新UI界面变化较大&#xff0c;网上的一些信息已经不再有效&#xff0c;更新后的访问密码在如下图所示&#xff1a; 演示的版本为7.21.4—— 设置每次你的设备访问的密码

Hi6274 反激式20瓦电源芯片

HI6274为高性能多模式 PWM 反激式20瓦电源芯片。HI6274较少的外围元器件、较低的系统成本可设计出高性能的"无Y"开关电源。HI6274提供了极为全面和性能优异的智能化保护功能&#xff0c;包括逐周期过流保护、过载保护、软启动、芯片过温保护、可编程输出过压保护功能…

Kettle 登录示例 POST请求

登录接口是post请求&#xff0c;组装Body为json字符串 var body "{\"username\":\""username"\",\"password\": \""password"\",\"code\":\""verification"\",\"uuid\…

【算法/训练】:前缀和差分

&#x1f680; 前言&#xff1a; 前面我们已经通过 【算法/学习】前缀和&&差分-CSDN博客 学习了前缀和&&差分的效相关知识&#xff0c;现在我们开始进行相关题目的练习吧 1. 校门外的树 思路&#xff1a;给[0, n]的数组都标记为1&#xff0c;然后输出m行范围…

初学Mybatis之配置解析

MyBatis 中文网配置教程 mybatis-config.xml 环境配置&#xff08;environments&#xff09; 尽管可以配置多个环境&#xff0c;但每个 SqlSessionFactory 实例只能选择一种环境 可以有多个 enviroment&#xff0c;但是 enviroments default&#xff08;默认&#xff09;只…

Linux:Linux发展史

大家好&#xff01;此篇文章并非技术博文&#xff0c;而是简单了解Linux的时代背景和发展史&#xff0c;只有知其所以然才能让我们更好地让走进Liunx的世界&#xff01; 一、计算机的发展历史背景 首先我们要知道&#xff0c;早期大多数科技的进步都是以国家的对抗为历史背景的…

【优秀设计案例】基于K-Means聚类算法的球员数据聚类分析设计与实现

背景及意义 随着NBA比赛的日益竞争激烈&#xff0c;球队需要更加深入地了解球员的能力和特征&#xff0c;以制定更有效的战术和球队管理策略。而NBA球员的统计数据包含了大量有价值的信息&#xff0c;通过对这些数据进行聚类分析&#xff0c;可以揭示出球员之间的相似性和差异…

Java生成四位纯数字并且确保唯一性

背景&#xff1a; 给了我一个需求&#xff0c;由于某些问题原因&#xff0c;需要给属性和数据添加一个code字段&#xff0c;这是给我发的消息 这两个要求其实是同一个需求&#xff0c;就是在创建对象的时候塞入一个unique的code嘛&#xff0c;听起来很简单吧&#xff0c;但是实…

WPF串口通讯程序

目录 一 设计原型 二 后台源码 一 设计原型 二 后台源码 using HardwareCommunications; using System.IO.Ports; using System.Windows;namespace PortTest {/// <summary>/// Interaction logic for MainWindow.xaml/// </summary>public partial class MainW…

二叉树精选面试题

&#x1f48e; 欢迎大家互三&#xff1a;2的n次方_ 1. 相同的树 100. 相同的树 同时遍历两棵树 判断结构相同&#xff1a;也就是在遍历的过程中&#xff0c;如果有一个节点为null&#xff0c;另一棵树的节点不为null&#xff0c;那么结构就不相同 判断值相同&#xff1a;只需…

【刷题汇总 -- 压缩字符串(一)、chika和蜜柑、 01背包】

C日常刷题积累 今日刷题汇总 - day0181、压缩字符串(一)1.1、题目1.2、思路1.3、程序实现 2、chika和蜜柑2.1、题目2.2、思路2.3、程序实现 3、 01背包3.1、题目3.2、思路3.3、程序实现 -- dp 4、题目链接 今日刷题汇总 - day018 1、压缩字符串(一) 1.1、题目 1.2、思路 读完…

宠物空气净化器哪款除臭效果好?质量好的养狗空气净化器排名

作为一个宠物家电小博主&#xff0c;炎炎夏日&#xff0c;家中的宠物给你带来的不仅仅是温暖的陪伴&#xff0c;还有那挥之不去的宠物异味。普通空气净化器虽然能够应对一般的空气净化需求&#xff0c;但对于养猫家庭特有的挑战&#xff0c;如宠物毛发、皮屑和异味等&#xff0…

模版初阶与STL

1.泛型编程 void Swap(int& left, int& right) {int temp left;left right;right temp; } void Swap(double& left, double& right) {double temp left;left right;right temp; } void Swap(char& left, char& right) {char temp left;left r…

Linux系统安装的详细步骤详解

在VM虚拟机上安装Linux系统全过程&#xff0c;闭眼跟着走就行&#xff01;&#xff01;&#xff01; 1、准备好VMware Worestation虚拟机软件和Linux系统的映像文件 2、点击创建新的虚拟机 3、在新建虚拟机向导中&#xff0c;选择典型安装模式。典型安装模式可以通过几个简单的…

简析漏洞生命周期管理的价值与关键要求

开展全面且持续的漏洞管理工作&#xff0c;对于企业组织改善数字化应用安全状况&#xff0c;降低潜在风险&#xff0c;并保持数字资产的完整性和可信度至关重要。做好漏洞管理并不容易&#xff0c;组织不仅需要拥有健全的漏洞管理策略&#xff0c;同时还要辅以明确定义的漏洞管…

VulnHub:tenderfoot1

靶机下载地址 信息收集 主机发现 扫描攻击机同网段存活主机。nmap 192.168.31.0/24 -Pn -T4 目标主机ip&#xff1a;192.168.31.199。 端口扫描 nmap 192.168.31.199 -A -p- -T4 开放了22,80端口&#xff0c;即ssh和http服务。 目录扫描 访问http服务&#xff0c;是apac…

IPython魔法命令的深入应用

目录 IPython魔法命令的深入应用 一、魔法命令基础 1. 魔法命令的分类 2. 基本使用 二、高级应用技巧 1. 数据交互与处理 2. 交互式编程与调试 三、魔法命令的进阶操作 1. 自定义魔法命令 2. 利用魔法命令优化工作流程 四、总结与展望 IPython魔法命令的深入应用 IP…