当前位置: 首页 > news >正文

【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♪(・ω・)ノ谢谢阅读!!!

下一篇文章见

相关文章:

【Linux】信号的处理

你很自由 充满了无限可能 这是很棒的事 我衷心祈祷你可以相信自己 无悔地燃烧自己的人生 -- 东野圭吾 《解忧杂货店》 信号的处理 1 信号的处理2 内核态 VS 用户态3 键盘输入数据的过程4 如何理解OS如何正常的运行5 如何进行信号捕捉信号处理的总结6 可重入函数volatile关…...

Python数据分析的数据导入和导出

在Python数据分析中&#xff0c;数据的导入和导出是非常关键的步骤。这些步骤通常涉及到将数据从外部文件&#xff08;如CSV、Excel、数据库等&#xff09;读入到Python程序中&#xff0c;以及将处理后的数据导出回外部文件或数据库。以下是一些常用的库和方法来实现这些操作。…...

【JAVA多线程】线程池概论

目录 1.概述 2.ThreadPoolExector 2.1.参数 2.2.新任务提交流程 2.3.拒绝策略 2.4.代码示例 1.概述 线程池的核心&#xff1a; 线程池的实现原理是个标准的生产消费者模型&#xff0c;调用方不停向线程池中写数据&#xff0c;线程池中的线程组不停从队列中取任务。 实现…...

java双亲委派机制

Java中的双亲委派机制&#xff08;Parent Delegation Model&#xff09;是一种类加载机制&#xff0c;它确保了类加载的安全性和一致性。该机制规定了类加载器在加载类时的顺序和方式&#xff0c;从而避免了重复加载和类冲突问题。 以下是一个简单的自定义类加载器的示例&#…...

记录第一次使用air热更新golang项目

下载 go install github.com/cosmtrek/airlatest 下载时提示&#xff1a; module declares its path as: github.com/air-verse/air but was required as: github.com/cosmtrek/air 此时&#xff0c;需要在go.mod中加上这么一句&#xff1a; replace github.com/cosmtrek/air &…...

Leetcode 3213. Construct String with Minimum Cost

Leetcode 3213. Construct String with Minimum Cost 1. 解题思路2. 代码实现 题目链接&#xff1a;3213. Construct String with Minimum Cost 1. 解题思路 这一题的话思路上还是比较直接的&#xff0c;就是一个trie树加一个动态规划&#xff0c;通过trie树来快速寻找每一个…...

python操作SQLite3数据库进行增删改查

python操作SQLite3数据库进行增删改查 1、创建SQLite3数据库 可以通过Navicat图形化软件来创建: 2、创建表 利用Navicat图形化软件来创建: 存储在 SQLite 数据库中的每个值(或是由数据库引擎所操作的值)都有一个以下的存储类型: NULL. 值是空值。 INTEGER. 值是有符…...

【电控笔记6.7】非最小相位系统

全通滤波器 [...

Day05-04-持续集成总结

Day05-04-持续集成总结 1. 持续集成2. 代码上线目标项目 1. 持续集成 git 基本使用, 拉取代码,上传代码,分支操作,tag标签 gitlab 用户 用户组 项目 , 备份,https,优化. jenkins 工具平台,运维核心, 自由风格工程,maven风格项目,流水线项目, 流水线(pipeline) mavenpom.xmlta…...

PyQt5动态热力图清空画布关闭ColorBar

PyQt5生成正弦波动态热力图清空画布关闭ColorBar 1、简介 生成随机正弦波,使用pyqtgraph展示出来,并且使用热力图展示不同频率的正弦波,使用不同的画布颜色显示热力图的变化。 使用python3.8 导入库: pip install matplotlib==3.7.5 pip install numpy==1.24.4 pip in…...

python爬虫入门(一)之HTTP请求和响应

一、爬虫的三个步骤&#xff08;要学习的内容&#xff09; 1、获取网页内容 &#xff08;HTTP请求、Requests库&#xff09; 2、解析网页内容 &#xff08;HTML网页结构、Beautiful Soup库&#xff09; 3、存储或分析数据 b站学习链接&#xff1a; 【【Python爬虫】爆肝两…...

华为OD机考题(HJ41 称砝码)

前言 经过前期的数据结构和算法学习&#xff0c;开始以OD机考题作为练习题&#xff0c;继续加强下熟练程度。有需要的可以同步练习下。 描述 现有n种砝码&#xff0c;重量互不相等&#xff0c;分别为 m1,m2,m3…mn &#xff1b; 每种砝码对应的数量为 x1,x2,x3...xn 。现在要…...

Qt涂鸦板

Qt版本&#xff1a;Qt6 具体代码&#xff1a; 头文件 dialog.h #ifndef DIALOG_H #define DIALOG_H#include <QDialog>QT_BEGIN_NAMESPACE namespace Ui { class Dialog; } QT_END_NAMESPACEclass Dialog : public QDialog {Q_OBJECTpublic:Dialog(QWidget *parent n…...

C++_03

1、构造函数 1.1 什么是构造函数 类的构造函数是类的一种特殊的成员函数&#xff0c;它会在每次创建类的新对象时执行。 每次构造的是构造成员变量的初始化值&#xff0c;内存空间等。 构造函数的名称与类的名称是完全相同的&#xff0c;并且不会返回任何类型&#xff0c;也不…...

强化学习中的Double DQN、Dueling DQN和PER DQN算法详解及实战

1. 深度Q网络&#xff08;DQN&#xff09;回顾 DQN通过神经网络近似状态-动作值函数&#xff08;Q函数&#xff09;&#xff0c;在训练过程中使用经验回放&#xff08;Experience Replay&#xff09;和固定目标网络&#xff08;Fixed Target Network&#xff09;来稳定训练过程…...

前端八股文 说一说样式优先级的规则是什么?

标准的回答 CSS样式的优先级应该分成四大类 第一类 !important&#xff1a; &#x1f604;无论引入方式是什么&#xff0c;选择器是什么&#xff0c;它的优先级都是最高的。 第二类 引入方式&#xff1a; &#x1f604;行内样式的优先级要高于嵌入和外链&#xff0c;嵌入和外链…...

洞察国内 AI 绘画行业的璀璨前景

在科技的浪潮中&#xff0c;AI 绘画如同一颗璀璨的新星&#xff0c;正在国内的艺术与技术领域绽放出耀眼的光芒。 近年来&#xff0c;国内 AI 绘画行业发展迅猛&#xff0c;展现出巨大的潜力。随着人工智能技术的不断突破&#xff0c;AI 绘画算法日益精进&#xff0c;能够生成…...

socket编程

文章目录 套接字网路字节序列TCP和UDP套接字 本文章主要介绍Linux下套接字的相关接口&#xff0c;和一些基础知识。 套接字 所有网络通信的行为本质都是进程间进行通信&#xff0c;网络通信也是进程间通信&#xff0c;只不过是不同主机上的两个进程之间的通信。网络通信对于双…...

python自动移除excel文件密码(升级v2版本)

欢迎查看第一版 https://blog.csdn.net/weixin_45631815/article/details/140013476?spm1001.2014.3001.5502 一功能改进 此版本主要改进功能有以下: 直接可以调用函数实现可以尝试多个密码没有加密的文件进行保存,可以按实际业务进行改进.思路来源:java 面向对象设计模式.…...

深入MOJO编程语言的单元测试世界

引言 在软件开发的历程中&#xff0c;单元测试扮演着至关重要的角色。单元测试不仅帮助开发者确保代码的每个部分都按预期工作&#xff0c;而且也是代码质量和维护性的关键保障。本文将引导读者了解如何在MOJO这一假想编程语言中编写单元测试&#xff0c;尽管MOJO并非真实存在…...

对WWDC 2025 Keynote 内容的预测

借助我们以往对苹果公司发展路径的深入研究经验&#xff0c;以及大语言模型的分析能力&#xff0c;我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际&#xff0c;我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测&#xff0c;聊作存档。等到明…...

前端开发面试题总结-JavaScript篇(一)

文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包&#xff08;Closure&#xff09;&#xff1f;闭包有什么应用场景和潜在问题&#xff1f;2.解释 JavaScript 的作用域链&#xff08;Scope Chain&#xff09; 二、原型与继承3.原型链是什么&#xff1f;如何实现继承&a…...

Mac下Android Studio扫描根目录卡死问题记录

环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中&#xff0c;提示一个依赖外部头文件的cpp源文件需要同步&#xff0c;点…...

安卓基础(aar)

重新设置java21的环境&#xff0c;临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的&#xff1a; MyApp/ ├── app/ …...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...

WebRTC从入门到实践 - 零基础教程

WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC&#xff1f; WebRTC&#xff08;Web Real-Time Communication&#xff09;是一个支持网页浏览器进行实时语音…...

python爬虫——气象数据爬取

一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用&#xff1a; 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests&#xff1a;发送 …...

【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道

文/法律实务观察组 在债务重组领域&#xff0c;专业机构的核心价值不仅在于减轻债务数字&#xff0c;更在于帮助债务人在履行义务的同时维持基本生活尊严。湖北理元理律师事务所的服务实践表明&#xff0c;合法债务优化需同步实现三重平衡&#xff1a; 法律刚性&#xff08;债…...

Unity VR/MR开发-VR开发与传统3D开发的差异

视频讲解链接&#xff1a;【XR马斯维】VR/MR开发与传统3D开发的差异【UnityVR/MR开发教程--入门】_哔哩哔哩_bilibili...