Linux——信号(3)

news2025/1/11 20:03:48
经过前两部分的介绍,我们现在已经认识了信号是如何产生的,并且知道无
论信号是如何产生的,最终只能由操作系统来对特定进程发送信号,而发送
信号其实也就是写信号,往内核数据结构(pending表)中写。我们也认识了
信号的几种状态,也直到了关于信号的一些操作,但是我们之前说过,当信
号处于信号未决状态时,进程会在合适的时候对信号进行处理,那么这个“合
适”的时候是什么时候呢?这就是我们今天要谈论的主要问题。

1. 信号的处理和捕获

我们现在知道了,信号的处理方式有三种:默认处理方式,忽略和自定义捕捉。那么接着上面的话题,“合适”的时候究竟是什么时候呢?这里直接给出答案:
进程在从内核态到用户态的时候会进行信号的检测并处理。

a. 小铺垫

从这里就出现了两个新的名词:用户态和内核态。它们俩代表了计算机中进程的不同的身份,从名字来看,内核态所能使用的资源一定是多于用户态的,事实也是正如你所想的。
那么什么是内核态,什么是用户态呢?
我们的代码在编写号的时候,难免会使用到系统调用,访问到系统的内核级资源(例如管道资源,task_struct结构体等等),那么对于我们用户来说,内核级资源是我们普通用户能够访问的吗?答案是肯定不能的,所以当我们的进程调用系统调用访问系统内核级资源的时候,进程要进行从用户态到内核态的身份的转换。比如我们的信号:
在这里插入图片描述
其中我们使用signal函数进行自定义捕捉然后向进程发送对应信号时,我们的进程一定是经过了从用户到内核的。
我们以前看到地址空间的时候可能看到的是这样的分布:
在这里插入图片描述

我们应该都直到用户区的各种分区代表的什么意思,但是内核数据区我们好像不是那么熟悉。
那你有没有想过我们调用系统调用的时候,这个系统调用在哪里呢?系统调用又不是我们写的,系统调用也肯定不是什么库函数那自然也不在共享区中,我们也没有什么系统库这样的说法。
此时地址空间这幅图里面有一个区域:内核数据区,地址空间中的内核数据区有自己独立的页表会跟内存中的操作系统进行映射。内存中的操作系统有一部分就是系统调用,当然除了系统调用外,还有各种管理体系,调度方法,内核数据结构等,这些都会通过内核级页表来跟地址空间中的内核数据区进行映射(要知道操作系统也是一个软件,也是一个进程,它也有代码和数据)。
那么此时就说得通了,调用系统调用就跟调用库函数一样,也是在地址空间中进行的。
在32位机器下地址空间的大小是4GB其中用户区占[0, 3GB],内核占[3GB, 4GB]。
在这种机制下,我们进程的代码所有的执行都是在资金的地址空间进行调用,跳转,和返回。
此外这么做的话,要知道计算机是不论何时都会有进程在运行,系统中又不止一个进程,那么我们要进行我们的文件系统,驱动管理,调度方法等等直接就可以通过当前调度进程的地址空间的内核区去进行找对应的方法然后调用不就行了啊。比如时钟中断到了需要进行进程调度切换的时候,此时我们直接就可以在当前要被剥离的进程上的地址空间中的内核区去调用调度方法,直接就可以实现进程间切换。这样的机制可以让CPU随时都可以找到操作系统
当我们的进程从用户区到内核区的跳转其实就已经让进程的身份发生变化了。当再次回到用户区执行我们的主代码时,又会从内核态转变位用户态,此时操作系统就会对该进程进行信号的检测以及处理。
那么就有人问了,CPU是怎么知道该进程此时是属于内核态还是用户态啊,该不该让它进入内核啊,其实CPU中有一个寄存器叫做CS,它的低两位会表示当前进程是用户态还是内核态,在Linux操作系统中1表示用户,3表示内核,就算两个位能够表示四种状态,但确实设置的就只有两种用户态和内核态,这样,CPU就能够识别进程的状态了。
这里有个小知识点:我们知道CPU运行我们的代码和运算我们的数据时使用的都是虚拟地址,然后通过MMU转化为物理地址区进行操作的,而页表的地址不是虚拟地址而是物理地址,这个结果也是不令人意外的。

b. 信号的处理和捕捉

经过上面的铺垫之后,我们再来认识“合适”的时候捕获并处理信号。
那么我们发送给进程信号之后,当进程执行完系统调用之后,会从内核态转变为用户态,这个时候,操作系统首先会检测信号(pending表),检测到有信号(该信号没有被阻塞)之后就会进行对信号的处理。
如果被阻塞了,那么操作系统会直接返回,然后继续执行主代码。
在处理的时候默认处理和忽略自不用多说,这些都是内核的事情,它们会做好,它们会在做完之后,直接返回然后继续执行我们的代码。
那假如是用户自定义的呢?这里给出大致的流程图:
在这里插入图片描述
当自定义捕捉执行完之后,它该干什么呢?直接回到主代码接着执行吗?它怎么能够找到主代码在哪里呢?我们的函数中可没有提供主代码的地址。所以它会再次回到内核中,至于回到内核的方式则是调用系统调用:sigreturn
这个函数能够将我们重新带回到主代码上。
在这里插入图片描述
那么疑问就又来了,它都找不到主代码在哪里,我们又没有调用这个系统调用,它又凭什么能够找到这个函数呢?
要知道当调用函数的时候,是会在栈区建立栈帧的,而当函数有返回值时,还会通过压栈的方式将返回值存入到寄存器中,而操作系统正是利用了这一特点,将该系统调用的地址作为我们自定义捕捉函数的返回值,然后就能调用了。当调用了这个函数之后,我们的进程再次回到内核,然后再返回执行我们的主代码:
在这里插入图片描述
此刻信号的处理和捕获就完成了。
现在还有一些小问题:
自定义捕捉的函数是以用户态来执行还是以内核态来执行?
其实通过上面的了解后,它是以用户态进行的。这么做的主要的目的就是怕当以内核态执行自定义捕捉函数时,该函数中会又恶意修改内核数据的代码,这样的后果是不堪设想的。
用户态和内核态之间的切换只能通过系统调用吗?
我们有的代码中可能压根就不会使用系统调用,如果假如里面只进行一些运算呢?向这个进程发送信号就不起作用了吗?这样的现象显然是不合理的。要知道我们的进程调度和进程的切换机制。当时钟中断之后该进程的时间片到了,需要替换掉该进程,而当再次调度到该进程的时候,经过上面的介绍进程的切换调度方法是通过地址空间中的内核区找到的,而当调度完成之后开始运行这个进程,是不是也从内核态到用户态了呢?所以用户态和内核态之间的切换不仅仅是只能通过系统调用的,还有时钟中断。当然还不只有时钟中断,这里不再介绍了。
此时就可以将我们信号的处理和捕获进行大致的抽象了:
在这里插入图片描述
那处理方式是自定义捕捉的的话,两种状态的切换就进行了四次,再对这个过程进行抽象:
在这里插入图片描述

c. 信号的处理的特点

1) . sigaction

信号在进行递达时,如果此时有相同类型的信号再次未决的话,则这个信号会被阻塞,直到该次信号递达完成之后,在进行下一次的递达,这么做的目的也是防止进程重复进入相同的自定义捕捉中。
我们可以写一段这样的代码来验证:
在这里插入图片描述

在这里插入图片描述
可以看到当我们发送二号信号,进程开始处理二号信号时,我们再次发送二号信号该进程的二好信号是未决的,但是没有递达,直到第一次信号递达完成之后,立即开始第二次的信号递达,这里也有一个结论:被阻塞的信号当解除阻塞之后,该进程要立即处理
而此时我们又要介绍一个关于这方面的系统调用:
在这里插入图片描述
这个函数可以看到它的参数长得跟我们前面的sigprocmask有点像,但是这里使用参数的是结构体,如果我们只关注这个结构体的第一个参数的话,这个函数跟signal的效果是一样的:
在这里插入图片描述

在这里插入图片描述
我们要介绍的是这个结构体的另一个成员sa_mask,当看到它的类型和名字其实就可以猜个差不多了,这个成员的作用是,当进行信号的递达时,可以自定义阻塞想阻塞的信号,这个时候我们在使用一下这个接口,同时阻塞三号信号:
在这里插入图片描述

在这里插入图片描述
可以看到确实是阻塞住了。

2) .多信号的处理

以上谈论信号的捕捉和处理都是单个信号的情况,那么就有一个问题,如果是多信号的情况下的话,究竟是一个一个执行呢(中间会进行用户态和内核态的切换)?还是一次性全执行呢?
我们也可以写代码进行验证:
在这里插入图片描述

在这里插入图片描述可以看到是一并处理。但是看到它的处理顺序是无序的,这也说明信号的处理也是有优先级的

2. 信号的其他补充

a. 可重入函数

我们现在来想这么一个问题:
假如我们有一个链表,现在在主代码中要进行头插一个节点node1,而在进行插入的时候,刚好有一个信号发过来,也刚好立即处理信号,而信号的自定义捕捉中,也调用了头插函数,头插了一个节点node2.那么此时就会出现这样的情况:
在这里插入图片描述

就会出现内存泄漏。

你可以想象为主代码和自定义捕捉是两个执行流(虽然这是多进程和多线程的知识),像这样有两个执行流,在一个执行流还没结束的时候另一个执行流也进入了这个函数,这种现象叫做重入,而因为这种重入现象造成混乱(例如内存泄漏)的函数叫做不可重入函数,反之则称为可重入函数。
需要注意的是,不可重入/可重入这只是一个函数的特性,我们无法评判这个函数的好坏,所以不可重入函数它不是不可以使用的函数,它只是在多执行流中会产生混乱,但是单执行流中是正常的。
一般来说,拥有以下任一特点,都是不可重入函数:

调用了malloc或free,因为malloc也是用全局链表来管理堆的。
调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

b. volatile关键字

volatile是C语言中的一个关键字,它可以使得变量保持内存可见性。我们可以使用代码来验证这一点:
在这里插入图片描述
在这里插入图片描述
在使用g++/gcc编译器时默认是对我们形成的可执行没有优化的,使用-O+特定数字选项可以对我们要形成的可执行进行优化:
在这里插入图片描述
我们看到这个程序还在运行,下面我来解释其中的原理:
我们的主代码中并没有对这个flag进行算数修改,而我们又对我们的可执行进行了优化,导致在启动我们的程序的时候flag这个变量会被加载到寄存器中成为一个寄存器变量,while循环判断的就是这个寄存器中的变量,而我们发送信号进入自定义捕捉中将flag修改,这是将内存中的flag修改了。所以while还是会进入死循环,而解决方法就是使用volatile关键字:
在这里插入图片描述

在这里插入图片描述
保证了flag的内存可见性,而保证的对象就是CPU。

c. SIGCHLD

我们在研究父子进程的时候,僵尸进程是非常恐怖的事情,所以回收子进程变得尤为重要。其实子进程再退出的时候会给父进程发送一个信号那就是17号信号SIGCHLD:
在这里插入图片描述
通过代码我们可以验证这一点:
在这里插入图片描述
在这里插入图片描述
既然当子进程退出的时候会向父进程发送信号的话我们可以利用这一特点,在自定义捕捉中进行子进程的回收:
在这里插入图片描述
这样我们也可以进行多个子进程的回收:
在这里插入图片描述

在这里插入图片描述
但其实,我们也可以这样:
在这里插入图片描述
将17号信号手动设置成忽略,这样在Linux操作系统下,系统在子进程退出后会自动回收子进程。这样的方式回收子进程确实方便,但是却无法查看子进程的退出码和信号,所以子进程的回收需要依据具体场景使用。

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

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

相关文章

STM32引脚重定义问题

最近在搞资源管理,发现有些引脚不能用 比如这个PE引脚。我想用他输出PWM,但是不能用,我也重定义了,还是不能用。回去翻看了技术手册。 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //重映射引脚功能,需…

Java面试题:volatile专题

王有志,一个分享硬核Java技术的互金摸鱼侠 加入Java人的提桶跑路群:共同富裕的Java人 今天是《面霸的自我修养》第4篇文章,我们一起来看看面试中会问到哪些关于volatile的问题吧。数据来源: 大部分来自于各机构(Java之父,Java继父,某灵,某泡,某客)以及各博主整理文档…

解决Ubuntu中vscode右键没有create catkin package

右键发现没有这个create catkin package 解决方案: 查了一会发现安装个拓展就可以了 效果:

最新Unity游戏主程进阶学习大纲(2个月)

过完年了,很多同学开始重新规划自己的职业方向,找更好的机会,准备升职或加薪。今天给那些工作了1~5年的开发者梳理”游戏开发客户端主程”的学习大纲,帮助大家做好面试准备。适合Unity客户端开发者。进阶主程其实就是从固定的几个方面搭建好完整的知识体…

【Spring】创建和使用

目 录 一.创建 Spring 项目1.1 创建⼀个 Maven 项目1.2 添加 Spring 框架依赖1.3 手动创建启动类 二.将 Bean 对象存储到 Spring2.1创建Bean对象2.2将 Bean 存储到 Spring 三.获取并使用 Bean 对象3.1 创建 Spring 上下文3.2 获取指定的 Bean 对象3.3 使用 Bean 四.总结 经过前…

Elasticsearch:创建自定义 ES Rally tracks 的分步指南

作者:Alejandro Snchez 按照这个综合教程学习如何制作个性化的 Rally tracks ES Rally 是什么?它的用途是什么? ES Rally 是一个用于在 Elasticsearch 上测试性能的工具,允许你运行和记录比较测试。 做出决策可能很困难&#x…

体感互动游戏定制开发:创新与技术的交融

体感互动游戏是一种结合体感技术和游戏娱乐的新型形式,为玩家提供了更加身临其境的游戏体验。而要开发一款成功的体感互动游戏,需要一支跨学科的团队,他们将创新与技术有机地结合,以满足用户的需求和期待。 首先,游戏…

钉钉小程序 没有调用该接口的权限

钉钉小程序 没有调用该接口的权限 problem 钉钉官方自带免登陆小程序 后端接口报错 {"errcode":60011,"errmsg":"没有调用该接口的权限,接口权限申请参考:https://open.dingtalk.com/document/orgapp-server/add-api-permiss…

Flutter插件开发指南02: 事件订阅 EventChannel

Flutter插件开发指南02: 事件订阅 EventChannel 视频 https://www.bilibili.com/video/BV1zj411d7k4/ 前言 上一节我们讲了 Channel 通道,但是如果你是卫星定位业务,原生端主动推消息给 Flutter 这时候就要用到 EventChannel 通道了。 本节会写一个 1~…

C++ 二维差分 二维前缀和逆运算 差分矩阵

输入一个 n 行 m 列的整数矩阵,再输入 q 个操作,每个操作包含五个整数 x1,y1,x2,y2,c ,其中 (x1,y1) 和 (x2,y2) 表示一个子矩阵的左上角坐标和右下角坐标。 每个操作都要将选中的子矩阵中的每个元素的值加上 c 。 请你将进行完所有操作后的…

mfc140u.dll文丢失导致应用程序无法正常,有哪些解决办法

mfc140u.dll是Microsoft Foundation Classes(MFC)的一个重要组件,它提供了许多用于开发Windows应用程序的功能和工具。然而,当系统或应用程序升级、恶意软件感染或文件损坏以及用户错误操作等情况发生时,mfc140u.dll文…

力扣238和169

一:238. 除自身以外数组的乘积 1.1题目 1.2思路 1.3代码 //左右乘表 int* productExceptSelf(int* nums, int numsSize, int* returnSize) {int* answer (int*)malloc(numsSize*sizeof(int));int i 0;int left[numsSize],right[numsSize];left[0] 1;for(i 1;…

打码半年,开源一款自定义大屏设计软件!

hi,大家好,我是Tduck马马。 最近我们开源了一款大屏软件-TReport,与大家分享。 TReport是一款基于Vue3技术栈的数据可视化系统,支持静态、动态api等数据源;可用于数据可视化分析、报表分析、海报设计使用。 提供自定…

☀️将大华摄像头画面接入Unity 【1】配置硬件和初始化摄像头

一、硬件准备 目前的设想是后期采用网口供电的形式把画面传出来,所以这边我除了大华摄像头还准备了POE供电交换机,为了方便索性都用大华的了,然后全都连接电脑主机即可。 二、软件准备 这边初始化摄像头需要用到大华的Configtool软件&#…

哈希(哈希散列数据结构)---底层原理

Day02: 1.哈希散列数据结构:底层实现就是:数组链表(红黑树) map的put方法和get方法。 2.数组方法和链表存取数据的区别 数组方法:法随机访问快 链表:增删改效率快。 3.哈希结合了链表和数组的特性。 …

Sora后观察:AI大模型产业落地的八个锚点

在正在进行的2024年,国内大模型也将更下沉和落地,在技术上的突破之外,也会出现更多的向下的产业兼容和产业实践案例,作为新质生产力推动产业数字化转型的航船加速前进。 作者|斗斗 编辑|皮爷 出品|产业家 “电影讲述了一名…

关于深度学习和大模型的基础认知

这年头,作为一个技术人,话头里没有“大模型”,和人聊天都聊不下去。为了让自己和大家能更好的参与话头,特撰写此文,提供一些对大模型的基础认知能力(门外汉,浅尝辄止)。旨在解自己的…

音乐与步伐同行:南卡、韶音和墨觉的骨传导耳机深度评测

在快节奏的现代生活中,音乐成为了许多人精神慰藉的方式之一。特别是对于那些热爱运动的人来说,音乐不仅是他们运动过程中的最佳伴侣,更是激发潜力,突破极限的源动力。但是在运动的过程中如何享受到最佳的音乐体验呢?这…

【wu-lazy-cloud-network】Java自动化内网穿透架构整理

项目介绍 wu-lazy-cloud-network 是一款基于(wu-framework-parent)孵化出的项目,内部使用Lazy ORM操作数据库,主要功能是网络穿透,对于没有公网IP的服务进行公网IP映射 使用环境JDK17 Spring Boot 3.0.2 版本更新 1…