【Linux】信号的处理

信号的处理
- 1 信号的处理
- 2 内核态 VS 用户态
- 3 键盘输入数据的过程
- 4 如何理解OS如何正常的运行
- 5 如何进行信号捕捉
- 信号处理的总结
- 6 可重入函数
- volatile关键字
- Thanks♪(・ω・)ノ谢谢阅读!!!
- 下一篇文章见
1 信号的处理
处理信号本质就是递达这个信号!首先我们来看如何进行捕捉信号:信号的处理有三种:
signal(2 , handler);//自定义
signal(2 , SIG_IGN);//忽略
signal(2 , SIG_DFL);//默认
注意handler表是函数指针表,传入的参数一定是函数指针类型!!!
我们说过:信号可能不会被立即处理,而是在合适的时候进行处理。那么这个合适的时候到底是什么时候?!
进程从内核态(处于操作系统的状态)返回到用户态(处在用户状态)的时候进行处理!

- 首先用户运行一个进程,在执行代码指令时因为中断,异常或者系统调用进如操作系统。
- 进入操作操作系统就变为内核态,操作系统处理完之后,就对进程的三张表进行检查:如果pending中存在,继续判断,如果被block了了就不进行处理,反之执行对应方法!
- 执行对应的方法时,如果是自定义方法,会返回到用户层面的代码,执行对应的方法。然后通过系统调用再次回到内核态。
- 进入内核态之后,再返回到原本的用户指令位置中
注意:
- 操作系统不能直接转过去执行用户提供的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 ...。只有我们找到特定数组下标(系统调用号)的方法,就能执行系统调用了!
回到之前的函数指针数组,我们在这里再添加一个新方法,用来调度任何的系统调用。使用系统调用就要有:
- 系统调用号
- 系统调用函数指针表(操作系统内部)
用户层面如何使用到操作系统中的函数指针表呢?
这就要回到CPU中来谈,CPU中两个寄存器,假设叫做X 和 eax,当用户调用fork时,函数内部有类似
mov 2 eax //将系统调用号放入寄存器中
而所谓的中断不也是让CPU中的寄存器储存一个中断号来进行调用吗!那CPU内部可不可以直接写出数字呢?可以,当eax获取到数字时,寄存器X就会形成对应的数字,来执行操作系统的系统调用。
通过这种方法就可以通过用户的代码跳转到内核,来执行系统调用。但操作系统不是不相信任何用户吗?怎么就直接跳转了呢?用户是无法直接跳转到内存中的内核空间(3~4GB)。那么就有几个问题:
- 操作系统如何阻止用户直接访问?
- 系统调用最终是可以被调用的,又是如何做到的?
在操作系统中,解决这两种问题是非常复杂的!有很多概念,所以简单单来讲:做到这些需要硬件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很类似了
再来看看参数
- int signum : 表示要对哪个信号进行捕捉
- const struct sigaction *act : 输入型参数,表示要执行的结构体方法
- 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号信号永远都不能屏蔽!!!
信号处理的总结
对于信号我们学习了三个阶段:
- 信号的产生与发送:中断,异常,系统调用。
- 信号的保存:三张表:阻塞,未决和递达
- 信号的处理
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数据分析中,数据的导入和导出是非常关键的步骤。这些步骤通常涉及到将数据从外部文件(如CSV、Excel、数据库等)读入到Python程序中,以及将处理后的数据导出回外部文件或数据库。以下是一些常用的库和方法来实现这些操作。…...
【JAVA多线程】线程池概论
目录 1.概述 2.ThreadPoolExector 2.1.参数 2.2.新任务提交流程 2.3.拒绝策略 2.4.代码示例 1.概述 线程池的核心: 线程池的实现原理是个标准的生产消费者模型,调用方不停向线程池中写数据,线程池中的线程组不停从队列中取任务。 实现…...
java双亲委派机制
Java中的双亲委派机制(Parent Delegation Model)是一种类加载机制,它确保了类加载的安全性和一致性。该机制规定了类加载器在加载类时的顺序和方式,从而避免了重复加载和类冲突问题。 以下是一个简单的自定义类加载器的示例&#…...
记录第一次使用air热更新golang项目
下载 go install github.com/cosmtrek/airlatest 下载时提示: module declares its path as: github.com/air-verse/air but was required as: github.com/cosmtrek/air 此时,需要在go.mod中加上这么一句: replace github.com/cosmtrek/air &…...
Leetcode 3213. Construct String with Minimum Cost
Leetcode 3213. Construct String with Minimum Cost 1. 解题思路2. 代码实现 题目链接:3213. Construct String with Minimum Cost 1. 解题思路 这一题的话思路上还是比较直接的,就是一个trie树加一个动态规划,通过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请求和响应
一、爬虫的三个步骤(要学习的内容) 1、获取网页内容 (HTTP请求、Requests库) 2、解析网页内容 (HTML网页结构、Beautiful Soup库) 3、存储或分析数据 b站学习链接: 【【Python爬虫】爆肝两…...
华为OD机考题(HJ41 称砝码)
前言 经过前期的数据结构和算法学习,开始以OD机考题作为练习题,继续加强下熟练程度。有需要的可以同步练习下。 描述 现有n种砝码,重量互不相等,分别为 m1,m2,m3…mn ; 每种砝码对应的数量为 x1,x2,x3...xn 。现在要…...
Qt涂鸦板
Qt版本:Qt6 具体代码: 头文件 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 什么是构造函数 类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。 每次构造的是构造成员变量的初始化值,内存空间等。 构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不…...
强化学习中的Double DQN、Dueling DQN和PER DQN算法详解及实战
1. 深度Q网络(DQN)回顾 DQN通过神经网络近似状态-动作值函数(Q函数),在训练过程中使用经验回放(Experience Replay)和固定目标网络(Fixed Target Network)来稳定训练过程…...
前端八股文 说一说样式优先级的规则是什么?
标准的回答 CSS样式的优先级应该分成四大类 第一类 !important: 😄无论引入方式是什么,选择器是什么,它的优先级都是最高的。 第二类 引入方式: 😄行内样式的优先级要高于嵌入和外链,嵌入和外链…...
洞察国内 AI 绘画行业的璀璨前景
在科技的浪潮中,AI 绘画如同一颗璀璨的新星,正在国内的艺术与技术领域绽放出耀眼的光芒。 近年来,国内 AI 绘画行业发展迅猛,展现出巨大的潜力。随着人工智能技术的不断突破,AI 绘画算法日益精进,能够生成…...
socket编程
文章目录 套接字网路字节序列TCP和UDP套接字 本文章主要介绍Linux下套接字的相关接口,和一些基础知识。 套接字 所有网络通信的行为本质都是进程间进行通信,网络通信也是进程间通信,只不过是不同主机上的两个进程之间的通信。网络通信对于双…...
python自动移除excel文件密码(升级v2版本)
欢迎查看第一版 https://blog.csdn.net/weixin_45631815/article/details/140013476?spm1001.2014.3001.5502 一功能改进 此版本主要改进功能有以下: 直接可以调用函数实现可以尝试多个密码没有加密的文件进行保存,可以按实际业务进行改进.思路来源:java 面向对象设计模式.…...
深入MOJO编程语言的单元测试世界
引言 在软件开发的历程中,单元测试扮演着至关重要的角色。单元测试不仅帮助开发者确保代码的每个部分都按预期工作,而且也是代码质量和维护性的关键保障。本文将引导读者了解如何在MOJO这一假想编程语言中编写单元测试,尽管MOJO并非真实存在…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...
Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...
【2025年】解决Burpsuite抓不到https包的问题
环境:windows11 burpsuite:2025.5 在抓取https网站时,burpsuite抓取不到https数据包,只显示: 解决该问题只需如下三个步骤: 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...
学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...
