Linux 操作系统 --- 信号

news2024/11/15 1:35:14

序言

 在本篇内容中,将为大家介绍在操作系统中的一个重要的机制 — 信号。大家可能感到疑惑,好像我在使用 Linux 的过程中并没有接触过信号,这是啥呀?其实我们经常遇到过,当我们运行的进程当进程尝试访问非法内存地址时,我们的进程会被中断,这是因为操作系统向该进程发送了中断信号。
Linux 操作系统离不开信号机制,在这篇文章中,让我们走进信号,了解信号的从哪里来,又到哪里去。


1. 信号的概念

1.1 定义

 信号是操作系统向进程发送的一种通知,表示某个特定事件已经发生。在Unix、类Unix 以及其他系统中,信号被广泛使用。

1.2 特点

 信号具有如下的特点:

  • 异步性:信号的产生对进程来说是异步的,即 进程无法预知信号何时到来
  • 通知机制:信号是一种 软件中断(由软件程序触发的中断方式),用于中断进程的正常执行流程,使其处理特定事件。
  • 进程间通信:虽然信号主要用于异常处理和系统调试,但也可以用于进程间的基本通信。

1.3 种类

 在 Linux 系统下,使用指令:kill -l 即可查看所有的信号:
在这里插入图片描述

信号是使用宏定义的,每个信号前面的数字,就是该信号宏对应的值。

前 31 个信号为常规信号,其余为实时信号。在本篇文章中,我们主要讨论前 31 个常规信号。

 你也可以使用指令: man 7 signal 查看每一个详细信息:
在这里插入图片描述

补充知识点:Core && Term

 在描述信号的字段中,有一个叫做 Action 的特征,他的值大多都是 Core Term 这是什么呢?

Term

termterminate 的缩写,表示默认动作是终止进程。当进程接收到一个默认动作为 term 的信号时,进程会被立即终止。

Core

core 表示 默认动作是终止进程并生成一个核心转储(core dump)文件。核心转储是一个包含进程在终止时的内存映像的文件,它对于调试程序非常有用,因为它 提供了进程终止时的状态信息
 咦?就比如,SIGSEGV 段错误 当我的程序非法访问被终结时,被没有产生传说中的核心转储文件呀?这是因为你的服务器默认关闭了该功能,使用指令 ulimit -a 查看:
在这里插入图片描述
现在我们使用指令 ulimit -c 4096 开启该功能:
在这里插入图片描述

现在我们运行下一段程序:

   8 int main()
   9 {
  10 
  11     int *ptr = NULL;
  12     *ptr = 1;
  13     
  14     return 0;
  15 }

程序不负众望地报错并退出了,产生了一个文件:
在这里插入图片描述
这个文件可以干嘛呢?当我们的程序出现异常时,相当该文件保存了案发现场,具体的用法是:

  • 首先使用 gdb 调试你的程序:
    在这里插入图片描述
  • 之后输入指令 core-file your_core
    在这里插入图片描述

可以看到,直接就复原了事故现场。

区别
  • 进程终止:term 信号会终止进程,但 不生成核心转储文件term 信号通常是用于请求进程正常终止的情况。
  • 调试信息:core 信号不仅会终止进程,还会 生成核心转储文件,这包含了进程的内存映像、寄存器状态、堆栈跟踪等信息,用于调试目的。

2. 信号的产生

 信号是从哪里产生的呢?虽然最后都是操作系统来执行对一个进程发送信号,但是是谁告诉操作系统这样做的呢?

2.1 用户操作 — kill 指令

 当我们运行一个程序时,可以通过指令 kill 来让操作系统对该进程发送相应的信号,就比如,我们可以手动发送 SIGKILL 9号 信号将该进程终结,这里有一个程序:

 1 TestSig1.cc                                                                                                                                                                                                  X 
   1 #include <iostream>
   2 #include <unistd.h>
   3 
   4 
   5 int main()
   6 {
   7     while(true)
   8     {
   9         std::cout << "I am Running, my pid is " << getpid() << std::endl;
  10         sleep(1);
  11     }
  12     return 0;
  13 }

该程序会每秒打印相应的内容,现在我们可以使用相关的指令 kill -9 [pid] 来杀掉该进程:
在这里插入图片描述
可以看到该进程被杀掉了!!!

2.2 用户操作 — 按键操作

 我们也可通过按键来让操作系统发送相关的信号,就比如我们平时终止一个进程的方式更多的是通过键盘按键,就比如 ctrl + c
在这里插入图片描述
其实这个按键对应的就是 3 号信号 SIGQUIT

2.3 用户操作 — 系统调用

 操作系统提供一个系统调用 int kill(pid_t pid, int sig); 该函数你可以想指定进程发送信号:

  • pid: 表示要发送信号的进程 ID
  • sig: 表示要发送的信号
  • 返回值:成功返回 0 ,失败返回 -1 ,错误码被设置

还有一个函数是 int raise(int sig);,该函数是向当前进程发送指定信号,简单来说,相当于简化的 kill => kill(getpid(), int sig);

2.4 触发软件条件

 在之前管道的学习中,我们了解到如果 读端被关闭了,写端一直再写,那么操作系统就会认为这是一个坏掉的管道,就会发送 13 号信号 SIGPIPE 终止该进程,这就是触发了某种软件条件。
 现在,在这里先向大家介绍几个非常重要的函数:

signal 函数

 该函数允许程序员定义当特定信号发生时,程序应该如何响应, 简单说,这个函数用于捕获特定信号,然后执行指定操作的sighandler_t signal(int signum, sighandler_t handler);:

  • signum:指定要处理的信号类型。注意,SIGKILLSIGSTOP 这两个信号不能被捕获、阻塞或忽略。
  • handler:指定信号的处理方式。它可以是一个函数指针,指向一个用户定义的信号处理函数;也可以是 SIG_IGN,表示忽略该信号;或者是 SIG_DFL,表示采用信号的默认处理方式。
  • 返回值:成功时,signal 函数返回之前为该信号设置的信号处理函数的指针。如果之前没有为该信号设置过处理函数,则返回 SIG_DFL。失败时,返回 SIG_ERR,并设置 errno 以指示错误原因。

看着描述这么多,其实用起来不复杂,比如,现在我要捕获 2 号信号 SIGINT,他的默认操作是退出,现在我不想要推出,想要执行我的逻辑:

 1 TestSig1.cc                                                                                       X 
   1 #include <iostream>
   2 
   3 #include <unistd.h>
   4 #include <sys/types.h>
   5 #include <signal.h>
   6 
   7 void signal_handle(int signum)
   8 {
   9     std::cout << "I got you signal: " << signum << std::endl;
  10 }
  11 
  12 int main()
  13 {
  14 
  15     // 2号信号的捕获
  16     signal(2, signal_handle);
  17 
  18     while(true)
  19     {
  20         std::cout << "I am Running, my pid is " << getpid() << std::endl;
  21         sleep(1);
  22     }
  23     return 0;
  24 }

现在我们使用 ctrl + c 已经不能终止该进程了:
在这里插入图片描述

你也可将 signal(2, signal_handle); 中的函数换成 SIG_IGN 这样就会忽略该信号。

现在我有个想法就是将全部信号都捕获,在写个死循环,是不是就没有人把我停下来了!!!我们能想到的,人家肯定也想到了,规定 9 号信号 SIGKILL 和 19 号信号 SIGSTOP 这两个信号不能被捕获、阻塞或忽略。保证系统的稳定性和管理员的控制权。

alarm 函数

 大家为了早起都设置过闹钟吧,闹钟的作用就是时间一到就提醒我们执行某件任务。在 Linux 中的闹钟 alarm 也是一样的,我们设置一个定时,当时间一到执行某项任务,unsigned int alarm(unsigned int seconds);

  • seconds:定时器应该等待的秒数。如果 seconds 是 0,则任何当前设置的定时器都会被取消(你可以同时设置多个闹钟),但不会发送 SIGALRM 信号。
  • 返回值:如果之前已经设置了定时器,alarm 函数 返回之前设置的剩余时间(秒),直到定时器到期。如果之前没有设置定时器,则返回 0。

alarm 定时器到期时,会向进程发送 SIGALRM 信号,终止进程:

12 int main()
  13 {
  14 
  15     // 设置一个闹钟,执行默认操作
  16     alarm(2);
  17 
  18     while(true)
  19     {
  20         std::cout << "I am Running, my pid is " << getpid() << std::endl;
  21         sleep(1);
  22     }
  23     return 0;
  24 }

两秒之后,进程自动终止:

 但更多情况下,我们想要闹钟解释后执行我们的逻辑,而不是终止进程,那咋办呢? 捕获该信号,自定义处理信号,这就需要我们上面说的 signal 函数了:

   7 void signal_handle(int signum)
   8 {
   9     std::cout << "Your alarm clock is ringing." << std::endl;
  10 }
  11 
  12 int main()
  13 {
  14 
  15     // 设置一个闹钟,执行默认操作
  16     alarm(2);
  17     // 捕获闹钟信号
  18     signal(SIGALRM, signal_handle);
  19 
  20     while(true)
  21     {
  22         std::cout << "I am Running, my pid is " << getpid() << std::endl;
  23         sleep(1);
  24     }
  25     return 0;
  26 }

现在闹钟时间到了,就不会终止进程啦!但是,我还有一个疑问,你这个闹钟只能执行一次呀,之后就失效了,我想要一个一直生效的定时任务,怎么做到呢?当捕获并执行自定义函数时再设置一个闹钟不就好啦:

   7 void signal_handle(int signum)
   8 {
   9     std::cout << "Your alarm clock is ringing." << std::endl;
  10     alarm(2);
  11 }

这样就得到一个持续的定时任务啦:
在这里插入图片描述

 在这里的闹钟就是一个触发了软件条件(倒计时),从而产生信号发送给进程!

2.4 硬件异常

段错误

 在我们的程序中,很可能涉及到 段错误(非法内存访问),具体触发错误的细节如下:

  • 现代计算机使用内存管理单元(MMU)来管理内存。MMU 负责 将虚拟地址(程序使用的地址)映射到物理地址(实际内存地址)
  • 当我们尝试访问一个地址时,MMU 尝试将虚拟地址翻译为物理地址,并检查该虚拟地址对应的页表项,以确定是否有权限访问该地址,以及地址是否有效
  • CPU 发现该块地址是 无效的,或者是不具有写权限的,或者是无权限访问的,将触发异常
  • 操作系统向该进程发送 SIGSEG 的信号

这就是简单的硬件异常触发流程。


3. 信号的保存

 现在我们已经基本了解了信号是从哪里来的,那么信号被一个进程接受过后,是以什么形式存在于进程当中呢?

 在介绍信号的保存之前,希望大家记住这几个概念:

  • 实际执行信号的处理动作称为信号递达(Delivery)
  • 信号从产生到递达之间的状态,称为信号未决(Pending)。

 信号的信息被保存在一个进程的 task_struct 中:
在这里插入图片描述
我们来好好的介绍这 3 个结构:

3.1 block 位图

Block 位图用于指示哪些信号当前被进程 阻塞。如果一个信号在 Block 位图中对应的位被设置(为 1 ),那么即使该信号已经到达,它也不会被立即处理,而是会 保持在未决状态,直到进程解除对该信号的阻塞。

3.2 pending 位图

Pending 位图(通常不是直接暴露给用户的,而是作为进程控制块 task_struct 的一部分)用于 跟踪哪些信号已经到达进程但尚未被处理。每个位代表一个信号,如果该位被设置(通常为 1 ),则表示对应的信号已经到达且处于 未决状态

3.3 handler 函数指针数组

 用户可以通过系统调用来设置特定信号的处理函数。当用户为某个信号注册了一个自定义的处理函数时,操作系统就会将该函数的地址存储在 handler 表中对应信号编号的位置。如果用户没有为某个信号设置自定义处理函数,那么当该信号发生时,操作系统就会 调用默认的处理函数

 所以,我们总结一下,当一个信号传递给进程时,操作系统会将 pending 表中该信号对应的值置 1,如果 block表中的对应的值 也是 1,代表该信号被阻塞,不会被立即处理,直至解除阻塞;当解除阻塞或者一开始就不是阻塞状态的话,就会执行 handle
表中该信号对应的函数操作。

3.4 验证结论

 现在我们准备验证我们的想法,我们先阻塞一个信号,然后发送该信号,查看是否执行相关操作,在解除对该信号的阻塞,再次观察现象:

在这里会涉及到对信号集的操作,大家可以简单理解为 对信号集进行的对某个信号的阻塞操作最终会保存到阻塞表中 ,在这里就不具体说明操作了,感兴趣的小伙伴,我找了一篇比较好的文章 👉信号集操作指南。

6 // 自定义函数
   7 void signal_handler(int signum)
   8 {
   9     std::cout << "Recived signal SIGINT!!!" << std::endl;
  10 }
  11 
  12 int main()
  13 {
  14     // 捕获信号
  15     signal(SIGINT, signal_handler);
  16 
  17     sigset_t sigset;
  18     // 初始化信号集
  19     sigemptyset(&sigset);
  20     // 添加指定信号到信号集
  21     sigaddset(&sigset, SIGINT);
  22 
  23     // 阻塞该信号
  24     if(sigprocmask(SIG_BLOCK, &sigset, NULL) == -1)
  25     {
  26         perror("sigprocmask");
  27     }
  28 
  29     std::cout << "SIGINT is blocked. Try pressing Ctrl+C after 5s!!!\n" << std::endl;
  30     sleep(5);
  31 
  32     // 解除阻塞
  33     if(sigprocmask(SIG_UNBLOCK, &sigset, NULL) == -1)
  34     {
  35         perror("sigprocmask");
  36     }
  37 
  38     std::cout << "SIGINT is unblocked. Try pressing Ctrl+C!!!\n" << std::endl;
  39 
  40     while(true)
  41     {
  42         sleep(1);
  43     }
  44 }

在这里插入图片描述

第一次我们按 ctrl + c 没什么反应,过了 5s 后,函数自动被执行,可以看出我们的结论是正确的。


4. 信号的处理

 现在我们知道信号哪里来的了,也知道保存在哪里了,现在我们来看看信号的处理方式。

4.1 执行默认方式

 对于没有为其注册信号处理函数的信号,进程会执行该信号的默认操作。就比如,SIGTERM 信号的默认操作是请求进程终止,而 SIGSEGV(段错误)信号的默认操作是生成core文件并终止进程。

4.2 调用信号处理函数

 如果进程为某个信号注册了信号处理函数(也称为信号处理器,上面代码中的 signal_handler 函数),那么当该信号到达时,内核会暂停进程的正常执行流程,转而调用该处理函数。

4.3 忽略信号

  进程可以选择忽略某些信号。这意味着当这些信号到达时,进程不会执行任何特别的操作,而是继续执行其当前的代码路径。然而,需要注意的是,并非所有信号都可以被忽略。例如,SIGKILL和SIGSTOP等信号是不能被忽略的

4.4 阻塞信号

 进程可以选择屏蔽某些信号,以 避免在关键操作期间接收到这些信号。通过调用sigprocmask 等系统调用,进程可以设置其信号屏蔽字,以决定哪些信号能够传递到进程中。被屏蔽的信号将保持在未决状态,直到屏蔽被解除后才会被处理。

4.5 阻塞和忽略的区别

 这两个概念相当容易混淆,从定义上来说:

  • 阻塞是指 进程选择性地阻止某些信号的传递。当这些被阻塞的信号发生时,它们会被内核记录下来(处于未决状态),但不会立即执行信号的处理函数或执行默认操作。
  • 忽略是指进程对收到的某些信号 不执行任何操作,即不调用处理函数也不执行默认操作,而是简单地丢弃这些信号。

大家可以这样理解:信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。 前者是处于未决的状态,后者是被递达后选择了忽略,不做其他处理。


总结

 在这篇文章中介绍了信号的概念,也介绍了信号从哪里来,到哪里去,被接受处理的过程,希望大家有所收获😁。

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

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

相关文章

NC 数组中只出现一次的两个数字

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 一个整型数组…

Apache--简介与基本使用

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 一、Apache简介 Apache HTTP Server&#xff08;在Red Hat发行版中俗称Apache或httpd&#xff09;是由Apache Software Foundation在Apache License…

windows bat脚本基础指令详解

pause暂停批处理的执行并在屏幕上显示"请按任意键继续…"echo显示指令&#xff0c;会把需要显示的内容展示出来。echo off在此语句后所有运行的命令都不显示命令行本身&#xff0c;但是本身的指令是会显示出来的。不显示本行命令行call调用另一个批处理文件&#xff…

Ricardo Milos

目录 一、题目 二、思路 三、payload 四、思考与总结 一、题目 <!-- Challenge --> <form id"ricardo" method"GET"><input name"milos" type"text" class"form-control" placeholder"True" va…

【C++STL详解(十一)】map/set/multimap/multiset的介绍与使用

目录 一、关联式容器 二、键值对 三、set 介绍 简单使用 1.构造 2.相关迭代器 3.容量 4.修改 四、multiset 五、map 介绍 使用 1.定义的方式 2.迭代器相关 3.容量与operator【】(重点) 4.修改 小总结&#xff1a; 六、multimap 一、关联式容器 在CSTL中…

【学习笔记】A2X通信的协议(十三)- 消息功能定义和内容

目录 11 消息功能定义和内容 11.1 概述 11.2 通过PC5信令消息进行的A2X通信 11.2.1 A2X直接链路建立请求 11.2.1.1 消息定义 11.2.1.2 目标用户信息 11.2.1.3 密钥建立信息容器 11.2.1.4 Nonce_1 11.2.1.5 KNRP-sess ID的最高有效位&#xff08;MSB&#xff09; 11.2…

替代 SMR 算法!两步孟德尔随机化方法 TWMR 与 revTWMR 整合xQTL+GWAS数据分析基因表达与疾病的关联

全基因组关联研究&#xff08;GWAS&#xff09;是研究大型队列中基因型与表型关系的重要工具。GWAS的已知局限性主要在于从与致病变异相关的连锁不平衡区域中识别生物学机制&#xff0c;而无法直接获得基因层面的表型关联。为了解决这个问题&#xff0c;基于转录组关联研究&…

C语言——函数专题

1.概念 在C语言中引入函数的概念&#xff0c;有些翻译为子程序。C语言中的函数就是一个完成某项特定任务的一小段代码&#xff0c;这个代码是有特殊的写法和调用方法的。一般我们可以分为两种函数&#xff1a;库函数和自定义函数。 2.库函数 C语言国际标准ANSIC规定了一些常…

Docker 容器运行时如何实现与宿主机的目录挂载

虽然容器启动前挂载目录相对简单,只需要在启动容器的时候通过-v 参数即可轻松实现,但对于已运行的容器进行目录挂载则稍显复杂。本教程将深入讲解如何在不中断容器运行的情况下,为已运行的Docker容器配置目录挂载,实现灵活的数据共享与持久化策略。 一、环境准备 为了本次实…

ffmpeg采用gpu加速增加水印

1.环境需要 系统 windows10 ffmpeg&#xff0c;ffprobe 字体文件 python3以上版本 2.环境配置 从官网上下载ffmpeg版本https://github.com/BtbN/FFmpeg-Builds/releases&#xff0c;这里我用的是这个&#xff0c;解压之后里面包含ffmpeg&#xff0c;ffprobe&#xff0c;f…

使用Kernel Memory进行RAG评估:AI助力企业知识管理新突破

在现代企业知识管理中&#xff0c;随着业务的不断发展和扩展&#xff0c;各种文档和数据呈现爆炸式增长。为了有效且高效地管理这些知识&#xff0c;企业通常会导入大量文档。然而&#xff0c;当涉及到对文档切片质量和回答准确度的判断时&#xff0c;传统的人工方法显得既费时…

复现dom破坏案例和靶场

目录 1.dom型xss平台 第一关 Ma Spaghet !: &#xff08;1&#xff09;&#xff1a;当get参数中存在somebody时&#xff0c;h2回显 &#xff08;2&#xff09;&#xff1a;当get参数中不存在somebody时&#xff0c;h2回显 第二关Jefff&#xff1a; 第三关&#xff1a;Ugan…

【前端面试】挖掘做过的nextJS项目(中)

https://blog.csdn.net/weixin_43342290/article/details/141170360?spm1001.2014.3001.5501文章浏览阅读105次。需求:快速搭建宣传官网1.适应pc、移动端2.基本的路由跳转3.页面渲染优化4.宣传的图片、视频资源的加载优化5.seo优化全栈react web应用、tailwind css原子工具的支…

动态规划篇--代码随想录算法训练营第三十二天|343. 整数拆分,96.不同的二叉搜索树,01背包理论,01背包优化

343. 整数拆分 题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 讲解视频&#xff1a; 动态规划&#xff0c;本题关键在于理解递推公式&#xff01;| LeetCode&#xff1a;343. 整数拆分 题目描述&#xff1a; 给定一个正整数 n &#xff0c;将其拆分为 k …

c++47 二级指针

二级指针的输入和输出模型 指针的输入&#xff1a;主调函数分配内存 指针输出 &#xff1a;被调用函数分配内存 指针做输入第一种模型 #define _CRT_SECURE_NO_WARNINGS #include <stdlib.h> #include <string.h> #include <stdio.h>// 二级指针做输…

《计算机组成原理》(第3版)第8章 CPU的结构和功能 复习笔记

第8章 CPU的结构和功能 一、CPU的结构 &#xff08;一&#xff09;CPU的含义 CPU实质包括运算器和控制器两大部分。 对于冯诺依曼结构的计算机而言&#xff0c;一旦程序进入存储器后&#xff0c;就可由计算机自动完成取指令和执行指令的任务&#xff0c;控制器就是专用于完成…

Python爬虫图片:从入门到精通

在数字化时代&#xff0c;图片作为信息传递的重要媒介之一&#xff0c;其获取和处理变得越来越重要。Python作为一种功能强大且易于学习的编程语言&#xff0c;非常适合用来编写爬虫程序&#xff0c;帮助我们自动化地从互联网上获取图片资源。本文将从基础到高级&#xff0c;详…

CTF密码学小结

感觉没啥好总结的啊 基础的永远是RSA、流密码、哈希、对称密码、古典密码那一套&#xff08;密码学上过课都会&#xff09;&#xff0c;其他的就是数论的一些技巧 似乎格密码也很流行&#xff0c;以及一些奇奇怪怪的性质利用也很多 1、random设置种子后随机的性质&#xff1a…

【LiteX】【开发板】【BoChenK7】使用Python开发FPGA【Hello World、LED点灯、Memory测速、替换BIOS】

目录 开发板介绍下载仓库工程设计工程构建构建流程 工程测试Hello WorldLED点灯Memory测速替换BIOS 开发板信息 开发板介绍 手头目前只有一个购买的BoChenK7开发板&#xff0c;后续会用它来进行LiteX FPGA SoC的构建 测试可能会包括&#xff1a; LED&#xff1a;本篇文章 DDR …

【区块链+乡村振兴】“蜜链盟”乡村振兴基层治理数字化平台 | FISCO BCOS应用案例

在国家战略政策推动和新一代信息化发展应用的合力之下&#xff0c;数字乡村是互联网化、信息化和数字化在农业农村经 济社会发展中的表现。为进一步加强乡村基层治理&#xff0c;根据《中共海南省委农村工作领导小组办公室海南省农业农 村厅关于在我省乡村治理中推广运用积分制…