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

深入了解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.lryc.cn/news/591394.html

相关文章:

  • 防止电脑息屏 html
  • 人类社会发展过程中的熵增定律
  • 共指消解技术全解析:从语言学规则到深度学习(附论文精读)
  • 01-提问的艺术:如何让AI听懂“人话”
  • Day23| 39. 组合总和、40.组合总和II、131.分割回文串
  • 【47】MFC入门到精通——MFC编辑框 按回车键 程序闪退问题 ,关闭 ESC程序退出 问题
  • 泛型与类型安全深度解析及响应式API实战
  • python网络爬虫(第二步:安装浏览器驱动,驱动浏览器加载网页、批量下载资源)
  • 板凳-------Mysql cookbook学习 (十一--------12)
  • 20250717在荣品的PRO-RK3566开发板的Android13系统下解决点屏出现问题unsupport command data type: 217
  • x3CTF-2025-web-复现
  • 深度学习 -- Tensor属性及torch梯度计算
  • 计算机的网络体系及协议模型介绍
  • 外贸ERP软件有哪些?八大热门erp软件功能测评
  • centos中新增硬盘挂载文件夹
  • 河南萌新联赛2025第(一)场:河南工业大学(补题)
  • 亚远景科技助力长城汽车,开启智能研发新征程
  • 视频安全新思路:VRM视频分片错序加密技术
  • C++性能优化与现代工程实践:打造高效可靠的软件系统
  • C++性能优化
  • 91套商业策划创业融资计划书PPT模版
  • Java Stream API性能优化:原理深度解析与实战指南
  • PyTorch边界感知上下文神经网络BA-Net在医学图像分割中的应用
  • 多端协同的招聘系统源码开发指南:小程序+APP一体化设计
  • Android 实现:当后台数据限制开启时,仅限制互联网APN。
  • 小程序按住说话
  • 紫金桥跨平台监控组态软件 | 功能强大,支持复杂工业场景,与西门子 PLC 无缝兼容
  • 【Linux基础知识系列】第五十二篇 - 初识Linux的内置命令
  • 三十四、【扩展工具篇】JSON 格式化与解析:集成 Monaco Editor 打造在线 JSON 工具
  • 物联网主机在化工园区安全风险智能化管控平台中的应用