Linux的进程信号(下)

news2024/11/26 14:24:10

在这里插入图片描述

文章目录

  • 1. 阻塞信号
    • 1.1 信号其他相关常见概念
    • 1.2 在内核中的表示
  • 2. sigset_t
  • 3. 信号集操作函数
    • 3.1 sigprocmask
    • 3.2 sigpending
    • 3.3. 实例演示
  • 4. 信号的处理
    • 4.1. sigaction
    • 4.2 多个信号的处理
  • 5. 可重入函数
  • 6. volatile
  • 7. SIGCHLD信号

1. 阻塞信号

1.1 信号其他相关常见概念

1.实际执行信号的处理动作称为信号递达(默认,忽略,自定义捕捉)。
2.信号从产生到递达之间的状态,称为信号未决。
3.进程可以选择阻塞 (Block)某个信号。
4.被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞才执行递达的动作。
5.阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作

1.2 在内核中的表示

信号在内核中的示意图:
在这里插入图片描述
这里的pending就是前面说的位图,用来识别信号。handler就是函数指针数组,用来处理信号的。block(阻塞方法集)也是位图,它和pending的结构是一样的,几号bit位代表几号信号,表示该信号是否阻塞

每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。

在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号。因为进程仍有机会改变处理动作之后再解除阻塞。
SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。

如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理
Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。

2. sigset_t

从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储。sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。 阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。

3. 信号集操作函数

sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现。从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做
任何解释,比如用printf直接打印sigset_t变量是没有意义的。
在这里插入图片描述
1.函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。
2.函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位设为1,表示该信号集的有效信号包括系统支持的所有信号。
3.函数sigaddset是添加某个信号到这个信号集中。
4.函数sigdelset是删除某个信号。
5.函数sigismember是判断某个信号是否在信号集中

注意:在使用sigset_ t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号

3.1 sigprocmask

调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集,也就是block表)
在这里插入图片描述
假设当前的信号屏蔽字为mask,下表说明了how参数的可选值:
在这里插入图片描述
这里第二个参数set就是我们用户需要输入的,第三个参数是OS返回给用户的。如果oset是非空指针,则读取进程的当前信号屏蔽字(原来的)通过oset参数传出。

如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达

3.2 sigpending

在这里插入图片描述
这是一个输出型参数,读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。

3.3. 实例演示

在这里插入图片描述
当我们获取当前信号集的时候,我们把它打印出来。
在这里插入图片描述
打印函数我们要自己写一下:
在这里插入图片描述
我们先看一下运行结果:
在这里插入图片描述
我们可以看到一开始都是0,现在我们给这个进程发2号信号:
在这里插入图片描述
在这里插入图片描述
可以看到它并没有把对应的位置打印出来,直接结束进程了。那么我们就需要将2号信号给捕捉一下:
在这里插入图片描述
在这里插入图片描述
现在我们再看一下运行情况:
在这里插入图片描述
这里收到了信号,但是对应的pending信号集里还是没有,因为CPU很快就处理了这个信号,我们看不到。
所以,当我们发送2号信号时,让2号信号block:
在这里插入图片描述
这里可以看到,2号信号被阻塞了,但发送了2号信号,对应的pending信号集已经变成1了。

既然我们给它阻塞了,那么我们就需要恢复它。
在这里插入图片描述
运行结果如下:
在这里插入图片描述

4. 信号的处理

之前说过,进程处理信号,不是立即处理,是在合适的时候,那么到底是什么时候呢
当当前进程从内核态,切换回用户态的时候,进行信号的检测与处理

那么什么是内核态和用户态呢
在这里插入图片描述
前面我们说过,进程的会通过虚拟地址空间和页表把用户空间(3G)映射到物理内存。那么这里的页表叫做用户级页表,每一个进程,都有一份,但是大家的用户级页表都是不一样的

其实在OS还有一份页表叫做内核级页表,它映射的是内核空间(1G),这个页表所有进程共享一份

我们知道无论进程怎么切换,我们都可以找到内核的代码和数据,前提是你要有权利访问。

那么我们怎么知道当前进程如何具备权利,访问这个内核页表,乃至访问内核数据呢
要进行身份切换,进程如果是用户态,那么只能访问用户级页表,进程如果是内核态,可以访问内核级和用户级页表

那么怎么知道当前进程是用户态还是内核态呢
CPU内部有对应的状态寄存器,可以用bit位标识当前进程的状态0代表内核态,3代表用户态

什么时候进入到内核态呢
系统调用的时候或者时间片到了,进程间切换

我们的程序会无数次直接或者间接的访问系统软硬件资源(管理者是OS),本质上,你并没有自己去操作这些软硬件资源,但是通过了OS,从而无数次的陷入内核(1.切换身份,2.切换页表),然后调用内核的代码,从而完成访问动作,把结果返回给用户(1.切换身份,2.切换页表)

举个例子:
在这里插入图片描述
在我们的代码中写了一个系统调用open,当执行到open时,就会进入内核态,当执行完open函数时,OS会检测进程的PCB,检测PCB里面的信号(block&&pending),假设block为0,pending为1,那么就会去执行对应的handler,假设handler是我们自己定义的方法:
在这里插入图片描述
但是当我们执行完自己的handler方法时,它会直接去执行open后面的代码吗
答案是:不会。原因是:我们是执行open函数时遇到信号了,当处理完信号后,open并没有返回值,所以我们不能直接执行open后面的代码。而是先回到内核态通过处理再回到用户态执行open后面的代码。

那么这里是以谁的身份去执行这个handler呢
可能大家会认为是内核身份,虽然内核态可以去执行,但是它不愿意。这里只能用户级身份来执行。原因是:这个handler是我们自己写的,如果在里面我们写了一段恶意代码,让内核去执行,那么就可能造成问题。

完整过程
在这里插入图片描述

4.1. sigaction

在这里插入图片描述
sigaction函数可以读取和修改与指定信号相关联的处理动作,也就是获取和修改handler表。调用成功则返回0,出错则返回- 1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体

这个函数和signal功能是差不多的,但是signal比较简单。

那么我们先看一下sigaction的结构体
在这里插入图片描述
我们暂时先考虑这两个参数,其它的暂时不考虑。

代码演示:
在这里插入图片描述
这里我们设置了一个自定义捕捉动作。
在这里插入图片描述
当收到信号时,完成了自定义捕捉动作。
将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字。这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止

举个例子:
在这里插入图片描述
运行结果:
在这里插入图片描述
首先执行的是主函数里面的sleep。
在这里插入图片描述
当我们发送2号信号时,它就会一直处理。
在这里插入图片描述
当我们再次发送2号信号,它就会屏蔽了。

那么这个sigaction里面的maks到底是什么呢
如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字
在这里插入图片描述
运行结果:
在这里插入图片描述
现在我们想要结束进程,我们可以使用kill -9,也可以使用killall 进程名
在这里插入图片描述
在这里插入图片描述

4.2 多个信号的处理

方法一:
在这里插入图片描述
这样就可以调用同一个函数来执行不同的方法。然后我们把捕捉方法写好就行:
在这里插入图片描述
测试结果:
在这里插入图片描述
方法二:
在这里插入图片描述
这里可以使用一个vector来把所有方法push进去,但是我们需要自己把所有函数捕捉方法写好。所以我们更推荐 unordered_map。
在这里插入图片描述

5. 可重入函数

在这里插入图片描述
这里是一个单链表,在main函数里面调用插入函数,并且在信号捕捉里也调用了这个插入函数。
在这里插入图片描述
我们执行main函数里面的插入时,假设时间片到了,只完成了第一步,没有完成head=p,那么此时用户就会从用户态切换成内核态。那么在内核态就会检测信号,然后就会执行我们的handler方法。
在这里插入图片描述
那么handler方法没人打扰,就会执行完。
在这里插入图片描述
执行完后,再去我们自己的代码执行head=p。
在这里插入图片描述
insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入

insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为不可重入函数。反之,如果一个函数只访问自己的局部变量或参数,则称为可重入函数

如果一个函数符合以下条件之一则是不可重入的:
1.调用了malloc或free,因为malloc也是用全局链表来管理堆的。
2.调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构

6. volatile

该关键字在C++当中我们已经有所涉猎,今天我们站在信号的角度重新理解一下:
在这里插入图片描述
在没有收到2号信号的时候一直在循环里判断,当收到2号信号的时候,就退出循环。
在这里插入图片描述
这是正常的理想状态下。
在这里插入图片描述
这里O2是告诉编译器进行优化。
在这里插入图片描述
还是同样的代码,但是现在循环并没有退出。

这里的优化是把flags的值放到了CPU的寄存器中,在逻辑计算的时候,就不会从内存中取数据,而是直接从CPU寄存器里取数据。那么为了防止这种情况,我们可以加个volatile。
在这里插入图片描述
在这里插入图片描述
又可以正常退出了。

volatile 作用:保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量的任何操作,都必须在真实的内存中进行操作

7. SIGCHLD信号

在这里插入图片描述
在子进程退出的时候,自动给父进程发送SIGCHLD信号

证明如下
在这里插入图片描述
我们对子进程暂停或者退出,看父进程能否捕捉信号。
在这里插入图片描述
现在我们暂停子进程:
在这里插入图片描述
我们再恢复子进程:
在这里插入图片描述
我们再删除子进程:
在这里插入图片描述
我们可以看到以上三种情况,父进程都可以收到SIGCHLD信号。

那么子进程给父进程发送信号有什么作用呢
在这里插入图片描述
这是我们之前写的一个父进程等待子进程,但是都是父进程要自己主动等待。
在这里插入图片描述
现在我们可以让父进程去做自己的事情,不主动等待:
在这里插入图片描述
我们让父进程做自己的事情,并且捕捉信号。
在这里插入图片描述
我们可以看到:父进程和子进程一开始都在运行,然后子进程退出的时候,父进程一直做自己的事情,并且自动等待成功。

但是这个代码有一些bug
在这里插入图片描述
如果我们一次性创建多个进程,当进程退出时给父进程发送信号,就可能造成多个进程同时退出。而Linux处理一次信号时,其它信号可能就被阻塞了,那么其它进程就不会被等待回收,一直就是僵尸进程。
在这里插入图片描述
我们循环不断waitpid去等待子进程,当所有子进程都没了,就退出循环。

但是这里还存在一些问题:
在这里插入图片描述
这里的意思是:前8个运行5秒,后2个子进程运行20秒。这种情况会造成后两个子进程在运行时,父进程会在waitpid被阻塞等待。
在这里插入图片描述
那么父进程就不能执行自己的代码了。我们需要改成WNOHANG
在这里插入图片描述
当参数为WNOHANG时,id等于0的时候,说明子进程还有没退出完的。

事实上,由于UNIX 的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调用sigaction或者signal将SIGCHLD的处理动作置为SIG_IGN。这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。系统默认的忽略动作和用户用sigaction函数自定义的忽略通常是没有区别的,但这是一个特例。此方法对于Linux可用,但不保证在其它UNIX系统上都可用
在这里插入图片描述
设置过后,父进程就不需要任何处理了,OS就会把所有僵尸状态的子进程都给忽略掉。

子进程退出的时候,默认的信号处理就是忽略,那么调用signal/sigaction设置为SIG_IGN,,意义在哪里呢

SIG_IGN手动设置,让子进程退出,不要给父进程发送信号了,并且自动释放

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

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

相关文章

java基于springboot自来水收费缴费系统+jsp

本次设计拟采用JAVA技术,对乡镇自来水收费系统的功能需求进行了全面分析,从模块功能定义、前后端交互技术、数据库及编程语言的选择、系统调试及测试、功能完善和改进等方面进行设计,解决了从用户新装、抄表、计费、收费、复查、换表、发票管…

Pyside6-第三篇-QToolButton一个奇葩的按钮

今天是Pyside6的第三篇内容。一起来看另一个按钮。 QToolButton。 from PySide6.QtWidgets import QToolButton, QWidget, QApplicationapp QApplication([])win QWidget() win.setWindowTitle("QToolButton按钮")btn QToolButton(win) btn.setText("触发&qu…

Cell揭秘--慢性压力如何导致肠道炎症

大脑产生的信号传导到肠道神经细胞,导致炎症化学物质的释放。 溃疡性结肠炎患者的肠道组织(人工着色)。图片来源: Steve Gschmeissner/Science Photo Library 心理压力会加重某些肠道疾病引起的肠道炎症。现在,科学家们找到了原因…

初学QT(Day05)

继续第四天的demo 总结了之前的经验教训,我重新开一个项目项目,先给出demo的结果吧,第一张是第一次写的demo,第二张图是成品的demo 结果还是比较满意的,虽然过程中有遇到的问题不是我自己独立解决的。。。相比于第…

GPT-4能否取代数据分析师?达摩院的初步实验为你解答~

深度学习自然语言处理 原创作者 | 刘嘉玲 最近,数据分析师圈子大家在讨论GPT-4对他们的工作有什么影响:是替代还是辅助?个人认为GPT-4可以帮助我提高工作效率和质量。 要成为一名高级的数据分析师,需要经过长期的学习和实践&#…

无代码玩转GIS应用,我也在行【文末送书】

您好,我是码农飞哥(wei158556),感谢您阅读本文,欢迎一键三连哦。💪🏻 1. Python基础专栏,基础知识一网打尽,9.9元买不了吃亏,买不了上当。 Python从入门到精通…

jQuery-图片跟随

<!DOCTYPE html > <html> <head> <meta http-equiv"Content-Type" content"text/html; charsetUTF-8"> <title>图片跟随 </title> <style type"text/css"> body { text-align: center; …

【配电网重构】基于改进二进制粒子群算法的配电网重构研究(Matlab代码实现

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

使用FPGA进行 AI 火灾定位-FirAI

部署在 FPGA 上加速的 AI 火灾侦查。助力消防人员快速应对火灾事故~ 绪论 问题&#xff1a;近年来&#xff0c;不断增加的城市人口、更复杂的人口密集建筑以及与大流行病相关的问题增加了火灾侦查的难度。因此&#xff0c;为了增强消防人员对火灾事件的快速反应&#xff0c;安装…

delmia机器人建模与装配

1 可以用catia中的模型或其他三维建模软件中的模型转化为step格式即可 2 在demlia中打开 3 打开单个零件保存为cgr格式 对机械臂所有零件都做同样的转化 4 新建装配设计&#xff0c;并导入带有坐标的零件 将转化后的零件都选中导入即是装配好的 5 将模式修改为device buildin…

用户行为数据采集:常见埋点方案优劣势对比及选型建议

数据采集是大数据的基石&#xff0c;用户在使用App、微信小程序等各种线上应用产生的行为&#xff0c;只有通过埋点才能进行采集。没有埋点&#xff0c;数据分析决策、数据化运营都是无源之水&#xff0c;巧妇难为无米之炊。但很多时候&#xff0c;“埋点”两个字却成了C端产品…

STM32——关于NVIC讲解及标准库应用(基础篇)

简介&#xff1a; NVIC是指STM32中的中断控制器&#xff08;Nested Vectored Interrupt Controller&#xff09;。中断是STM32中的重要机制&#xff0c;通过中断可以实现异步事件处理。NVIC提供了灵活、高效、可扩展的中断处理机制&#xff0c;支持多级优先级、多向中断、嵌套向…

深入理解网络协议

hi 大家好&#xff0c;之前带小伙伴&#xff0c;一起复习了一遍网络协议&#xff0c;对网络协议的核心知识进行梳理&#xff0c;希望大家早日掌握这些核心知识&#xff0c;打造自己坚实的基础&#xff0c;为自己目标慢慢积累&#xff0c;厚积薄发。 详细点击查看-> 极客星球…

傅一平:一文讲透ERP的下一代架构!

”5月22日&#xff0c;华为宣布仅用15小时便完成了全球88家子公司MetaERP系统的切换。这也意味着华为MetaERP系统研发取得胜利&#xff0c;成功摆脱外国供应商断供停服威胁&#xff0c;实现该系统的全栈自主可控。“ 自己最近对ERP下一代架构有了兴趣&#xff0c;原因有四个&am…

阿里拆成1+6+N,中台还搞不搞了?

&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3; &#x1f38d;大家好&#xff0c;我是慕枫 &#x1f38d;前阿里巴巴高级工程师&#xff0c;InfoQ签约作者、阿里云专家博主&#xff0c;一直致力于用大白话讲解技术知识 &#x…

推荐 6 个上周 火火火 的开源项目

本期推荐开源项目目录&#xff1a; 1. ChatGPT 网页应用&#xff08;AI&#xff09; 2. AI 换脸&#xff08;AI&#xff09; 3. API 调用 Midjourney 进行 AI 画图&#xff08;AI&#xff09; 4. 如何使用 Open AI 的 API&#xff1f;&#xff08;AI&#xff09; 5. 中华古诗词…

写在2023年乐夏前

&#xff08;1&#xff09;白衬衣的少年 勇敢的你 站在这里 脸庞清瘦却骄傲 &#xff08;2&#xff09;来啊&#xff0c;一起摇摆啊 这首歌发表在2016年。那时候的他们已经功成名就&#xff0c;彭磊却还能写出这样的词。 纸醉金迷不应该是&#xff1a;防晒霜、付税单、玫瑰金、…

Python海龟有了新技能,这回画了个印度美女。Python海龟洪水填充命令fill的用法

Python海龟有了新技能&#xff0c;这回画了个印度美女。看官想一想&#xff0c;如果要填充圆环区域&#xff0c;该如何填充呢&#xff1f;Python的海龟模块本质是对凸多边形的填充&#xff0c;对于凹多边形的填充无法胜任。 真正的Python海龟绘图在这&#xff0c;视频里还有点…

AI绘画真的是太神奇!

最近会员群的同学一直研究AI绘画&#xff0c;AI技术真的太神奇了&#xff0c;以前是插画师的看家本领&#xff0c;现在通过AI技术一下子让平头老百姓也能有这样的技能&#xff01;好像路痴有了导航也能开车了。最近在研究如何利用AI做卡通头像&#xff0c;发现这个不仅好玩&…

深度图解 Redis Hash(散列表)实现原理

1. 是什么 Redis Hash&#xff08;散列表&#xff09;是一种 field-value pairs&#xff08;键值对&#xff09;集合类型&#xff0c;类似于 Python 中的字典、Java 中的 HashMap。一个 field 对应一个 value&#xff0c;你可以通过 field 在 O(1) 时间复杂度查 field 找关联的…