【Linux】进程信号 --- 信号处理

在这里插入图片描述

👦个人主页:Weraphael
✍🏻作者简介:目前正在学习c++和算法
✈️专栏:Linux
🐋 希望大家多多支持,咱一起进步!😁
如果文章有啥瑕疵,希望大佬指点一二
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍


目录

  • 一、信号的处理时机(什么时候处理)
  • 二、用户态与内核态
      • 2.1 概念
      • 2.2 再谈进程地址空间
  • 三、信号的处理过程(如何处理)
  • 四、信号的捕捉
      • 4.1 内核如何实现信号的捕捉
      • 4.2 系统调用 --- sigaction
  • 五、进程信号总结
  • 六、相关代码

一、信号的处理时机(什么时候处理)

在前面的学习中,我们反复说过:当操作系统给进程发生信号后,进程可能不会立即去处理信号,而是等到合适的时候去处理,这是因为进程可能正在做更重要的事情!

但在特殊情况下,信号可能会立即被处理。比如:当信号被阻塞后,信号产生时,此时信号被阻塞了,没有立即被处理,那么信号就会记录在pending表中,当阻塞解除后,信号会被立即递达,此时信号会被立即处理。

那么对于进程来说什么是合适的时候呢?

进程要处理一个信号,首先要知道自己收到信号了。那么进程可以通过查阅block表、pending表和handler表。但这些表都是内核的数据结构,进程无法直接查看或修改它们,只能通过操作系统提供的系统调用接口来与之交互(进程处于内核状态)。因此,进程处理的合适时机是:进程从内核态返回到用户态时,会对信号进行检测及处理

二、用户态与内核态

2.1 概念

  • 用户态执行用户所写的代码时,就属于用户态

  • 内核态执行操作系统的代码时,就属于内核态

我们知道,一旦程序被加载到内存中形成了一个进程,CPU可以开始执行这个进程的代码。如果代码中调用系统调用,由于系统调用的实现在操作系统内核中,并且操作系统不相信任何人。因此,CPU在这时会从用户态切换到内核态(“身份”变化,提升权限),才允许开始执行相应的操作系统内核代码。当内核代码执行完之后,就会从内核态切换回用户态继续执行用户写的代码。

总之,进程调用系统调用不仅仅是调用函数这么简单,操作系统还需要自动进行“身份”变换

下面来结合进程地址空间深入理解操作系统的代码及状态切换等内容。

2.2 再谈进程地址空间

在这里插入图片描述

32位操作系统中,通常会将整个4GB的虚拟地址空间划分为用户空间内核空间两部分。

  • 用户空间:这个部分包含了用户进程可以直接访问的虚拟内存地址范围。每个用户进程都有自己的独立的用户空间,使得它们之间的内存访问相互隔离,提高了系统的安全性和稳定性。

  • 内核空间:这个部分是保留给操作系统内核使用的,存储的就是操作系统相关代码和数据。并且这块区域采用内核级页表与物理地址进行映射进程所谓的执行操作系统的代码及系统调用,就是在使用这1 GB的内核空间(和动态库类似)

  • 内核级页表用于管理操作系统内核的虚拟地址和物理地址之间的映射。它与用户级页表有所不同,用户级页表只管理用户进程的地址映射。这两张页表是相互独立的!

    • 对于用户级页表,有几个进程,操作系统就要维护几份,因为要保证进程独立性
    • 对于内核级页表,操作系统只需维护一份,因为内核的代码和数据对所有进程来说都是相同的。

如何理解进程切换?

  1. 进程切换需要调用系统调用,CPU在这时会从用户态切换到内核态,在当前进程的进程地址空间中的内核空间,找到操作系统的代码和数据。
  2. 执行操作系统的代码,将当前进程的用户空间的代码和数据剥离下来,从而换上另一个进程的代码和数据,但内核空间的内容永远不变。

操作系统的本质?

操作系统是管理软硬件资源的软件。当电脑开机时,它就已经被加载到内存,因此操作系统也是一个进程。但我们可能会疑惑,用户写的进程是通过操作系统去运行调度的。那是谁在推动操作系统去运行的呢?答案:硬件。

这里直接给出结论:操作系统是基于时钟中断的一个死循环!

在这里插入图片描述

其实在计算机硬件中,有一个时钟芯片,每隔很短的时间(纳秒级别),向CPU发送时钟中断。CPU收到中断请求后,会暂停当前正在执行的任务,并根据中断号找到对应的中断向量表(本质上就是数组)中对应中断号的条目。中断向量表是一个存储在内存中的数据结构,由操作系统维护,表中的条目是调用硬件设备驱动程序的方法的地址,找到后并执行。比如里面就会进行检查进程的时间片到了没有,如果到了,就把这个进程剥夺下去,换下一个进程。从而完成进程的调度。

如果没有进程需要调度,操作系统会陷入空闲状态,内核可能会执行一个类似for(;;) pause();的死循环。这段代码的作用是让内核进入一种等待状态,等待系统中的某些事件发生,比如新的进程就绪或者外部中断的到来等。

用户态和内核态之间是如何转化?

CPU有还2个寄存器:

  • CR3 寄存器:是页表基址寄存器,用于存储页表的地址(物理地址)。在内核态和用户态身份切换时,操作系统会更新CR3寄存器,以加载新的地址空间信息。
  • ECS寄存器CPU是执行内核态代码还是用户态代码,主要看ECS寄存器最低2位,排列组合式00011011。如果是00代表是内核态,11代表用户态。如果要调用系统调用接口,操作系统都会对此寄存器做检测。

三、信号的处理过程(如何处理)

  • 说明:以下不考虑信号被阻塞的情况。如果信号被阻塞,信号不会立即被处理,一旦解除阻塞,信号就会立即被处理。

在这里插入图片描述

因此,进程处理的合适时机是:进程从内核态返回到用户态时,会对信号进行检测及处理

通过一张图快速记录信号的处理过程

在这里插入图片描述

四、信号的捕捉

4.1 内核如何实现信号的捕捉

这里其实就是对下图第③步再进行详细讲解。

在这里插入图片描述

  • 概念:如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,则称为捕捉信号。
  • 因为用户自定义动作是位于用户空间中的,所以当内核态中任务(执行内核代码)完成,准备返回用户态时,会检测三表中的pending表为1且没有被阻塞block表为0,并且此时信号处理动作handler表为用户自定义动作,先将pending表对应1置为0,再切入用户态 ,完成用户自定义动作的执行。
  • 因为用户自定义动作sighandler函数和main函数属于不同的堆栈空间,它们之间也不存在调用与被调用的关系(是操作系统调用的sighandler函数),是两个独立的执行流。
  • sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态,其中sigreturn的目的是告诉操作系统当前进程的信号处理已经完成。
  • 最后当执行完sighandler函数后会到内核中,如果没有新的信号要递达,自动执行特殊的系统调用sys_sigreturn,这次再返回用户态就是恢复main函数的上下文继续执行了。

4.2 系统调用 — sigaction

sigaction函数也可以用户自定义动作,比signal函数功能更丰富。

函数原型如下:

#include <signal.h>int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

参数解释:

  • signum:要处理的信号的编号,比如 SIGINT等。
  • act:指向 struct sigaction 结构的指针,用来设置新的信号处理方式。
  • oldact:指向 struct sigaction 结构的指针,用来获取之前该信号的处理方式。或者可以设置为nullptr表示不获取之前该信号的处理方式。
  • 返回值:成功时返回0,失败时返回-1,并设置errno表示具体错误原因。

以下是 struct sigaction 结构的定义

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

字段解释:

  • void (*sa_handler)(int);:指定的信号处理函数,可以是一个函数指针,用于处理指定的信号。可以设置为 SIG_IGN(忽略信号)或 SIG_DFL(默认处理方式)。
  • sigset_t sa_mask;:当信号在执行 用户自定义动作时,可以将部分信号进行屏蔽,直到用户自定义动作执行完成。也就是说,我们可以提前设置一批 待阻塞 的 屏蔽信号集,当执行用户自定义动作时,这些 屏蔽信号集 中的 信号将会被屏蔽(避免干扰用户自定义动作的执行),直到用户自定义动作执行完成。
  • void (*sa_sigaction)(int, siginfo_t *, void *);:不关心。
  • int sa_flags;:不关心。
  • void (*sa_handler)(void);:不关心。

可以简单用一下sigaction函数

#include <iostream>
#include <signal.h>
#include <cstring>
#include <unistd.h>
using namespace std;void DisplayPending(sigset_t pending)
{// 打印 pending 表cout << "当前进程的 pending 表为: ";int i = 1;while (i < 32){if (sigismember(&pending, i))cout << "1";elsecout << "0";i++;}cout << endl;
}// 自定义信号处理函数
void handler(int signo)
{cout << "捕捉到了一个信号:" << signo << endl;int n = 10;while (n--){// 获取进程的pending表sigset_t pending;sigemptyset(&pending);int n = sigpending(&pending);if (n < 0){continue;}DisplayPending(pending);sleep(1);}exit(0);
}int main()
{struct sigaction act, oldact;// 初始化结构体对象(可选)memset(&act, 0, sizeof(act));memset(&oldact, 0, sizeof(oldact));// 设置信号处理函数act.sa_handler = handler;// 初始化 屏蔽信号集sigaddset(&act.sa_mask, 3);sigaddset(&act.sa_mask, 4);sigaddset(&act.sa_mask, 5);//  给2号信号注册自定义动作sigaction(2, &act, &oldact);while (true){cout << "I am a process: " << getpid() << endl;sleep(1);}return 0;
}

【程序结果】

在这里插入图片描述

并且我们还发现了一个现象:当我们捕捉2号信号后,对应的2号信号的pending位图是为0,说明在执行handler方法之前,就已经将其置为0了,这就和我们之前的理论对应上了。

注意:除了sa_mask可以在当信号在执行用户自定义动作时,将部分信号进行屏蔽,直到用户自定义动作执行完成。除此之外,当某个信号的处理函数被调用时,内核自动会将当前信号屏蔽,也就是将当前的pending表和block表对应位置为1,直到用户自定义动作执行完成后恢复。如果在执行用户自定义动作的过程中,这种信号再次产生,那么它会被阻塞到当前处理结束为止。这是因为操作系统不允许对某个信号重复捕捉,最多只能捕捉一层,防止信号捕捉被嵌套调用

#include <iostream>
#include <signal.h>
#include <cstring>
#include <unistd.h>
using namespace std;void DisplayPending(sigset_t pending)
{// 打印 pending 表cout << "当前进程的 pending 表为: ";int i = 1;while (i < 32){if (sigismember(&pending, i))cout << "1";elsecout << "0";i++;}cout << endl;
}void handler(int signo)
{cout << "捕捉到了一个信号:" << signo << endl;while (true){// 获取进程的pending表sigset_t pending;sigemptyset(&pending);int n = sigpending(&pending);if (n < 0){continue;}DisplayPending(pending);sleep(1);}exit(0);
}int main()
{struct sigaction act, oldact;// 初始化结构体对象(可选)memset(&act, 0, sizeof(act));memset(&oldact, 0, sizeof(oldact));// 设置信号处理函数act.sa_handler = handler;//  给2号信号注册自定义动作sigaction(2, &act, &oldact);while (true){cout << "I am a process: " << getpid() << endl;sleep(1);}return 0;
}

【程序结果】

在这里插入图片描述

五、进程信号总结

截至目前,信号 处理的所有过程已经全部学习完毕了

  • 信号产生阶段:有四种产生方式,包括 键盘键入、系统调用、软件条件、硬件异常

  • 信号保存阶段:内核中存在三张表,blcok 表、pending 表以及 handler 表,信号在产生之后,存储在 pending 表中

  • 信号处理阶段:信号在 内核态 切换回 用户态 时,才会被处理

在这里插入图片描述

六、相关代码

本篇博客的代码:点击跳转

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

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

相关文章

睿考网:没有初级能直接考中级经济师吗?

经济师考试分为初级、中级、高级三个类别&#xff0c;属于职业资格证书&#xff0c;考试是由统一组织、统一大纲、统一命题的方式。 没有初级可以直接考中级经济师吗? 可以直接报考&#xff0c;如果考生符合中级经济师报考要求&#xff0c;直接报名参加就可以&#xff0c;具…

通过iframe嵌套的不同域名的页面之间处理cookie存储失败的问题——js技能提升

最近同事在写mvc的后台管理系统&#xff0c;通过iframe实现不同域名的页面的嵌套。 但是有个问题&#xff0c;就是从父页面打开iframe的子页面时&#xff0c;需要登录子页面&#xff0c;此时需要将子页面登录后的token存储到子页面的cookie中&#xff0c;方便子页面的其他接口…

SpringBoot+Session+redis实现分布式登录

SpringBootSessionRedis实现分布式登录功能实现 文章目录 目录 文章目录 前言 一、引库 二、修改配置文件 三、使用 四、解决乱码问题 1.引库 2.配置redis序列化 3.配置Session-Redis序列化 前言 这里简单介绍一下&#xff0c;如果你想多台机器部署你的项目的话&#xff0c;在…

Maven的常用命令(面试篇之Maven)

我在写项目时,使用Maven的插件的命令来进行打包等,却发现报错误了,虽然解决了, 但借此机会来总结一下Maven的常用命令: 这些插件都有着自己的命令,虽然,我们可以简化的使用一些idea中的方便的按键: 但 , 一个程序员的功力深浅就在这些细节末尾处: 在Maven中&#xff0c;插件是…

【MySQL进阶之路 | 高级篇】范式概述与第一范式

1. 范式简介 在关系型数据库中&#xff0c;关于数据表的设计的基本原则&#xff0c;规则就称为范式。可以理解为&#xff0c;一张数据表的设计结果需要满足的某种设计标准的级别。要想设计一个结构合理的关系型数据库&#xff0c;必须满足一定的范式。 范式的英文名是Normal …

OpenHarmony 入门——ArkUI 自定义组件之间的状态装饰器小结(一)

文章大纲 引言一、状态管理概述二、基本术语三、状态装饰器总览 引言 前面说了ArkTS 是在TypeScript基础上结合ArkUI框架扩展定制的&#xff0c;状态管理中的各种装饰器就是扩展的功能之一&#xff0c;可以让开发者通过声明式UI快速高效实现组件之间的数据同步&#xff0c;至于…

Leetcode之string

目录 前言1. 字符串相加2. 仅仅反转字母3. 字符串中的第一个唯一字符4. 字符串最后一个单词的长度5. 验证回文串6. 反转字符串Ⅱ7. 反转字符串的单词Ⅲ8. 字符串相乘9. 打印日期 前言 本篇整理了一些关于string类题目的练习, 希望能够学以巩固. 博客主页: 酷酷学!!! 点击关注…

转置卷积方法

一、定义 1、卷积神经网络层通常会减少&#xff08;或保持不变&#xff09;采样输入图像的空间维度&#xff08;高和宽&#xff09;&#xff0c;另一种类型的卷积神经网络层&#xff0c;它可以增加上采样中间层特征图的空间维度&#xff0c; 用于逆转下采样导致的空间尺寸减小…

【BES2500x系列 -- RTX5操作系统】系统启动流程 -- boot loader概念讲解 --(九)

&#x1f48c; 所属专栏&#xff1a;【BES2500x系列】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f49…

.NET 情报 | 分析某云系统添加管理员漏洞

01阅读须知 此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等&#xff08;包括但不限于&#xff09;进行检测或维护参考&#xff0c;未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失&#xf…

【计算机毕业设计】881音乐网站

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

跟代码执行流程,读Megatron源码(二)训练入口pretrain_gpt.py

Megatron-LM默认支持GPT、T5、BERT等多个常见模型的预训练&#xff0c;当下大模型流行&#xff0c;故以pretrain_gpt.py为例做源码的走读。 一. 启动pretrain_gpt.py pretrain_gpt.py为GPT类模型的训练入口&#xff0c;它通过命令行形式被调用&#xff0c;其精确执行路径位于M…

计算机网络通信基础概念

目录 1、网络通信的本质 2、网络的发展 3、网络协议&#xff08;TCP\IP协议&#xff09; 3.1 协议实现通信的原理 3.2 协议的具体概念 3.3 协议的模型 4、数据链路层 5、网络协议栈和操作系统的关系 6、网络协议通信过程 6.1 通信过程的封装与解包 7、以太网通信…

Ai绘画变现的14种途径 学习Stablediffusion midjourney用途

AIGC&#xff0c;一个在当代社会中不可忽视的词汇&#xff0c;指的是利用人工智能技术生成创作内容。近年来&#xff0c;全球范围内涌现出50个热门的AI工具&#xff0c;其中&#xff0c;以140亿次访问量雄踞榜首的“GBT”&#xff0c;无疑是AI领域的领头羊。在这些工具中&#…

DETR目标检测模型训练自己的数据集

前言 基础环境&#xff1a;ubuntu20.04、python3.8、pytorch:1.10.0、CUDA:11.3 代码地址&#xff1a;https://github.com/facebookresearch/detr 目录 一、训练准备1、预训练模型下载2、txt文件转为coco模式 二、修改训练模型参数三、开始训练四、实现DETR的推理 一、训练准备…

【RT摩拳擦掌】RT600 4路音频同步输入1路TDM输出方案

【RT摩拳擦掌】RT600 4路音频同步输入1路TDM输出方案 一&#xff0c; 文章简介二&#xff0c;硬件平台构建2.1 音频源板2.2 音频收发板2.3 双板硬件连接 三&#xff0c;软件方案与软件实现3.1 方案实现3.2 软件代码实现3.2.1 4路I2S接收3.2.2 I2S DMA pingpong配置3.2.3 音频数…

Python自动化批量下载ECWMF和GFS最新预报数据脚本

一、白嫖EC和GFS预报数据 EC的openData部分公开了一部分预报数据&#xff0c;作为普通用户只能访问这些免费预报数据&#xff0c;具体位置在这 可以发现&#xff0c;由于是Open Data&#xff0c;我们只能获得临近四天的预报结果&#xff0c;虽然时间较短&#xff0c;但是我们…

vue3前端开发-小兔鲜项目-二级页面面包屑导航和跳转

vue3前端开发-小兔鲜项目-二级页面面包屑导航和跳转&#xff01;这一次&#xff0c;做两件事。第一件事是把二级分类页面的跳转&#xff08;也就是路由&#xff09;设计一下。第二件事是把二级页面的面包屑导航设计一下。 第一件事&#xff0c;二级页面的跳转路由设计一下。 如…

Python爬虫(4) --爬取网页图片

文章目录 爬虫爬取图片指定url发送请求获取想要的数据数据解析定位想要内容的位置存放图片 完整代码实现总结 爬虫 Python 爬虫是一种自动化工具&#xff0c;用于从互联网上抓取网页数据并提取有用的信息。Python 因其简洁的语法和丰富的库支持&#xff08;如 requests、Beaut…

科普文:后端性能优化的实战小结

一、背景与效果 ICBU的核心沟通场景有了10年的“积累”&#xff0c;核心场景的界面响应耗时被拉的越来越长&#xff0c;也让性能优化工作提上了日程&#xff0c;先说结论&#xff0c;经过这一波前后端齐心协力的优化努力&#xff0c;两个核心界面90分位的数据&#xff0c;FCP平…