深入了解linux系统—— 信号的捕捉

前言

信号从产生到处理,可以分为信号产生、信号保存、信号捕捉三个阶段;了解了信号产生和保存,现在来深入了解信号捕捉。

信号捕捉

对于1-31号普通信号,进程可以立即处理,也可以不立即处理而是在合适的时候处理;

在合适的时候处理信号,什么时候合适呢?

信号捕捉的流程

要了解信号捕捉的流程,先要了解内核态和用户态;

简单来说,内核态就是以操作系统的身份去运行;而用户态就是以用户的身份去运行。(后面再详细说明)

这里直接来看信号捕捉的流程:

我们的进程在正常执行,在执行到某条指令,因为系统调用、中断或异常从而进入内核;

而内核处理完异常之后,准备回到用户之前,就会处理当前进程可以递达的信号;

处理信号,执行so_signal方法,如果进程对于信号是自定义捕捉,处理信号就要从内核态回到用户态处理信号;

自定义捕捉完信号之后,就要再回到内核态,然后由内核态再回到用户态,从上次被中断的地方继续向下执行。

以自定义捕捉为例,信号捕捉的流程如下图所示:

在这里插入图片描述

所以,在信号捕捉的整个流程中,存在4次用户态和内核态的转换;简化成以下图:

在这里插入图片描述

简单总结描述信号捕捉流程:

  1. 用户进程执行
    • 进程在用户空间正常执行代码
  2. 进入内核
    • 发生系统调用/中断/异常 → CPU自动切换到内核态
  3. 内核处理事件
    • 内核完成系统调用/中断/异常的处理
  4. 信号检查
    • 内核返回用户态前检查信号:
      有未处理且未阻塞的信号? → 继续
      无信号 → 直接返回用户态
  5. 准备信号处理(针对自定义信号)
    • 内核在用户栈创建"信号栈帧"(包含):
      • 信号处理函数地址
      • 原始执行状态(寄存器值)
      • rt_sigreturn系统调用地址
  6. 第一次返回用户态
    • 内核修改CPU状态:
      • 指令指针 → 信号处理函数
      • 栈指针 → 新信号栈帧
    • 切换到用户态执行信号处理函数
  7. 信号处理完成
    • 信号处理函数执行结束(return语句)
    • 自动跳转到rt_sigreturn系统调用
  8. 第二次进入内核
    • 执行rt_sigreturn系统调用 → 进入内核态
    • 内核从信号栈帧恢复原始状态
  9. 最终返回用户态
    • 内核切换回用户态
    • 进程从当初被中断的位置继续执行

操作系统运行

要了解操作系统是如何运行的,就要先了解一些硬件相关知识

硬件中断

硬件中断是外部硬件设备(如键盘、鼠标、硬盘、网卡、定时器芯片等)向 CPU 发出的一种紧急通知信号,意思是“我有重要的事情需要你马上处理!

就像OS是如何知道键盘上有数据那样,并不是OS定期去排查,而是键盘给CPU发送中断,从而让CPU执行OS中对应的方法。

在这里插入图片描述

如上图所示,存在一个中断控制器,其中每一个中断号都对应一个外部设备;

  • 当外部设备就绪时,就会向中断控制器发送中断,中断控制器就会通知CPU存在中断;(向CPU对应针脚发送高低电频)
  • CPU就会获取中断号,然后中断当前工作并保护现场(保存临时数据等);
  • OS中存在中断向量表,其中存储了对于每一个中断号的对应处理方法;
  • CPU就会根据中断号,去执行中断向量表这对应的中断处理方法。

中断向量表是操作系统的一部分,在启动时就会加载到内存;

通过外部硬件中断,操作系统就不需要对外设进行周期性检测;而是当外部设备触发中断时,CPU就会执行对应的中断处理方法。

这种由外部设备触发,中断系统运行流程,称为硬件中断

时钟中断

有了硬件中断,操作系统就无序去对外设进程周期性检测;

而操作系统不光要管理硬件资源,也要进行进程调度;那能否按照硬件中断的原理,定期的向CPU发送中断,从而定期的执行操作系统的进程调度方法。

在这里插入图片描述

所以,就有了时钟源(当代已经集成在CPU内部);就会定期的向CPU发送中断,CPU通过中断号去执行中断向量表中对应的进程调度方法。

那这样,定期的向CPU发送中断,也就是定期执行进程调度方法;那进程的时间片,本质上就是一个计数器了,每次调度进程就让进程的时间片计数器--,当减到0时就说明进程时间片用完,就指定进程调度算法,执行下一个进程。

CPU存在主频,主频指的就是时钟源向CPU发送中断的频率,主频越快,CPU单位时间内就能够完成更多的操作;CPU就越快。

死循环

有了硬件中断和时钟中断,那操作系统只需要将对应功能添加到中断向量表中,那操作系统还需要干什么呢?

操作系统的本质:就是死循环

void main()
{//......for(;;)pause();
}

通过查看内核,我们也能够发现,操作系统在做完内存管理等任务之后,就是死循环。

软中断

上述硬件中断、时钟中断都是由硬件触发的中断;除此之外呢,也可能因为软件原因触发上述中断。

为了让操作系统支持进行系统调用,CPU中也设计了汇编指令int(或者syscall),让CPU内部触发中断逻辑。

在这里就要了解一下系统调用了,在之前的认知中,系统调用是由操作系统通过的,我们是直接调用系统调用;

但是,在操作系统中,所有的系统调用都存储在一张系统调用表当中;(这张系统调用表用于系统调用中中断处理程序)

我们所调用的系统调用openwrite等等,都是由glibc封装的;

而想要让CPU执行对应的方法,就要让CPU直到对应的系统调用号;

CPU根据系统调用号,然后查表才能调用对应的方法。

在这里插入图片描述

在这里插入图片描述

通过观察,我们也可以发现在glibc的封装实现,是先将系统调用号写入寄存器eax;然后再syscall触发软中断,让CPU根据eax寄存器中的系统调用号执行对应的方法。

内核态和用户态

在信号捕捉流程中,存在一个概念就是:内核态和用户态;

我们知道在进程运行时,通过系统调用或者中断等等陷入内核,进入内核态;而在进行自定义处理时,再有内核态回到用户态;自定义处理完成之后,再通过特定的系统掉用再进入内核态;最后才回到最初中断的位置,由内核态进入用户态。

那内核态和用户态是什么呢?

简单来说,内核态就是以操作系统的身份执行;用户态就是以用户的身份执行。

在虚拟地址空间(进程地址空间中),[0,3]GB是用户空间,我们程序的代码数据、动态库等等都在用户这3GB中;而[3,4]GB是内核空间;

在我们的程序中,我们可以返回自己实现的方法、可以调用库函数;这都是在[0,3]GB用户空间内进行跳转的。
执行对应的代码时,使用用虚拟地址通过页表(用户页表)映射物理地址处,就可以找到对应的代码和数据。

而在我们调用系统调用时,在进程地址空间中,就要从[0,3]GB用户空间跳转到[3,4]GB的内核空间;这样在执行时,通过内核页表映射,找到对应内核的代码运行。

当然,在内核中存在许多进程,这些进程都可能会调用系统调用;而在每一个进程的进程地址空间中的[3,4]GB都是内核空间,都可以通过页表(内核页表)映射,找到内存中操作系统的代码。

所以,我们在进行系统调用时,不用去担心进程能否在内存中找到对应的地址,因为在进程[3,4]GB内核空间中,有了虚拟地址,通过内核页表映射,就能够在内存找到对应的物理地址。

所以,系统调用的执行就是在进程地址空间中进行的。

说了这么多,简单总结就是:

  • 用户态就是,在进程地址空间中,通过[0,3]GB用户空间的虚拟地址,进行页表映射,执行用户自己的代码
  • 内核态就是,通过[3,4]GB内核空间的虚拟地址,进行页表映射,执行操作系统的代码

问题:如何知道虚拟地址是[0,3]GB用户空间的地址还是[3,4]GB内核空间的地址?(CPU执行时如何知道是用户态还是内核态)

在页表当中,记录的不仅仅是虚拟地址和物理地址的映射关系,还用权限(rw)以及当前身份。

此外,在硬件上也存在对应标志:CPU中的Cs段寄存器对应标志位:00(二进制)代表内核、11(二进制)代表用户。

可重入函数

可重入函数是指可以被多个执行流(例如线程、中断处理程序、信号处理程序)同时调用,而不会产生错误或意外结果的函数

简单来说就是:

一个可重入函数在执行过程中,如果被另一个执行流打断并再次进入该函数,当恢复执行时,它仍然能够正确完成其任务,不会破坏自身的数据或全局状态。

在这里插入图片描述

如上图所示,在调用insert时,执行至某位置,进程收到信号转而去执行handler方法,而在handler方法中有调用了insert方法;这样导致了最终的结果不符合我们的预期。

对于一个可重入函数,该函数要满足:

  1. 不使用静态(全局)或非常量静态局部变量: 这些变量在内存中只有一份拷贝,如果多个执行流同时修改它们,会导致数据不一致。
  2. 不返回指向静态数据的指针: 调用者可能会修改这些数据,影响其他执行流。
  3. 仅使用调用者提供的数据或自己栈上的局部变量: 每个执行流(线程/函数调用实例)都有自己的栈空间,局部变量是独立的。
  4. 不调用不可重入的函数: 如果它调用的函数本身是不可重入的(比如使用了全局状态),那么它自己也就变得不可重入了。
  5. 不修改自身的代码: 通常这不是问题,但某些特殊场景(如自修改代码)需要考虑。
  6. 不依赖外部硬件状态(除非以原子方式访问): 比如多个执行流同时操作同一个硬件寄存器可能造成冲突。

volatile

volatileC语言中的一个关键字,这个关键字的在之前的学习中并没有使用过;

volatile关键字用来修饰一个变量,其作用就是,告诉编译器该变量的值可能会变化,让编译器不要对其进程优化,让CPU每次访问该变量的值都从内存中获取。

#include <iostream>
#include <signal.h>
#include <unistd.h>int flag = 0;
void handler(int signum)
{std::cout << "change flag 0 -> 1" << std::endl;flag = 1;
}
int main()
{signal(2, handler);int cnt = 0;while (!flag){std::cout << "flag :" << flag << std::endl;sleep(1);}return 0;
}

在上述代码中,main函数while(!falg),当flag = 0时,循环一直在进行;

当进程收到2号信号时,执行自定义处理handler方法,修改falg

预期结果就是:进程在收到2号信号时,flag修改为1,循环就结束了。

正常来说,CPU在执行进程时,访问flag变量都是从内存中读取;而在main函数中并没有修改flag变量,一些编译器就会对其进行优化,将flag变量直接写入CPU寄存器中。

volatile修饰变量就是告诉编译器不要进行优化,每次都从内存中读取变量的值。

SIGCHLD信号

这里简单了解一些SIGCHLD信号;

SIGCHLD信号是子进程退出时,操作系统给父进程发送的一个信号。

我们知道,子进程在退出时,会进入僵尸状态,等待父进程回收退出信息;就要父进程等待子进程。

而如果我们不关心子进程的退出信息,我们就可以将父进程对于SIGCHILD信号的处理方式设置成SIG_IGN

这样子进程在退出时,操作系统给父进程发送SIGCHLD信号,父进程SIG_IGN,此时子进程的task_struct就会立即被回收,不需要父进程等待。

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{signal(SIGCHLD, SIG_IGN);int id = fork();if(id < 0)exit(1);else if(id == 0){printf("child process pid : %d\n",getpid());sleep(1);exit(1);}int cnt = 3;while(cnt--){printf("parent process pid : %d\n",getpid());sleep(1);}return 0;
}

在这里插入图片描述

可以看到,子进程退出后,父进程没有等待wait;子进程也没有出现僵尸状态。

在这里插入图片描述

但是,可以看到进程对于SIGCHLD信号的处理方式是Ign;那为什么不调用signal(SIGCHLD, SIG_IGN),父进程不等待,子进程就要进入僵尸状态呢?

这里,进程对于SIGCHLD信号的处理方式是默认处理SIG_DFL,而默认处理的方式是Ign

SIG_IGN不一样,操作系统设置成默认处理SIG_DFL,默认处理的方式是Ign;这样在子进程退出后,父进程就可以随时获取子进程的退出信息,回收子进程了。

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

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

相关文章

twikitFKS: 基于 twikit 2.3.1 的改进版本

twikitFKS: 基于 twikit 2.3.1 的改进版本 项目概述 关于 twikit twikit 是一个优秀的 Twitter API 爬虫库&#xff0c;它的核心优势在于无需 API Key即可访问 Twitter 功能。通过网页爬虫技术&#xff0c;twikit 实现了&#xff1a; 发布推文和媒体内容搜索推文和用户获取…

C Primer Plus 第6版 编程练习——第9章(下)

7.编写一个函数&#xff0c;从标准输入中读取字符&#xff0c;直到遇到文件结尾。程序要报告每个字符是否是字母。如果是&#xff0c;还要报告该字母在字母表中的数值位置。例如&#xff0c;c和C在字母表中的位置都是3。合并一个函数&#xff0c;以一个字符作为参数&#xff0c…

如何用文思助手改好一篇烂材料

在日常工作中&#xff0c;我们常常会遇到这样的问题&#xff1a;因为工作要使用到之前写的文章再看发现内容杂乱无章、或者收到的一些返稿内容质量差&#xff0c;不修改无法使用。但其实它们可能只是缺少了系统性的梳理与打磨。今天我们就来聊一聊&#xff0c;如何对一些不满意…

VSCODE常规设置

摘要&#xff1a;用于新下载的vscode设置一些个人化的操作在 "Files: Auto Save" 下拉菜单中&#xff0c;选择你想要的自动保存模式。常见的选项包括&#xff1a;"off"&#xff1a;禁用自动保存。 "afterDelay"&#xff1a;在你停止编辑一段时间…

2025秋招突围战:AI智能监考如何重构远程笔试公平防线?

2025秋招季即将来临&#xff0c;企业校招规模预计突破百万量级&#xff0c;远程笔试成为主流筛选方式。然而&#xff0c;传统监考模式暴露出作弊行为难追溯、人力成本过高、数据维度单一等痛点&#xff0c;让HR陷入“效率与公平”的两难困境。牛客AI智能监考系统&#xff0c;通…

Python 基础语法与数据类型(十三) - 实例方法、类方法、静态方法

文章目录1. 实例方法 (Instance Methods)1.1 特点与语法1.2 实例方法示例2. 类方法 (Class Methods)2.1 特点与语法2.2 类方法示例3. 静态方法 (Static Methods)3.1 特点与语法3.2 静态方法示例4. 三种方法的对比总结总结练习题练习题答案创作不易&#xff0c;请各位看官顺手点…

Wireshark的安装和基本使用

文章目录一、Wireshark介绍二、Wireshark安装三、Wireshark讲解1.界面介绍&#xff08;1&#xff09;分组列表&#xff08;2&#xff09;分组详情&#xff08;3&#xff09;分组字节流一、Wireshark介绍 Wireshark 是一款开源的网络协议分析工具&#xff0c;能够捕获、过滤和分…

[yotroy.cool] Git 历史迁移笔记:将 Git 项目嵌入另一个仓库子目录中(保留提交记录)

个人博客https://www.yotroy.cool/&#xff0c;感谢关注&#xff5e; 图片资源可能显示不全&#xff0c;请前往博客查看哦&#xff01; 说来惭愧&#xff0c;这篇是AI帮助我解决实际问题后&#xff0c;又生成的一篇博客&#xff0c;效率特别高。 在开发中&#xff0c;我们常会…

91套商业策划创业融资计划书PPT模版

创业融资计划书PPT模版&#xff0c;商业项目技术书PPT模版&#xff0c;商业创业计划书&#xff0c;商业融资企业宣传PPT模版&#xff0c;活动策划方案书PPT模版&#xff0c;IOS风格商业计划书PPT模版 91套商业策划创业融资计划书PPT模版&#xff1a;https://pan.quark.cn/s/739…

探秘阿里云通义九子:解锁AI无限可能

通义九子初印象在当今人工智能飞速发展的时代&#xff0c;阿里云通义九子宛如一颗璀璨的明星&#xff0c;闪耀在 AI 的浩瀚天空中。作为阿里云推出的一系列强大的人工智能模型&#xff0c;通义九子在自然语言处理、图像生成、智能客服等多个领域展现出了卓越的能力&#xff0c;…

Python网络爬虫之requests库

目录 一.网络爬虫的介绍 1.网络爬虫库 2.robot.txt规则 二.requests库 1.requests库的安装 2.get()函数 3.Response对象 Response的属性 设置编码 返回网页内容 text() content() 三.提交信息到网页 post()函数 四.会话与代理服务器 一.网络爬虫的介绍 1.网络爬虫…

区块链技术详解:从原理到应用

引言 区块链作为一项颠覆性技术&#xff0c;已从加密货币的基石演变为重塑多个行业的创新引擎。本文旨在深入解析其核心原理、关键特性、技术架构、主流应用及未来挑战。一、 区块链核心概念&#xff1a;超越加密货币的分布式账本 本质定义&#xff1a; 区块链是一个去中心化、…

用Finalshell连接服务器后出现文件目录不显示,且刷新报错空指针问题记录

修改SSH配置‌编辑sshd_config‌使用管理员权限编辑/etc/ssh/sshd_config文件&#xff0c;找到Subsystem相关配置。原配置为Subsystem sftp /usr/libexec/openssh/sftp-server使用“i”编辑文件将修改为Subsystem sftp internal-sftp修改完成后使用Esc命令&#xff0c;退出编辑…

C语言:游戏代码分享

小游戏分享 目录 小游戏分享 1.井字棋游戏 2.简单计算器游戏 3.猜单词 4.石头剪刀布游戏 5.猜数字游戏 1.井字棋游戏 「33 棋盘上的思维博弈&#xff01;与好友轮流落子&#xff0c;抢占先机&#xff0c;连成一线即可获胜。简单规则蕴含无限策略&#xff0c;展现你的战术…

深度学习入门-卷积神经网络(CNN)(下)

1-4、 深度学习入门-卷积神经网络&#xff08;CNN&#xff09;&#xff08;上&#xff09;-CSDN博客 5、 卷积神经网络&#xff08;CNN&#xff09;的实现 简单网络的构成是“Convolution - ReLU - Pooling - Affine - ReLU - Affine - Softmax”&#xff1a; 相关代码&#…

Java 大视界 -- Java 大数据在智能交通智能公交站台乘客流量预测与服务优化中的应用(349)

Java 大视界 -- Java 大数据在智能交通智能公交站台乘客流量预测与服务优化中的应用&#xff08;349&#xff09;引言&#xff1a;正文&#xff1a;一、Java 全场景韧性调度系统&#xff08;新增极端天气 车型适配&#xff09;1.1 极端天气&#xff1a;暴雪 / 台风的分钟级响应…

数论内容主要包括哪些

数论&#xff08;Number Theory&#xff09;是数学中研究整数的性质及其相互关系的一个分支&#xff0c;被誉为“数学中的皇后”。它历史悠久&#xff0c;内容丰富&#xff0c;既包含许多初等、直观的问题&#xff0c;也涉及高深、抽象的理论。数论的主要内容包括以下几个方面&…

springboot打包二次压缩Excel导致损坏

springboot打包二次压缩Excel导致损坏开发时&#xff0c;将Excel文件放到resources下&#xff0c;通过类加载器流读取&#xff0c;返回api用于下载该Excel文件。我发现这样下载的Excel被损坏了&#xff0c;无法打开&#xff0c;推测是springboot打包插件默认对resources下的所有…

huggingface笔记

1. huggingface的下载目录 ~/.cache/huggingface 2. 如何修改hugging face的模型默认下载地址 huggingface的默认下载路径在~/.cache/huggingface/hub/&#xff0c;但模型数据占用空间往往很大&#xff0c;可以用以下方法修改默认下载路径。 方法一&#xff1a;在linux中指定环…

Redis3:Redis数据结构与命令全解析

目录 1、redis数据结构介绍 1.1命令学习方式 1.1.1命令行查询 2、redis的通用命令 2.1查找常见的通用命令 2.2常见的通用命令&#xff1a; 3、String类型 3.1String类型的常见命令 3.2Key的层级格式 3.2.1Key的结构 4、Hash类型 4.1Hash类型 4.2Hash类型常见命令 …