一文搞懂Linux信号【下】

news2025/2/8 16:54:45

目录

🚩引言

🚩阻塞信号

🚩信号保存

🚩信号捕捉 

🚩操作信号集

1.信号集操作函数

2.其它操作函数

🚩总结:


🚩引言

在观看本博客之前,建议大家先看一文搞懂Linux信号【上】。由于上一篇博客篇幅太长,为了更好的阅读体验,我拆成了两篇博客。那么接下来,在上一篇的基础上,我们继续学习Linux信号部分。本篇我们主要谈论信号保存和信号处理。

🚩阻塞信号

🌸信号的其他几个相关的概念

首先,先向大家抛出信号中的几个概念

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

 张三在上小学时,非常讨厌数学老师,但是数学老师又很凶。有一次上课时,老师说:“拿起本子记一下作业”。尽管很不喜欢这个老师,但又很害怕这几老师,张三无奈的记下了作业,想着:我现在先不写,假如老师真的发现我没写作业的话,我再写。而相比于懦弱的张三,头铁的李四则选择压根不写,忽略这次信号。

在这里,信号就像是作业。张三选择先记下作业,这就像是阻塞信号,等到什么时候被发现了,才写,写作业的过程,就是信号递达的过程。而李四的行为就是兑现好做出了处理,这个处理就是忽略。

阻塞和忽略是两个不同的概念:

阻塞信号是指在信号还未到来之前,先对某个信号阻塞,等到阻塞解除,才对信号做出处理动作。

忽略:忽略本身就信号的处理动作,只不过这个处理动作是忽略。

🚩信号保存

🚀pending位图

我们再一文搞懂Linux信号【上】中说过:信号在内核中是以unsigned int类型的位图来保存的,从低位到高位,比特位的位置代表信号的编号,比特位的内容代表是否收到对应的信号,0代表没有收到,1代表收到了对应的信号。这个位图就叫做pending位图

所以:发送信号的本质就是修改pending位图。与其说发送信号,不如说是写信号。由于pending位图在task_struct结构体中,属于内核数据结构,所以修改位图的结构只能是操作系统。

🚀block位图

在操作系统,还有一个位图结构,叫作lock位图。

在block位图中,比特位的位置代表对应的信号的编号。对应的比特位为0,代表该信号没有被阻塞,可以递达;对应的比特位为1,代表该信号被阻塞,无法递达,除非解除阻塞。

所以,一个信号要想递达,①要将pending位图中对应的比特位置为1,②要将block位图中对应的比特位置为0。

🚀hander数组

在进程的task_struct结构体中,存在着一个存放sighander_t*类型的指针数组。在这个数组中,数组的位置代表信号的编号, 数组下标的内容,代表对应信号的处理方法(自定义行为)。当上层调用signal设置自定义行为时,操作系统会将自定义函数的地址传入该数组中,然后对信号进行捕捉时,通过数组中的地址找到对应的处理方法,完成捕捉。

如图:

 针对如上的三个结构,需要说明的有:

  1. 一个信号没有被发送,并不影响这个信号被阻塞。
  2. 我们刚开始学习信号时,知道操作系统认识对应的信号是通过程序员的编码完成的。现在我们知道每一个信号的相关信息都会被设置进3个结果中,等到信号来临时,就可以做出处理动作。
  3. 由于信号是用位图来保存的,所以,当操作系统连续多次向某个进程发送大量同种信号时,pending位图也只能记录一次。其他信号也就会丢失。

🚩信号捕捉 

 从刚一开始接触信号时,我们就说:信号在产生的时候,不会被立即处理,而是要等到合适的时候再进行处理。什么是合适的时候呢?在进程从内核态返回用户态的时候,也就代表着曾经我一定进入过内核态。为了方便讲解,我们先补充一些预备知识。

 🚀用户态和内核态

如图所示

代码的运行状态分为两种:用户态和内核态,用户态是最基本的运行状态,自己所写的代码全部都是用户态的代码,内核态则比较高级。

当代码中出现①使用操作系统的自身资源(getpid,waitpid.......)②涉及访问硬件资源(printf,scanf.......)时。用户为了访问这些资源,必须直接或者间接的使用操作系统提供的系统调用接口。但是普通用户无法直接调用系统调用接口,必须让自己的身份从用户态变为内核态。实际执行系统调用的进程,但是身份其实是内核。这里,还要说明一点:因为从用户态访问内核资源还要发生身份的变化,成本较高,所以往往系统调用比较浪费时间,所以尽量不要频繁的调用系统调用接口。

🚩cpu和寄存器

对于cpu大家都不陌生,负责数据的运算。在cpu中有大量的寄存器,这些寄存器分为可见寄存器和不可见寄存器。其中很多寄存器都和进程是强相关的,保存着进程的上下文数据。寄存器属于操作系统,但寄存器内的数据属于进程。当一个进程在cpu上运行时,有关该进程的数据都被投递到寄存器中。典型的比如:①当前进程的task_struct地址②页表的起始地址(方便虚拟内存和物理内存之间的转化)都被投递到不同的寄存器中。

其中,有一个名为CR3的寄存器,这个寄存器表征当前进程是处于用户态还是内核态。寄存器内的数字为0表示处于内核态,数字为3表示处于用户态。

🚀深挖虚拟内存空间

我们之前在将虚拟内存时,知道虚拟内存一共有4G的空间,其中3G的空间是用户空间,该块空间通过页表和物理内存映射,进而读取用户代码和数据。但是还存在1G的内核空间呢?这是什么鬼?干什么用的?

这块空间同样通过页表和物理内存形成映射,只不过想映射的物理内存中存储的不再是用户的代码和数据,而是操作系统和系统调用的相关代码数据和方法。

用户空间和内核空间的页表等等有什么不同呢?

  • 用户空间属于该进程的空间,具有私密性,同时每个进程都有相对应的用户空间页表结构,且不同进程的用户级页表不同。
  • 在操作系统启动时,操作系统的相关的代码和数据加载到对应的物理内存,由于操作系统只有一个,所以所有的进程共享一个内核级页表,不具有私密性。

 

所以,如果进程想要访问操作系统的资源,该如何做?

  1. 将CPU中的CR3寄存器储存的值由3变为0
  2. 在进程地址空间中,在空间的上下文之间进行跳转。由用户级空间跳转到内核级空间,通过内核级页表映射,找到系统调用的执行方法。

所以,我们知道从用户态和内核态之间的跳转是非常浪费资源的。当代码执行到需要访问操作系统资源的时候,尽管浪费资源和时间,但是进程还要从用户态变为内核态,然后执行相关的系统调用接口。但是,站在进程的角度,它认为跳转一次太慢了,必须把所有只能在内核态中才能进行的操作完成。进程从用户态切换成内核态常见的原因有:系统调用,进程切换。

因为处理信号也需要在内核态中进行。所以进程就开始检查信号对应的block位图和pending位图。

 检查顺序为先查block位图,然后再查pending位图。我展开说一下:

  1. 首先,查block位图。如果比特位为1,表示被阻塞,然后接着下一位比特位;如果比特位为0,再看pending中该信号对应的比特位,如果为0,接着查block位图的下一位比特位;如果比特位为1,说明该信号目前处于未决状态,应立即处理。然后查对应的处理方法hander表。

但是如果这个信号对应的处理方法是自定义行为呢?自定义函数属于自己编写的代码,在用户态中,操作系统允许进程在内核态中运行用户态的代码吗?

不行。理论上可以,但是操作系统为了安全,不敢这么干。因为它并不知道这个方法要干什么,万一要是恶意者恶搞系统咋办,所以,操作系统能力让进程在内核态中执行用户态的代码,但是不敢这么做。如果进程处于用户态然后执行这个方法,操作系统就没必要担心了,出了事也是这个进程被终止,和操作系统没关系。,

所以,为了执行信号的自定义方法,进程必须从内核态中返回用户态

当执行完方法后,如果有需要,进程还要返回内核态中,继续运行程序。

总结一下:

我们看到,其实整个过程看起来就像是个躺着的8。我把整个过程分为4个小过程,逐一说明

①代码在执行过程中遇到了系统调用或者时间片已到要进行程序替换。进程从用户态变为内核态来执行该过程。

②执行完毕。由于进程状态切换太浪费资源,进程就像一次性把要在内核态中干的所有事情全部搞完,再返回内核态。所以就检测是否收到了信号,如果收到了信号,并且处理方法是自定义方法,在用户态对应的物理内存。

③进程为了执行信号的处理方法,返回用户态执行。执行完毕后,返回内核态继续干其他工作。

④当进程把所有只能在内核态中运行的操作,全部完成后,返回用户态执行。 

🚩操作信号集

 我们的信号位图又称信号集,分为pending信号集和block信号集。block信号集又称信号屏蔽字。

1.信号集操作函数

#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置1,表示该信号集的有效信号包括系统支持的所有信号。
  • 在使用sigset_ t类型的变量之前,一定要调用sigemptyset 或sigfillset 做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。
  • 这四个函数都是成功返回0,出错返回-1。
  • sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1。

2.其它操作函数

调用函数sigprocmask可以读取或更改进程的信号屏蔽字(block)。

#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参数更改信号屏蔽字。

SIG_BLOCK:将set指向信号集中的信号,添加到进程阻塞信号集;
SIG_UNBLOCK:将set指向信号集中的信号,从进程阻塞信号集删除;
SIG_SETMASK:将set指向信号集中的信号,设置成进程阻塞信号集;

调用函数sigpending可以读取当前进程的未决信号集,

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

现在我们用上述函数来测试一下信号递达的过程:首先是对SIGINT信号进行阻塞,然后通过ctrl+c 发送SIGINT 信号,发现SIGINT信号在pending位图中别标记为1,但是信号未决,直到解除对SIGINT信号的屏蔽,SIGINT信号递达,后续再发送SIGINT信号,会被直接递达,因为ISGINT并没有被阻塞。

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<cassert>
#include<vector>
#define NUM 32
using namespace std;
vector<int>sigarr={2};
// 打印信号集
static void show_pending(const sigset_t &s)
{
    for(int signo=32;signo>=1;signo--)
    {
        if(sigismember(&s,signo))
        {
            cout<<"1";
        }
        else
        {
            cout<<"0";
        }
    }
    cout<<" "<<endl;

}
// 自定义信号处理方法
void hander(int signo)
{
    cout<<"收到一个信号:"<<signo<<endl;

}
int main()
{   
    //自定义行为
    for(auto signo:sigarr)
    {
        signal(signo,hander);
    }
    // 初始化信号集
    sigset_t block,oblock,pending;
    sigemptyset(&block);
    sigemptyset(&oblock);
    sigemptyset(&pending);
    
    for(auto signo:sigarr)
    {
        sigaddset(&block,signo);
    }
    // 写入信号屏蔽字中
    sigprocmask(SIG_SETMASK,&block,&oblock);

    int cnt=5;
    while(1)
    {
        // 读取pending信号集
        sigpending(&pending);
        show_pending(pending);

        sleep(1);
        
        if(cnt--==0)
        {
            cout<<"信号屏蔽字已更改"<<endl;

            sigprocmask(SIG_SETMASK,&oblock,&block);
        }
        cout<<"----------------------------------------------"<<endl;

    }
    
    
}

代码运行如下: 

🚩总结:

  • 我们可以选择性的对信号做出阻塞。要分清阻塞和忽略的区别。
  • 在task_struct中,有pending位图负责保存收到信号,block位图负责保存阻塞的信号,还有一个指针数组指向信号的处理方法。
  • 信号在进程由内核态返回用户态时进行处理,要牢记信号捕捉的过程。
  • 要熟悉操作信号位图的函数。

 本文到这里,就结束了,谢谢大家的观看。我们下一篇博客再见。

 

 

 

 

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

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

相关文章

JR-8000系列机架式多路4K超高清光端机

集中式 4K超高清光传输设备 1 产品特性 ⚫ 支持高达 8 通道 SMPTE 全格式 SDI 信号输入 ⚫ 发送端带有 LOOPOUT 环出端口&#xff0c;具备消抖动功能&#xff0c;可作为信号调理或级联信号源使用 ⚫ 接收端支持双输出端口 ⚫ 支持传输速率&#xff1a;143Mbps-11.88Gbps ⚫…

INVS利用gatearray实现post-mask的function ECO

随着现代IC的设计发展&#xff0c;设计的规模和复杂度逐步增加&#xff0c;对于验证完备性的挑战越来越大&#xff0c;加之TO的时间压力&#xff0c;芯片设计通常会出现下列的场景&#xff1a; 芯片回片一次点亮大部分的case都可以顺利通过小部分的功能需要修正 对于重要的特…

赵丽颖纯白茉莉绽放温柔之美

赵丽颖纯白茉莉&#xff0c;绽放温柔之美在这个繁忙喧嚣的娱乐圈&#xff0c;赵丽颖以其独特的魅力&#xff0c;成为了无数人心中的白月光。近日&#xff0c;赵丽颖工作室发布了一组live图&#xff0c;她身着一袭温柔白裙&#xff0c;宛如一朵盛开的纯白茉莉花&#xff0c;美得…

企业级WordPress开发 – 创建企业级网站的优秀技巧

目录 1 “企业级”一词是什么意思&#xff1f; 2 使用 WordPress 进行企业级 Web 开发有哪些好处&#xff1f; 3 使用 WordPress 进行企业级开发的主要好处 3.1 WordPress 可扩展、灵活且价格实惠 3.2 WordPress 提供响应式 Web 开发 3.3 WordPress 提供巨大的可扩展…

github-chinese,跟英文GitHub说拜拜

背景 对于我们程序员来说,Github是一个常逛的web网站,里面学习资源众多,不管是查问题还是查资料都离不开他。 但是Github作为一个国际化的网站,语言主要是英语,所以对于一些英语似懂非懂的同学来说还是有一些难处。 想过找一个国内中文的Github作为一个平替网站,但是资…

Linux基础指令(三)

目录 shell 权限指令&#xff1a; 文件的操作权限&#xff1a; 对文件进行操作的用户分类&#xff1a; 用户对文件进行的操作分类&#xff1a; 所有者、所属组、其他的访问权限&#xff1a; 创建用户 沾滞位 匹配查找指令&#xff1a; grep find shell shell&#x…

联想Y7000P 2023款拆机教程及升级内存教程

0.电脑参数介绍 联想Y7000P 2023电脑&#xff0c;笔者电脑CPU为i7-13700H&#xff0c;14核20线程&#xff1b;标配内存为三星的DDR5-5600MHz-8GB*2&#xff0c;由于电脑CPU限制&#xff0c;实际内存跑的频率为5200MHz; 2个内存插槽&#xff0c;2个固态硬盘插槽。每个内存插槽最…

Linux检查端口nmap

yum install -y nmap # 查看本机在运行的服务的端口号 nmap 127.0.0.1 补充&#xff1a;netstat netstat -tunlp | grep 3306

堆排序的实现原理

一、什么是堆排序&#xff1f; 堆排序就是将待排序元素以一种特定树的结构组合在一起&#xff0c;这种结构被称为堆。 堆又分为大根堆和小根堆&#xff0c;所谓大根堆即为所有的父节点均大于子节点&#xff0c;但兄弟节点之间却没有什么严格的限制&#xff0c;小根堆恰恰相反&a…

百度ai人脸识别项目C#

一、项目描述 本项目通过集成百度AI人脸识别API&#xff0c;实现了人脸检测和识别功能。用户可以上传图片&#xff0c;系统将自动识别人脸并返回识别结果。 二、开发环境 Visual Studio 2019或更高版本.NET Framework 4.7.2或更高版本AForge.NET库百度AI平台人脸识别API 三、…

09--keepalived高可用集群

前言&#xff1a;高可用集群配置是大型网站的一个基础&#xff0c;网站可用性的基础保障之一&#xff0c;这里将对应的概念知识和实操步骤进行整理与收集。 1、基础概念详解 1.1、高可用集群 高可用集群&#xff08;High Availability Cluster&#xff0c;简称HA Cluster&am…

已解决java.security.acl.AclNotFoundException异常的正确解决方法,亲测有效!!!

已解决java.security.acl.AclNotFoundException异常的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 目录 问题分析 出现问题的场景 报错原因 解决思路 解决方法 分析错误日志 检查ACL文件路径和名称 确认系统权限 修改代码逻辑 确保ACL文…

什么是微分和导数?

文章目录 设立问题微分特性指数特性线性特性常数特性 多项式微分导数 在机器学习领域&#xff0c;有多种解决最优化问题的方法&#xff0c;其中之一就是使用微分。 通过微分&#xff0c;可以得知函数在某个点的斜率&#xff0c;也可以了解函数在瞬间的变化。 设立问题 请想象一…

C++/Qt 小知识记录7

工作中遇到的一些小问题&#xff0c;总结的小知识记录&#xff1a;C/Qt 小知识7 编译FFMPEG遇到的问题CMakeLists.txt配置FFMPEG的依赖方式&#xff1a; x264在Windows下编译生成*.libVS编译Qt工程时&#xff0c;遇到提示Change Qt Version的情况在QtOsg的窗口上嵌入子窗口&…

Map集合之HashMap细说

最近在看面试题&#xff0c;看到了hashmap相关的知识&#xff0c;面试中问的也挺多的&#xff0c;然后我这里记录下来&#xff0c;供大家学习。 Hashmap为什么线程不安全 jdk 1.7中&#xff0c;在扩容的时候因为使用头插法导致链表需要倒转&#xff0c;从而可能出现循环链表问…

图像分割(四)---(图像显示、灰度直方图和三维灰度图综合分析选取最佳分割方法)

一、引言 对彩色图像进行分割的一种常用方法&#xff0c;是先把彩色图像转灰度图像&#xff0c;然后再选择合适的阈值进行二值分割。但有时彩色图像转灰度图像后不具有典型的双峰特性&#xff0c;二值分割效果不好。本文章提出一种确定彩色图像分割方法的新思路。首先读入一幅彩…

2024山东大学软件学院创新项目实训(9)使用OpenCompass进行模型评估

下载好OpenCompassData-core-20231110.zip 之后&#xff0c;解压压缩包 unzip OpenCompassData-core-20231110.zip 运行代码&#xff1a; python run.py --datasets ceval_gen --hf-path /hy-tmp/7B21/merged --tokenizer-path /hy-tmp/7B21/merged --tokenizer-kwargs p…

【数据结构】线性表之《栈》超详细实现

栈 一.栈的概念及结构二.顺序栈与链栈1.顺序栈2.链栈1.单链表栈2.双链表栈 三.顺序栈的实现1.栈的初始化2.检查栈的容量3.入栈4.出栈5.获取栈顶元素6.栈的大小7.栈的判空8.栈的清空9.栈的销毁 四.模块化源代码1.Stack.h2.Stack.c3.test.c 一.栈的概念及结构 栈&#xff1a;一种…

WDG开门狗

WDG开门狗简介 独立看门狗&#xff0c;它的特点就是独立运行&#xff0c;对时间精度要求较低。独立运行就是独立看门狗的时钟是专用的&#xff0c;LSI内部低速时钟&#xff0c;即使主时钟出现问题了&#xff0c;看门狗也能正常工作&#xff0c;这也是独立看门狗独立的得名原因&…

【34W字CISSP备考笔记】域1:安全与风险管理

1.1 理解、坚持和弘扬职业道德 1.1.1.(ISC)职业道德规范 1、行为得体、诚实、公正、负责、守法。 2、为委托人提供尽职、合格的服务。 3、促进和保护职业。 4、保护社会、公益、必需的公信和自信&#xff0c;保护基础设施。 1.1.2.组织道德规范 1、RFC 1087 &#xff0…