【Linux】信号的处理

在这里插入图片描述

你很自由
充满了无限可能
这是很棒的事
我衷心祈祷你可以相信自己
无悔地燃烧自己的人生
-- 东野圭吾 《解忧杂货店》

信号的处理

  • 1 信号的处理
  • 2 内核态 VS 用户态
  • 3 键盘输入数据的过程
  • 4 如何理解OS如何正常的运行
  • 5 如何进行信号捕捉
  • 信号处理的总结
  • 6 可重入函数
  • volatile关键字
  • Thanks♪(・ω・)ノ谢谢阅读!!!
  • 下一篇文章见

1 信号的处理

处理信号本质就是递达这个信号!首先我们来看如何进行捕捉信号:信号的处理有三种:

signal(2 , handler);//自定义
signal(2 , SIG_IGN);//忽略
signal(2 , SIG_DFL);//默认

注意handler表是函数指针表,传入的参数一定是函数指针类型!!!

我们说过:信号可能不会被立即处理,而是在合适的时候进行处理。那么这个合适的时候到底是什么时候?!

进程从内核态(处于操作系统的状态)返回到用户态(处在用户状态)的时候进行处理!

在这里插入图片描述

  1. 首先用户运行一个进程,在执行代码指令时因为中断,异常或者系统调用进如操作系统。
  2. 进入操作操作系统就变为内核态,操作系统处理完之后,就对进程的三张表进行检查:如果pending中存在,继续判断,如果被block了了就不进行处理,反之执行对应方法!
  3. 执行对应的方法时,如果是自定义方法,会返回到用户层面的代码,执行对应的方法。然后通过系统调用再次回到内核态。
  4. 进入内核态之后,再返回到原本的用户指令位置中

注意:

  • 操作系统不能直接转过去执行用户提供的handler方法!因为操作系统权限太高了,必须回到用户权限来执行方法!
  • 类似一个∞符号:在这里插入图片描述

2 内核态 VS 用户态

再谈地址空间
在这里插入图片描述
这样无论进程如何切换,都可以找到OS!!!
所以我们访问OS,其实还是在我们的地址空间进行的,和访问库函数没有区别!OS不相信任何用户,用户访问[3 , 4]地址空间,要受到一定约束(只能通过系统调用!)

3 键盘输入数据的过程

操作系统如何知道我们按下键盘呢?肯定不能是每一时刻都进行检查,这样消耗太大!

在CPU中,键盘按下时会向cpu发送硬件中断,CPU就会读取中断号读到寄存器中,CPU会告诉OS,后续通过软件来读取寄存器。

内存中,操作系统在启动时就会维护一张函数指针数组(中断向量表),数组下标是中断号,数组内容是读磁盘函数,读网卡函数等方法。每个硬件都有自己的中断号,键盘也是。按下键盘时,向CPU发送中断信号,然后调用键盘读取方法,将键盘数据读取到内存中!这样就不需要轮询检查键盘是否输入了!

4 如何理解OS如何正常的运行

根据我们使用电脑的经验,电脑开机到关机的过程中,本质一定是一个死循环。那这死循环是如何工作的呢?那么CPU内部有一个时钟,可以不断向CPU发送中断(例如每隔10纳秒),所以CPU可以被硬件推动下在死循环内部不断执行中断方法。来看Linux内核:
在操作系统的主函数中,首先是进行一些初始化(包括系统调用方法),然后就进入到了死循环!
在这里插入图片描述

操作系统本质是一个死循环 + 时钟中断 (不断调度系统任务)

那么系统调用时什么东西呢?
在操作系统内部,操作系统提供给我们一张表:系统调用函数表
在这里插入图片描述
平时我们用户层使用的fork , getpid , dup2...等都对应到底层的sys_fork , sys_getpid ...。只有我们找到特定数组下标(系统调用号)的方法,就能执行系统调用了!

回到之前的函数指针数组,我们在这里再添加一个新方法,用来调度任何的系统调用。使用系统调用就要有:

  1. 系统调用号
  2. 系统调用函数指针表(操作系统内部)

用户层面如何使用到操作系统中的函数指针表呢?
这就要回到CPU中来谈,CPU中两个寄存器,假设叫做X 和 eax,当用户调用fork时,函数内部有类似

mov 2 eax //将系统调用号放入寄存器中

而所谓的中断不也是让CPU中的寄存器储存一个中断号来进行调用吗!那CPU内部可不可以直接写出数字呢?可以,当eax获取到数字时,寄存器X就会形成对应的数字,来执行操作系统的系统调用。

通过这种方法就可以通过用户的代码跳转到内核,来执行系统调用。但操作系统不是不相信任何用户吗?怎么就直接跳转了呢?用户是无法直接跳转到内存中的内核空间(3~4GB)。那么就有几个问题:

  1. 操作系统如何阻止用户直接访问?
  2. 系统调用最终是可以被调用的,又是如何做到的?

在操作系统中,解决这两种问题是非常复杂的!有很多概念,所以简单单来讲:做到这些需要硬件CPU配合,在CPU中存在一个寄存器code semgent记录代码段的起始与终止地址。就可以通过两个cs寄存器来分别储存用户与操作系统的代码!CS寄存器中单独设置出两个比特位来记录是OS还是用户,这样就要区分了内核态和用户态。运行代码时就会检测当前权限与代码权限是否匹配,进而做到阻止用户直接访问。而当我们调用系统调用(中断,异常)时,会改变状态,变成内核态,此时就可以调用系统调用

5 如何进行信号捕捉

今天我们来认识一个新的系统调用:

NAMEsigaction, rt_sigaction - examine and change a signal actionSYNOPSIS#include <signal.h>int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

使用方法和signal很像,先介绍struct sigaction

struct sigaction {void     (*sa_handler)(int);void     (*sa_sigaction)(int, siginfo_t *, void *);sigset_t   sa_mask;int        sa_flags;void     (*sa_restorer)(void);
};

在这其中我们只需要注意 void (*sa_handler)(int);,这是个函数指针,就是自定义捕捉的函数方法。这样看来是不是就和signal很类似了

再来看看参数

  1. int signum : 表示要对哪个信号进行捕捉
  2. const struct sigaction *act : 输入型参数,表示要执行的结构体方法
  3. struct sigaction *oldact: 输出型参数,获取更改前的数据

我们写一段代码来看看:

// 创建一个进行,进入死循环
// 对2号信号进行自定义捕捉void handler(int signum)
{std::cout << "get a sig : " << signum << " pid: " << getpid() << std::endl;
}int main()
{struct sigaction act, oact;// 自定义捕捉方法act.sa_handler = handler;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(2, &act, &oact);while (true){std::cout << "I am a process... pid: " << getpid() << std::endl;sleep(1);}return 0;
}

我们运行看看:
在这里插入图片描述
这样就成功捕捉了2号信号!用起来和之前的signal很类似!那么我们介绍这个干什么呢?我们慢慢来说:

首先信号处理有一个特性,比如我们在处理二号信号的时候,默认会对二号信号进行屏蔽!对2号信号处理完成的时候,会自动解除对2号信号的屏蔽!也就是操作系统不允许对同一个信号进行递归式的处理!!!

我们来简单验证一下:我们在handler方法中进行休眠,看看传入下一个2号信号是否会进行处理

void handler(int signum)
{std::cout << "get a sig : " << signum << " pid: " << getpid() << std::endl;sleep(100);
}

来看:
在这里插入图片描述
可见进程就屏蔽了对2号信号的处理!

我们之前学习过三张表:阻塞,未决和抵达
既然操作系统对信号进行来屏蔽,那么再次传入的信号应该就会被记录到未决表(pending表)中,我们打印这个表来看看:


void Print(sigset_t &pending)
{for (int sig = 31; sig > 0; sig--){if (sigismember(&pending, sig)){std::cout << 1;}else{std::cout << 0;}}std::cout << std::endl;
}void handler(int signum)
{std::cout << "get a sig : " << signum << " pid: " << getpid() << std::endl;while (true){// 建立位图sigset_t pending;// 获取pendingsigpending(&pending);Print(pending);}
}

来看:
在这里插入图片描述
可以看的我们在传入2号信号时就进入到了未决表中!处理信号完毕,就会解除屏蔽!

接下来我们既可以来介绍sa_mask了,上面只是对2号信息进行了屏蔽,当我传入3号新号ctrl + \时就正常退出了,那么怎么可以在处理2号信号时屏蔽其他信号呢?就是通过sa_mask,将想要屏蔽的信号设置到sa_mask中,就会在处理2号信号的时候,屏蔽所设置的信号!

int main()
{struct sigaction act, oact;// 自定义捕捉方法act.sa_handler = handler;sigemptyset(&act.sa_mask);//向sa_mask中添加3号信号sigaddset(&act.sa_mask , 3);act.sa_flags = 0;sigaction(2, &act, &oact);while (true){std::cout << "I am a process... pid: " << getpid() << std::endl;sleep(1);}return 0;
}

这样就也屏蔽了3号信号
在这里插入图片描述
当然如果把所有信号都屏蔽了,肯定是不行的,所以有一部分信号不能被屏蔽,比如9号信号永远都不能屏蔽!!!

信号处理的总结

对于信号我们学习了三个阶段:

  1. 信号的产生与发送:中断,异常,系统调用。
  2. 信号的保存:三张表:阻塞,未决和递达
  3. 信号的处理

6 可重入函数

介绍一个新概念:可重入函数。
我们先来看一个情景:
在这里插入图片描述
这是一个链表,我们的inser函数会进行一个头插,头插会有两行代码:

void insert(node_t* p)
{p->next = head;//------在这里接收到信号-----head = p;
}

我们进行头插时,进行完第一步之后,突然来了一个信号,但是我们之前说过:信号处理时在用户态到内核态进行切换时才进行处理,这链表的头插没有进行状态的切换啊?其实状态的切换不一定只能是系统调用方法,在时间片到了(时钟中断)之后,也进行了状态的切换。

而且恰好,该信号的自定义捕捉方法也是insert这时就导致node2插入到了链表中,信号处理完之后,头指针又被掰到node1了,就造成node2丢失了(内存泄漏了)!!!

这就叫做insert函数被重入了!!!

在重入过程中一旦造成了问题,就叫做不可重入函数!!!(因为一旦重入就造成了问题,那当然不能重入了)
绝大部分函数都是不可重入函数!

volatile关键字

我们今天在信号的角度再来重温一下:
volatile 作用:保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量的任何操作,都必须在真实的内存中进行操作保持数据可见性!

看这样一段代码:

#include <iostream>
#include <signal.h>int flag = 0;
void changdata(int signo)
{std::cout << "get a sig : " << signo << " change flag 0->1"  << std::endl;flag = 1; 
}int main()
{signal(2 , changdata);while(!flag);std::cout << "process quit normal" << std::endl;
}

主函数会一直进行死循环,只有接收到了2号信号才会退出!
在这里插入图片描述
但当我们进行编译优化时(因为如果进程不接受到2号信号,那么flag就没有人来修改,编译器就认为没有任何代码对flag进行修改),共同有四级优化00 01 02 03

而while(!flag)是一个逻辑运算,CPU 一般进行两种类别计算:算术运算和逻辑运算!会从内存进行读取,然后进行运算

g++ main main.cc -01

我们再次运行,却发现,进程不会结束了?!这是为什么!因为优化直接将数据优化到寄存中,因为编译器认为后续不会进行修改,所以寄存器中的值不会改变,程序只会读到寄存器中的值。所以就有了volatile关键字解决了这样的问题!!!

Thanks♪(・ω・)ノ谢谢阅读!!!

下一篇文章见

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1958.力扣每日一题7/7 Java(100%解)

博客主页&#xff1a;音符犹如代码系列专栏&#xff1a;算法练习关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 目录 思路 解题方法 时间复杂度 空间复杂度 Code 思路 首先将指定位…

用vue2+elementUI封装手机端选择器picker组件,支持单选、多选、远程搜索多选

单选注意点&#xff1a; touchmove.prevent: 在 touchmove 事件上添加 .prevent 修饰符&#xff0c;以阻止默认的滚动行为。 handleTouchStart: 记录触摸开始的 Y 坐标和当前的 translateY 值。 handleTouchMove: 计算触摸移动的距离&#xff0c;并更新 translateY 值。 han…

Android 开发中 C++ 和Java 日志调试

在 C 中添加堆栈日志 先在 Android.bp 中 添加 ‘libutilscallstack’ shared_libs:["liblog"," libutilscallstack"]在想要打印堆栈的代码中添加 #include <utils/CallStack.h> using android::CallStack;// 在函数中添加 int VisualizerLib_Crea…

基于支持向量机、孤立森林和LSTM自编码器的机械状态异常检测(MATLAB R2021B)

异常检测通常是根据已有的观测数据建立正常行为模型&#xff0c;从而将不同机制下产生的远离正常行为的数据划分为异常类&#xff0c;进而实现对异常状态的检测。常用的异常检测方法主要有&#xff1a;统计方法、信息度量方法、谱映射方法、聚类方法、近邻方法和分类方法等。 …

26_嵌入式系统网络接口

以太网接口基本原理 IEEE802标准 局域网标准协议工作在物理层和数据链路层&#xff0c;其将数据链路层又划分为两层&#xff0c;从下到上分别为介质访问控制子层(不同的MAC子层&#xff0c;与具体接入的传输介质相关),逻辑链路控制子层(统一的LLC子层&#xff0c;为上层提供统…

MySQL之备份与恢复(八)

备份与恢复 还原逻辑备份 如果还原的是逻辑备份而不是物理备份&#xff0c;则与使用操作系统简单地复制文件到适当位置的方式不同&#xff0c;需要使用MySQL服务器本身来加载数据到表中。在加载导出文件之前&#xff0c;应该先花一点时间考虑文件有多大&#xff0c;需要多久加…

240707_昇思学习打卡-Day19-基于MindSpore通过GPT实现情感分类

240707_昇思学习打卡-Day19-基于MindSpore通过GPT实现情感分类 今天基于GPT实现一个情感分类的功能&#xff0c;假设已经安装好了MindSpore环境。 # 该案例在 mindnlp 0.3.1 版本完成适配&#xff0c;如果发现案例跑不通&#xff0c;可以指定mindnlp版本&#xff0c;执行!pip…

14-20 Vision Transformer用AI的画笔描绘新世界

概述 毫无疑问,目前最受关注且不断发展的最重要的主题之一是使用人工智能生成图像、视频和文本。大型语言模型 (LLM) 已展示出其在文本生成方面的卓越能力。它们在文本生成方面的许多问题已得到解决。然而,LLM 面临的一个主要挑战是它们有时会产生幻觉反应。 最近推出的新模…

【Unity小技巧】Unity字典序列化

字典序列化 在 Unity 中&#xff0c;标准的 C# 字典&#xff08;Dictionary<TKey, TValue>&#xff09;是不能直接序列化的&#xff0c;因为 Unity 的序列化系统不支持非 Unity 序列化的集合类型。可以通过手写字典实现 效果&#xff1a; 实现步骤&#xff1a; 继承ISe…

【TB作品】51单片机 Proteus仿真 MAX7219点阵驱动数码管驱动

1、8乘8点阵模块&#xff08;爱心&#xff09; 数码管测试程序与仿真 实验报告: MAX7219 数码管驱动测试 一、实验目的 通过对 MAX7219 芯片的编程与控制&#xff0c;了解如何使用单片机驱动数码管显示数字&#xff0c;并掌握 SPI 通信协议的基本应用。 二、实验器材 51…

AI中药处方模型构建与案例

在中医领域,人工智能(AI)可以生成各种指令来辅助诊断、治疗和研究。 1. 诊断辅助指令: 根据患者的症状和体征,自动分析并生成可能的中医证候诊断建议。利用中医望闻问切四诊信息,智能识别关键症状,提供对应的中医辨证思路。2. 治疗建议指令: 根据辨证结果,自动推荐相应…

JVM专题之垃圾收集算法

标记清除算法 第一步:标记 (找出内存中需要回收的对象,并且把它们标记出来) 第二步:清除 (清除掉被标记需要回收的对象,释放出对应的内存空间) 缺点: 标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需 要分配较大对象时,无法找到…

Python代码设置Excel工作表背景色或背景图

Excel是工作中数据处理和分析数据的重要工具。面对海量的数据和复杂的表格&#xff0c;如何提高工作效率、减少视觉疲劳并提升数据的可读性是不容忽视的问题。而给工作表设置合适的背景是表格优化的一个有效方式。为Excel工作表设置背景色或背景图不仅能够美化工作表&#xff0…

chrome 谷歌浏览器插件打包

1、找到id对应的字符串去搜索 C:\Users\<你的用户名>\AppData\Local\Google\Chrome\User Data\Default\Extensions2、选择根目录 直接加载下面的路径扩展可用&#xff1a;

AI绘画Stable Diffusion【图生图教程】:图片高清修复的三种方案详解,你一定能用上!(附资料)

大家好&#xff0c;我是画画的小强 今天给大家分享一下用AI绘画Stable Diffusion 进行 高清修复&#xff08;Hi-Res Fix&#xff09;&#xff0c;这是用于提升图像分辨率和细节的技术。在生成图像时&#xff0c;初始的低分辨率图像会通过放大算法和细节增强技术被转换为高分辨…

qt 如何添加子项目

首先我们正常流程创建一个项目文件&#xff1a; 这是我已经创建好的&#xff0c;请无视红线 然后找到该项目的文件夹&#xff0c;在文件夹下创建一个文件夹&#xff0c;再到创建好的文件夹下面创建一个 .pri 文件&#xff1a; &#xff08;创建文件夹&#xff09; &#xff08…

中国石油大学(华东)24计算机考研数据速览,计科学硕复试线288分!

中国石油大学&#xff08;华东&#xff09;计算机与通信工程学院是中国石油大学(华东)十三个教学院部之一&#xff0c;其前身是创建于1984年的计算机科学系&#xff0c;2001年撤系建院。伴随着学校50多年的风雨历程&#xff0c;计算机与通信工程学院也已经有了20多年的发展历史…

Python【打包exe文件两步到位】

Python打包Exe 安装 pyinstaller&#xff08;pip install pyinstaller&#xff09; 执行打包命令&#xff08;pyinstaller demo.py&#xff09; 打完包会生成 dist 文件夹&#xff0c;如下如

04.ffmpeg打印音视频媒体信息

目录 1、相关头文件 2、相关结构体 3、相关函数 4、函数详解 5、源码附上 1、相关头文件 #include <libavformat/avformat.h> 包含格式相关的函数和数据结构 #include <libavutil/avutil.h> 包含一些通用实用函数 2、相关结构体 AV…

【代码管理的必备工具:Git的基本概念与操作详解】

一、Git 初识 1.提出问题 不知道你工作或学习时&#xff0c;有没有遇到这样的情况&#xff1a;我们在编写各种⽂档时&#xff0c;为了防止⽂档丢失&#xff0c;更改失误&#xff0c;失误后能恢复到原来的版本&#xff0c;不得不复制出⼀个副本&#xff0c;比如&#xff1a; “…