Linux——信号的保存与处理

news2024/11/24 20:24:04

目录

前言

一、信号的常见概念

1.信号递达 

2.信号未决

3.信号阻塞

二、Linux中的递达未决阻塞

三、信号集

四、信号集的处理

1.sig相关函数

2.sigprocmask()函数

3.sigpending()函数

五、信号的处理时机

六、信号处理函数


前言

在之前,我们学习了信号的概念以及信号的产生方式,知道进程在何种情况下会收到信号,进程收到并不一定会立刻执行,因此需要对信号进行保存,再来处理信号。

一、信号的常见概念

在学习信号的保存前, 我们先来看看信号的常见概念

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

1.信号递达 

执行信号的处理动作称之为信号递达,具体操作中,对信号一般有三种处理方法

  1. 信号默认处理
  2. 信号忽略
  3. 信号自定义捕捉

我们先来看看信号的默认处理,代码中首先是先自定义捕捉了,当我们前sleep(5),之前输入ctrl+c信号,就会被自定义捕捉,而再后输入,就是默认的处理方式了。 

因此5秒之前,输入ctrl+c会先打印自定义内容,然后2号信号变默认处理方式,开始循环,现在我们给2号新号就会终止进程

那如果我们选择忽略2号新号 

 自然ctrl+c就不能终止了。

SIG_DFL和SIG_IGN的本质就是被函数指针强转的0和1,因为用户传递的地址是非常大的,0和1地址处是代码区,用户是没有设置权限的,因此不用担心传递的自定义函数地址是0和1,如果是0和1,编译器就会特殊处理,知道你想要的是默认还是忽略。

注意:忽略也是一种处理方式

2.信号未决

信号从产生到递达之间的状态,称之为信号未决

信号产生时,可能当前进程再做更重要的事情,不能及时递达,这种情况就叫做信号未决,那么就要求进程对信号进行保存,进程使用了信号位图的方式进行保存。那么信号如果被处理,信号位图中肯定就要置0,因此当还在信号位图为1的时候,也可以称作信号未决

3.信号阻塞

进程可以选择阻塞某个信号。

被阻塞的信号产生时将保持在未决状态,直到进程接触对信号的阻塞,才执行递达的操作。

这里我们看到阻塞和忽略好像差不多,那么阻塞和忽略有什么区别呢?

阻塞是进程根本没有处理该信号,而是处于未决状态,而忽略是阻塞通过后处理的一种操作,只不过我的处理方式是忽略。

小总结

信号是未决的,却不一定被阻塞,因为进程可能还没来得及处理信号。

如果信号被阻塞,且收到了当前信号,那么信号一定是未决的。

二、Linux中的递达未决阻塞

我们说了这么多概念,是有点绕,但确实是比较重要的,进程PCB实际中是真有这三张表的,block(阻塞)、pending(未决)、handler(递达:处理方法)

如果我们想给进程2号信号阻塞,那么就将进程task_struct中指针指向的block表的bit位1(2-1)处置1就好了。因为没有0号信号,因此要-1。

那么从此以后,我们要查看进程对信号的设置以及处理方式,只需要横向观看就好。block是否阻塞,pending是否有信号,handler处理方法是什么。

  • block 0 ,pending 0,代表没有阻塞,也没信号产生或进程已经处理完信号
  • block 0 ,pending 1,代表没有阻塞,信号产生,进程还没来得及处理。
  • block 1 ,pending 0,代表阻塞,没有信号产生或进程已经处理完信号
  • block 1 ,pending 1,代表阻塞,信号产生,进程无法处理信号。同时如果当阻塞取消,进程就会收到信号再做相应处理,并将pending表置0。当然如果此时有很多该信号产生,比如你一直ctrl+c,由于阻塞进程收不到信号,当阻塞取消,只会产生一次信号。

三、信号集

从上面可知,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。 因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集。

这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。

sigset_t本质上就是位图。

四、信号集的处理

1.sig相关函数

#include<signal.h>

int sigemptyset(sigset_t *set);                                   //使其中所有信号的对应bit清零

int sigfillset(sigset_t *set);                                          //使其中所有信号的对应bit置1

int sigaddset (sigset_t *set, int signo);                       //该信号集中添加某种有效信号

int sigdelset(sigset_t *set, int signo);                         //该信号集中删除某种有效信号

int sigismember(const sigset_t *set, int signo);       //判断一个信号集的有效信号中是否包含某种信号

2.sigprocmask()函数

sigprocmaks:可以读取或更改进程的信号屏蔽字

  • 参数1:how,选项:SIG_BLOCK、SIG_UNBLOCK、SIG_SETMASK

        1.SIG_BLOCK,添加到阻塞的信号                         mask = mask|set

        2.SIG_UNBLOCK:解除阻塞的信号                       mask = mask&~set

        3.SIG_SETMASK:直接设置当前set的值               mask = set

  • 参数2:set,表示对那些信号进行处理
  • 参数3:oset,输出型参数,设置为block表之前的信号信息

函数有点多,我们直接使用起来,我们让进程2号信号阻塞,如果没阻塞,肯定会打印消息,阻塞了发送2号信号就没有反应。

现在无论我们是ctrl+c,还是发送2号信号,该进程都不会有反应,因为2号信号被阻塞了。

你可以阻塞绝大部分信号,但是进程无法阻塞9号信号。9号信号是管理员信号,操作系统发现恶意进程会直接发9号信号kill他,可不管你有没有屏蔽。

3.sigpending()函数

sigpending:读取当前进程的未决信号集,通过set参数传出。

  • 参数1:set,输出型参数,可以将进程的未决信号输出到set中。
  • 返回值:成功返回0,出错返回1

 我们直接来使用sigpending,并写了打印函数帮助我们观看pending表的变化。

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

using namespace std;

void PrintPending(const sigset_t &pending)
{
    for (int i = 31; i > 0; i--)
    {
        if (sigismember(&pending, i))
            cout << "1";
        else
            cout << "0";
    }
    cout << endl;
}

int main()
{
    cout << "pid: " << getpid() << endl;
    // 屏蔽2号信号
    sigset_t block, oblock;
    sigemptyset(&block);  // 初始化为全0
    sigemptyset(&oblock); // 初始化为全0

    sigaddset(&block, 2);                    // 将bit位置1
    sigaddset(&block, 3);                    // 将bit位置1                  // 将bit位置1
    sigprocmask(SIG_BLOCK, &block, &oblock); // 写入到block信号集中

    sigset_t pending;
    while (true)
    {
        sigpending(&pending);  // 获取pending表里的内容
        PrintPending(pending); // 打印
        sleep(1);
    }
}

效果如下,可以看到pending表的状态变化 

我们知道,当进程收到信号,执行相关操作后,pending表对应的信号bit位会被置为0,那他是将处理操作做完再置0还是处理之前就置为0了呢?

我们写如下代码验证一下

 我们发现pending表在自定义捕捉函数中已经是0了,证明在信号处理前pending表已经被置0

我们学习了这么多,都没有提到信号的保存,但实际上,什么block表,pending表,都是信号保存的实现

五、信号的处理时机

我们一直在说,信号会在合适的时候处理,那么具体什么时候是合适的时候?

其实是进程从内核态返回到用户态的时候,会进行信号的检测和信号的处理。

其中用户态是一种受控的状态,能够访问的资源是有限的,内核态是一种操作系统的工作状态,能访问大部分的系统资源。

我们所写的代码,如果涉及到了系统调用,那么就会将身份切换为内核,那么此时就可以访问内核中的数据结构或字段,当访问结束,准备返回时,并不会立刻返回,而是利用内核身份先看看进程block和pending是否有内容,发现有内容,就会做相应的处理。

就算你没有代码里写得很干净,确实没有系统调用,但是当进程切换的时候,进程也会在用户态和内核态转化,因此不用担心你的代码不会被终止。但这里也涉及到一些问题,这样可能会中断关键代码的执行,因此还需要合理的信号函数或者设置关键代码适当的同步机制来确保其原子性执行。(不懂没关系,等学过多线程就明白了)

如果检测的时候发现某个信号block没有被阻塞,同时pending表为1,就要根据handler表做相应的处理,此时身份一定要切换为用户,为了防止进程利用handler的内核身份去执行自己的恶意操作。那么执行完毕,需要返回时,如果直接返回到用户代码处,那么你怎么知道代码执行到哪一步了,该返回到那个地方,你仅仅是个函数呀!

那么你执行完sighandler函数后,仍然需要执行系统调用sigreturn回到内核(虽然我们代码中没写,但是操作系统会帮我们执行),之后就能访问到相关寄存器或者pc指针等等内容,直到当前进程执行到哪里了,回去继续执行代码。

 

六、信号处理函数

上一章信号的概念和产生,我们学过signal自定义捕捉信号。今天我们再来学一个函数sigaction

sigaction:检查并改变信号的处理动作

参数1:signum,对那个信号进行自定义捕捉

参数2:是一个结构体,重要字段我们只看sa_handler和sa_mask

        1.sa_handler:跟signal的第二个参数一样,自定义捕捉函数的地址

        2.sa_mask:设置其他信号屏蔽(在当前自定义捕捉函数中,将其他信号也屏蔽,等到捕捉函数处理完,再取消屏蔽)

参数3:输出型参数,用来保存调用之前的结构体数据

返回值:成功返回1,失败返回0。

直接上代码,我们让sa_handler进行2号信号自定义捕捉,同时给sa_mask设置3号信号,观察给2号信号后,再发2号信号与3号信号pending表的变化。

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

using namespace std;

void PrintPending(const sigset_t &pending)
{
    for (int i = 31; i > 0; i--)
    {
        if (sigismember(&pending, i))
            cout << "1";
        else
            cout << "0";
    }
    cout << endl;
}

void sighandler(int signo)
{
    cout << "收到了" << signo << "号信号" << endl;
    while(1)
    {
        sigset_t pending;
        sigpending(&pending);
        PrintPending(pending);
        sleep(1);
    }
}

int main()
{
    cout << "pid: " << getpid()  << endl;
    struct sigaction act,oact;
    act.sa_handler = sighandler;    //自定义捕捉(更改变量阶段,还没进行写入)

    sigemptyset(&act.sa_mask);      //sa_mask清空
    sigaddset(&act.sa_mask,3);      //sa_mask写入3号信号

    sigaction(2,&act,&oact);        //更改信号的处理动作

    while(1)
        sleep(1);
}

当我们ctrl+c给了2号信号,就开始执行自定义捕捉的代码了,由于自定义捕捉是死循环,因此你再发送2号信号,只会让pending表置1,同时由于我们设置了sa_mask的3号信号,于是在执行2号信号的自定义捕捉时,3号信号也无法处理,于是pending表3号信号也置1了。 

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

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

相关文章

未发TOKEN的Scroll除了撸毛还能如何获取机会?来Penpad Season 2,享受一鱼多吃!

Penpad 是 Scroll 上的 LauncPad 平台&#xff0c;该平台继承了 Scroll 底层的技术优势&#xff0c;并基于零知识证明技术&#xff0c;推出了系列功能包括账户抽象化、灵活的挖矿功能&#xff0c;并将在未来实现合规为 RWA 等资产登录 Scroll 生态构建基础。该平台被认为是绝大…

STM32时钟简介

1、复位&#xff1a;使时钟恢复原始状态 就是将寄存器状态恢复到复位值 STM32E10xxx支持三种复位形式,分别为系统复位、上电复位和备份区域复位。 复位分类&#xff1a; 1.1系统复位 除了时钟控制器的RCC_CSR寄存器中的复位标志位和备份区域中的寄存器以外,系统 复位将复位…

Python学习:lambda(匿名函数)、装饰器、数据结构

Python Lambda匿名函数 Lambda函数&#xff08;或称为匿名函数&#xff09;是Python中的一种特殊函数&#xff0c;它可以用一行代码来创建简单的函数。Lambda函数通常用于需要一个函数作为输入的函数&#xff08;比如map()&#xff0c;filter()&#xff0c;sort()等&#xff0…

fast_bev学习笔记

目录 一. 简述二. 输入输出三. github资源四. 复现推理过程4.1 cuda tensorrt 版 一. 简述 原文:Fast-BEV: A Fast and Strong Bird’s-Eye View Perception Baseline FAST BEV是一种高性能、快速推理和部署友好的解决方案&#xff0c;专为自动驾驶车载芯片设计。该框架主要包…

ssm婚纱摄影管理系统的设计+1.2w字论文+免费调试

项目演示视频&#xff1a; ssm婚纱摄影管理系统的设计 项目介绍: 随着现在网络的快速发展&#xff0c;网上管理系统也逐渐快速发展起来&#xff0c;网上管理模式很快融入到了许多商家的之中&#xff0c;随之就产生了“婚纱摄影网的设计”&#xff0c;这样就让婚纱摄影网的设计更…

IDEA跑Java后端项目提示内存溢出

要设置几个地方&#xff0c;都试一下吧&#xff1a; 1、默认是700&#xff0c;我们设置大一点&#xff08;上次配置了这儿就解决了&#xff09; 2、 3、 4、-Xmx4g

Linux基础命令篇:文本处理命令基础操作(awk、sed、sort、uniq、wc)

Linux基础命令之文件处理 1. awk awk是一种文本处理工具&#xff0c;用于处理结构化文本数据。它基于模式匹配和动作来处理输入数据。以下是一些常用的awk选项和示例&#xff1a; 1.1- 打印指定字段&#xff1a;awk { print $1, $3 } input-file&#xff08;打印输入文件中的…

【YOLOv5改进系列(6)】高效涨点----使用DAMO-YOLO中的Efficient RepGFPN模块替换yolov5中的Neck部分

文章目录 &#x1f680;&#x1f680;&#x1f680;前言一、1️⃣ 添加yolov5_GFPN.yaml文件二、2️⃣添加extra_modules.py代码三、3️⃣yolo.py文件添加内容3.1 &#x1f393; 添加CSPStage模块 四、4️⃣实验结果4.1 &#x1f393; 使用yolov5s.pt训练的结果对比4.2 ✨ 使用…

Javascript 数字精度丢失的问题(超级详细的讲解)

文章目录 一、场景复现二、浮点数二、问题分析小结 三、解决方案参考文献 一、场景复现 一个经典的面试题 0.1 0.2 0.3 // false为什么是false呢? 先看下面这个比喻 比如一个数 130.33333333… 3会一直无限循环&#xff0c;数学可以表示&#xff0c;但是计算机要存储&a…

Linux下iptables实战指南:Ubuntu 22.04安全配置全解析

Linux下iptables实战指南&#xff1a;Ubuntu 22.04安全配置全解析 引言iptables基础知识工作原理组件介绍 iptables规则管理添加规则修改规则删除规则规则持久化 常见的iptables应用场景防止DDoS攻击限制访问速率端口转发日志管理 高级配置和技巧基于时间的规则基于用户的规则结…

语音模块摄像头模块阿里云结合,实现垃圾的智能识别

语音模块&摄像头模块&阿里云结合 文章目录 语音模块&摄像头模块&阿里云结合1、实现的功能2、配置2.1 软件环境2.2 硬件配置 3、程序介绍3.1 程序概况3.2 语言模块SDK配置介绍3.3 程序文件3.3.1 开启摄像头的程序3.3.2 云端识别函数( Py > C ) & 串口程序…

【实现报告】学生信息管理系统(顺序表)

目录 实验一 线性表的基本操作 一、实验目的 二、实验内容 三、实验提示 四、实验要求 五、实验代码如下&#xff1a; &#xff08;一&#xff09;顺序表的构建及初始化 &#xff08;二&#xff09;检查顺序表是否需要扩容 &#xff08;三&#xff09;根据指定学生个…

Redis命令-List命令

4.6 Redis命令-List命令 Redis中的List类型与Java中的LinkedList类似&#xff0c;可以看做是一个双向链表结构。既可以支持正向检索和也可以支持反向检索。 特征也与LinkedList类似&#xff1a; 有序元素可以重复插入和删除快查询速度一般 常用来存储一个有序数据&#xff…

工控安全双评合规:等保测评与商用密码共铸新篇章

01.双评合规概述 2017年《中华人民共和国网络安全法》开始正式施行&#xff0c;网络安全等级测评工作也在全国范围内按照相关法律法规和技术标准要求全面落实实施。2020年1月《中华人民共和国密码法》开始正式施行&#xff0c;商用密码应用安全性评估也在有序推广和逐步推进。…

Day60-Nginx反向代理与负载均衡基于URI及USER_AGENT等跳转讲解

Day60-Nginx反向代理与负载均衡基于URI及USER_AGENT等跳转讲解 9. 基于uri实现动静分离、业务模块分离调度企业案例&#xff08;参考书籍&#xff09;10.基于user_agent及浏览器实现转发(参考书籍)11.根据文件扩展名实现代理转发12. Nginx负载均衡监测节点状态13.proxy_next_up…

FPGA高端项目:解码索尼IMX327 MIPI相机转HDMI输出,提供FPGA开发板+2套工程源码+技术支持

目录 1、前言2、相关方案推荐本博主所有FPGA工程项目-->汇总目录我这里已有的 MIPI 编解码方案 3、本 MIPI CSI-RX IP 介绍4、个人 FPGA高端图像处理开发板简介5、详细设计方案设计原理框图IMX327 及其配置MIPI CSI RX图像 ISP 处理图像缓存HDMI输出工程源码架构 6、工程源码…

C/C++ ③ —— C++11新特性

1. 类型推导 1.1 auto auto可以让编译器在编译期就推导出变量的类型 auto的使⽤必须⻢上初始化&#xff0c;否则⽆法推导出类型auto在⼀⾏定义多个变量时&#xff0c;各个变量的推导不能产⽣⼆义性&#xff0c;否则编译失败auto不能⽤作函数参数在类中auto不能⽤作⾮静态成员…

构建智能未来:探索AI人工智能产品业务架构的创新之路

随着人工智能技术的快速发展&#xff0c;AI人工智能产品在各行各业中扮演着越来越重要的角色。本文将深入探讨AI人工智能产品业务架构的创新之路&#xff0c;探讨如何构建智能未来的商业生态。 ### AI人工智能产品业务架构的重要性 AI人工智能产品的业务架构是支撑产品成功的…

Zookeeper的选主流程

Zookeeper的核心是原子广播&#xff0c;这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。Zab协议有两种模式&#xff0c;它们分别是恢复模式&#xff08;选主&#xff09;和广播模式&#xff08;同步&#xff09;。当服务启动或者在领导者崩溃后&#xff…

学习vue3第十二节(组件的使用与类型)

1、组件的作用用途 目的&#xff1a; 提高代码的复用度&#xff0c;和便于维护&#xff0c;通过封装将复杂的功能代码拆分为更小的模块&#xff0c;方便管理&#xff0c; 当我们需要实现相同的功能时&#xff0c;我们只需要复用已经封装好的组件&#xff0c;而不需要重新编写相…