Linux信号超详细剖析

news2025/1/12 20:40:33
预备知识:

一、信号产生(OS发给进程)

1、键盘组合键

Linux中,一次登录对应一个终端,bash/shell。且只允许一个进程是前台进程,默认就是bash/shell,其它都是后台进程。获取键盘输入的是前台进程。

Ctrl+c: 向前台进程发送2号信号,SIGINT(interrupt),平时的指令都是bash/shell收到然后执行

Ctrl+\:向前台进程发送3号信号 SIGQUIT

Ctrl+Z:向前台进程发送19号信号 SIGSTOP

硬件中断问题:

键盘数据如何输入给内核的?数据如何转化为信号?

CPU有很多针脚(给CPU寄存器0-1充放电),每一个针脚都有自己的编号,键盘是通过中断控制器连接到CPU的,当键盘按下某个按钮,就会触发中断控制器触发中断,然后CPU当中会有一个中断号,操作系统会根据中断号在中断向量表(操作系统和不同外设互通的方法表)当中执行对应的函数。

外设  要拷贝和拷贝完给CPU发送(仅控制信号,DMA芯片....)
硬件中断  中断号  中断向量表

信号  软件中断  模拟硬件中断设计

外设写给内核缓冲区时,OS判断,区分数据和控制。

如果是数据就用系统调用read等 从进程缓冲区-->用户缓冲区(内核-->用户内存)
如果是控制就转化为信号发给进程(用户层)

该过程,内核知道何时开始和终止,进而也能控制进程是运行还是等待。

意义:提高OS效率,不用自己检查外设何时读写

信号是进程之间异步通知的一种方式,软中断
异步:硬件层面何时接收外部写入是不确定的
软件层面 进程何时收到信号是不确定的

2、kill命令

直接用bash/shell向指定进程发送信号

3、系统调用

signal

可以捕捉指定signum的信号,并传入自己的方法,自定义该信号的行为。

9  19号信号 不能被捕捉

只需要设置一次,底层将该进程对应该信号的方法替换了(函数指针)

kill(指定进程指定信号)

模拟实现mykill

raise(调用者发指定信号)

封装了kill(getpid(),signum)

abort(调用者固定信号)

已经变成3普通函数了

abort()

函数内部多了一些功能,比自定义多了固定的abort退出

即发送abort信号-->自定义   调用abort()函数-->自定义+aborted

4、硬件异常

一般捕捉信号完成一些收尾工作(面向用户  如:C++try catch异常体系),记录日志,数据保存等,在自定义工作完成后退出。

不是为了出现错误解决错误,而是让用户知道错误的原因。

div除零错误/异常

发生除零错误,OS给进程发信号,然后进程退出。

自定义8号进程为仅发送一条消息

当发生除零错误时,原代码执行到a/=0时,OS一直给进程发送8号信号

while :; do ps axj | head -1 && ps axj | grep mysignal | grep -v grep;sleep 1;done

该进程没有退出变为Z状态。

信号8捕捉前,进程要么正常退出,要么执行默认动作FPE后退出

信号8捕捉后,OS一直给它发送8号信号,它就一直执行自定义动作,不会退出

野指针/段错误:

信号捕捉后与上面的div异常相同。

异常如何让OS发信号?(不同的CPU寄存器报错)

1、对于div除零异常

进程不退出就会一直被调度,OS死循环向它发信号。(出现了硬件异常问题,但没有解决,CPU一直检测报错)

2、对于野指针异常

5、软件条件异常(特殊事件)

1、管道PIPE

2、文件描述符fd

返回-1,不会使进程退出。

3、闹钟问题

Myhanlder中可以通过调用alarm设置一些定时任务。

运行主要代码main外,定时执行指定的定时任务。

设置新的alarm的同时,得到上一次alarm的剩余时间,之前没有设置就返回0.

此外,OS中有很多闹钟,管理它们也要有相应的数据结构和对象。

alarm结构体中应该包含:时间戳记录开始/终止时间,指向的pid或task_struct指针

使用优先级队列,按照时间差作为Comp(小堆)

堆顶不超时就不用遍历,堆顶超时就操作后pop直至栈顶不超时

6、Term/Core终止进程区别

Core = Term+core dump

终止+保存出错信息用来事后调试

是否正常退出用[8,15]位表示,收到的信号用[0,6]位表示,收到的是Term还是Core用code dump标志位来标识。

ulimit

云服务器默认不开启core功能

开启core功能

出问题可以事后调试

core dump形成的临时文件太大了。

云服务器中服务挂掉后,第一时间不是为了找到出错位置,而是重新启动。

系统一般会自动重启,事后根据日志等排查。

如果开启core dump,且重启失败,一直重复,就会一直创建临时文件,进而导致磁盘存储的更大的问题。

7、实时信号

用于车载系统等,一遇到信号必须立即处理。

进程会维护一个实时信号队列,该进程每次收到信号就push到该队列中,不存在阻塞情况。

如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次
或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可 以依次放在一个队列里。本章不讨论实时信号。

二、信号的发送

1、进程是否收到信号?2、进程收到哪一个信号?

OS给进程发实际上是给PCB对象发送  [0,31]

OS是进程的管理者,只有OS才有资格修改task_struct

这里的signal就是下面的Pending

三、信号保存

只要来一个信号就要加入Pending中保存,然后结合Block判断是否处理,根据Handler决定如何处理。

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

1、三张表

block表和pending表都是位图的数据结构,而handler表则是一个函数指针数组。
信号的接收主要是靠pending表,block与handler表主要在信号的处理阶段使用。
其中pending表中的0、1就分别表示对应的信号是否存在
而block表中的0、1代表后面的信号是否能够被使用,1表示可以,0表示不可以。
如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次 或多次。Linux是这样实现的:常规信号在 递达之前 产生多次只计一次,而实时信号在递达之前产生多次可 以依次放在一个队列里。本章不讨论实时信号。

2、SIG_DEF和SIG_IGN

ignore忽略该信号,default相当于没有signal设置

3、sigset_t类型-->pending

是OS给用户层提供的数据类型,为了提高可移植性,封装一个类型,上层不论什么语言都用一种类型和相应的系统调用即可。

OS设计时只需要根据不同语言来添加不同版本,用户使用是统一的。

sigpending函数

输出型参数+sigismember得到pending位图(该位是否为1)

4、sigprocmask

int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
如果oset是非空指针,则读取进程的当前信号屏蔽字mask通过oset参数传出
如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。
如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后 根据set和how参数更改信号屏蔽字。

5、对2号信号屏蔽和解除屏蔽

6、9/19号无法block

阻塞除了9和19的信号,每次发送信号,对应的pending位就被设置为1.

四、信号处理

信号被处理的时间是内核态切换到用户态的时候。

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

调用系统调用时,OS会进行内核<-->用户 之间的状态切换

如:int 80

内核态:处于内核态的 CPU 可以访问任意的数据,包括外围设备,比如网卡、硬盘等,处于内核态的 CPU 可以从一个程序切换到另外一个程序,并且占用 CPU 不会发生抢占情况,一般处于特权级 0 的状态我们称之为内核态。

允许访问内核的代码和数据

用户态:处于用户态的 CPU 只能受限的访问内存,并且不允许访问外围设备,用户态下的 CPU 不允许独占,也就是说 CPU 能够被其他程序获取。

进程地址空间

OS的本质(被动接收时钟中断)

内核态与用户态的切换说白了就是CPU状态的切换+页表的切换。

状态切换:1、改变ecs寄存器保证权限 2、更换页表,确定起始地址

CPU中有一个寄存器为cr3(页表/页目录的虚拟地址)

一个ecs寄存器,标识为0时是内核态,标识为3时是用户态。

什么时候会进行内核态与用户态之间的转换呢?情况有很多:

1.系统调用时

2.时间片到了(要切换调度的进程就会进入内核态,返回时检测信号并处理)

for(;;)pause();

画图理解信号处理

问题1:内核态也能执行自定义的代码,为什么要切换回用户态?

内核态权限无约束,用户态的代码可能因此来访问OS的代码和数据,不安全。

问题2:执行完hander方法后为什么要回到内核再回到用户态?

用户态不知道进入内核前的上下文,执行到哪一行,要进入内核态找到后再返回。

sigaction

问题1:pending何时由1变0

进行信号处理前就会改为0

这里sigismember要从1开始,因为信号从1开始,0表示是否收到信号

问题2:信号处理时自动屏蔽

当某个信号的处理函数被调用时 , 内核自动将当前信号加入进程的信号屏蔽字 , 当信号处理函数返回时自动恢复原来的信号屏蔽字, 这样就保证了在处理某个信号时 , 如果这种信号再次产生 , 那么 它会被阻塞当前处理结束为止
例:在处理2号信号时,又收到2号信号,此时只会保存新的2号信号,不会立刻再去执行。
原因:在handler中只要陷入内核,(如系统调用,printf访问硬件等),若没有自动屏蔽,就会再次 检测到2号信号并执行,导致 重复调用
如果 在调用信号处理函数时, 除了当前信号被自动屏蔽之外 , 希望自动屏蔽另外一些信号 , 则用 sa_mask 字段说明这些需 要额外屏蔽的信号, 当信号处理函数返回时自动恢复原来的信号屏蔽字。
一直处理2号信号,此时再收到则会保存到pending位图中。

sa_mask

五、可重入函数

1、首先,main函数中调用了insert函数,想将结点node1插入链表,但插入操作分为两步,刚做完第一步的时候,因为某些原因(硬件中断,时间片轮转)使进程切换到内核,再次回到用户态之前检查到有信号待处理,于是切换到sighandler函数。

2、而sighandler函数中也调用了insert函数,将结点node2插入到了链表中,插入操作完成第一步后的情况如下:

3、当结点node2插入的两步操作都做完之后从sighandler返回内核态,此时链表的布局如下:

4、再次回到用户态就从main函数调用的insert函数中继续往下执行,即继续进行结点node1的插入操作。

最终结果是,main函数和sighandler函数先后向链表中插入了两个结点,但最后只有node1结点真正插入到了链表中,而node2结点就再也找不到了,造成了内存泄漏

实际执行顺序如下:

insert函数被不同的控制流调用main函数和sighandler函数使用不同的堆栈空间(并行),它们之间不存在调用与被调用的关系,是两个独立的控制流程),有可能在第一次调用还没返回时就再次进入该函数,我们将这种现象称之为重入。

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

如果一个函数符合以下条件之一则是不可重入的:

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

六、volatile在信号中的使用

volatile是C语言的一个关键字,该关键字的作用是保持内存的可见性。

原本flag为0,一直死循环,然后发送2号信号改变flag,继续向后执行。

flag为逻辑判断,也是计算,在CPU中进行。

但由于这只是单纯检测flag(只读取不写入),CPU可能对其进行优化(放到寄存器中

g++优化-O

使用-O1优化后发送信号2改变flag,但仍然是死循环。

优化后第一次直接把flag的值拷贝到寄存器中,之后就不会访问内存了(内存不可见),之后每次检测,都从CPU寄存器中读取。

在flag前加上volatile,避免编译器对flag过度优化,使其内存可见即可。

七、SIGCHLD17信号

为了避免出现僵尸进程,父进程需要使用waitwaitpid函数等待子进程结束。

父进程可以阻塞等待子进程结束,也可以非阻塞地查询的是否有子进程结束等待清理,即轮询的方式。采用第一种方式,父进程阻塞就不能处理自己的工作了;采用第二种方式,父进程在处理自己的工作的同时还要记得时不时地轮询一下,程序实现复杂。

其实,子进程终止时会给父进程发生SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理动作,这样父进程就只需专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用waitwaitpid函数清理子进程即可。

单进程下:

因此可以把wait/waitpid写在信号捕捉函数内部。

多进程下:

多个子进程同时退出,当正在处理一个时,会屏蔽SIGCLD信号,就会有一些信号没有被捕捉,进而导致内存泄漏。

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

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

相关文章

Nginx配置文件全解析【深度剖析细节】

文章目录 &#x1f4a5; 简介&#x1f4ab; 基本结构&#x1f349; 事件处理器&#x1f96d; 配置分析&#x1f34f; 配置示例 &#x1f349; HTTP服务器&#x1f96d; 配置分析&#x1f34f; 配置示例 &#x1f349; 虚拟主机 &#x1f34a; 优化&#x1f354; 总结 &#x1f…

02-使用Git命令操作远程仓库,如克隆或添加远程仓库,拉取或推送内容

操作远程仓库 创建远程仓库 第一步: 访问GitHub官网,登录自己的账号创建一个远程仓库 第二步: 设置远程仓库的信息(一般远程库的名字和本地库的名字一样),仓库创建完成后可以看到仓库地址(每个仓库都会对应一个网络地址) 第三步: 查看本地仓库对应的Https/SSH连接 远程仓库命…

【负载均衡 SLB介绍及其算法详解】(一万两千字)

目录 一、负载均衡 SLB 定义 二、负载均衡SLB的作用 三、负载均衡器&#xff08;Load Balancer&#xff09; 【1】工作原理 【2】主要功能 【3】关键概念 四、工作负载&#xff08;Workload&#xff09; 五、负载均衡算法 【1】轮询&#xff08;Round Robin&#xff0…

Oracle(2-8)Configuring the Database Archiving Mode

文章目录 一、基础知识1、Redo Log History2、NOARCHIVELOG Mode 非归档模式3、ARCHIVELOG Mode 归档模式4、Changing the Archiving Mode 更改归档模式![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/d6a09f9a6de24de7bbcdad90b8d6b9ca.png)5、Auto and Manual Ar…

图扑参展高交会-全球清洁能源创新博览会

“相聚鹏城深圳&#xff0c;共享能源盛宴” 第二十五届中国国际高新技术成果交易会(简称“高交会”)于 11 月 15-18 日在深圳盛大开幕。高交会由商务部、科学技术部、工业和信息化部、国家发展改革委、农业农村部、国家知识产权局、中国科学院、中国工程院和深圳市人民政府共同…

从零开始Inline Hook

中断表进入0环 通过中断门进入0环&#xff0c;首先了解一下中断门的构成 构造一个中断号 Base:函数地址 DPL:3 //因为三环使用调用门的条件就是CPL(即cs段选择子的RPL)<DPL P:1 //P为1时&#xff0c;中断表才有效 Segment Selector: 0x0008 //中断成功后切换自己的CPL 在…

生产制造中4种导致产品成本、库存核算差错的问题!(化工/化妆品/生物制剂/混凝土等行业ODOO)

在化工/化妆品/生物制剂/混凝土等行业&#xff0c;因为其生产物料及产成品大都以液体&#xff08;或散颗粒&#xff09;形态为主&#xff0c;多以重量为计数方式&#xff1b;且液体&#xff08;或散颗粒&#xff09;相较于固体的较大区别就是产品计数上变数较大&#xff0c;固体…

“掌握速卖通平台接口:电商开发的技术巅峰“

一、概述 速卖通平台接口是全球速卖通提供的一套API接口&#xff0c;旨在为开发者提供与速卖通平台进行数据交互的能力。通过使用速卖通平台接口&#xff0c;开发者可以快速构建自己的电商应用程序&#xff0c;并实现与速卖通平台的数据共享和交易流程。本文将详细介绍速卖通平…

List集合,遍历,数据结构

一.List常见的方法&#xff1a; 二. List集合的遍历方式 除了 迭代器遍历 增强for遍历 Lambda表达式遍历&#xff0c;还有自己独有的普通for遍历&#xff0c;列表迭代器遍历 1.迭代器遍历 2.增强for遍历 3.Lambda表达式遍历 4.普通for遍历 5.列表迭代器遍历 列表迭代器相对于…

云计算生成式 -给你不一样的音乐推荐新体验

目录 摘要&#xff1a; 正文&#xff1a; 一、亚马逊云与生成式 AI 结合的展望/总结 二、我用亚马逊云科技生成式 AI 产品打造了什么&#xff0c;解决了什么问题 三、未来云端技术发展趋势的见解 四、云端技术未来需要解决的问题 1、如何保护数据安全和隐私&#xff1f; …

SQL Sever 复习笔记【一】

SQL Sever 基础知识 一、查询数据第1节 基本 SQL Server 语句SELECT第2节 SELECT语句示例2.1 SELECT - 检索表示例的某些列2.2 SELECT - 检索表的所有列2.3 SELECT - 对结果集进行筛选2.4 SELECT - 对结果集进行排序2.5 SELECT - 对结果集进行分组2.5 SELECT - 对结果集进行筛选…

企业选择通配符SSL证书

通配符SSL数字证书是CA认证机构签发较多的一款SSL证书&#xff0c;它可以保护一个域名及其所有子域名&#xff0c;并且在证书有效期内可以免费添加子域名。对于子域名较多或者想要创建子域名站点的个人或者企事业单位&#xff0c;通配符SSL证书是性价比较高的一个选择。这种证书…

element ui el-date-picker日期时间选择器 设置只能选择不大于30天时间范围

需求&#xff1a;要求日期时间选择器只能选择最多32天&#xff0c;其他日期为不可点击状态。 日期组件type为daterange或者datetimerange都生效 实现&#xff08;vue2.x&#xff09;&#xff1a; 通过属性picker-options html <el-date-pickerv-model"dateTime&qu…

AUTOSAR OS任务调度的底层逻辑

先参考 FreeRTOS的任务触发底层逻辑 简述RTOS任务调度底层逻辑 AUTOSAR-OS的调度机制-调度表&#xff08;没理解透&#xff0c;继续更新&#xff09; OSEK与FreeRTOS在任务调度上最大的区别在于&#xff0c;FreeRTOS是基于全抢占任务调度和时间片轮转调度机制&#xff0c;具有…

Ubuntu 20.04 for NVIDIA V100 GPU安装手册

安装Ubuntu 20.04.3 LTS版本 image.png 安装Ubuntu 20.04按照安装提示&#xff0c;仔细选择每一项&#xff0c;基本默认即可。 系统中查看GPU信息 系统安装完成之后&#xff0c;进入系统&#xff0c;使用lspci 命令查询一下GPU是否存在、型号信息是什么。 bpangbobpang:\~$…

ROS URDF集成Rviz流程

实现流程&#xff1a; 一、新建功能包&#xff0c;导入依赖 二、编写 urdf 文件 三、在 launch 文件集成 URDF 与 Rviz 四、在 Rviz 中显示机器人模型 需求&#xff1a;在 Rviz 中显示一个盒状机器人 1、创建功能包&#xff0c;导入依赖 创建一个新的功能包&#xff0c;名…

数据爬取+数据可视化实战_哪里只得我共你(Dear Jane)_词云展示----网易云

一、前言 歌词上做文本分析&#xff0c;数据存储在网页上&#xff0c;需要爬取数据下来&#xff0c;词云展示在工作中也变得日益重要&#xff0c;接下来将数据爬虫与可视化结合起来&#xff0c;做个词云展示案例。 二、操作步骤 代码如下&#xff1a; # -*- coding:utf-8 -*-…

服饰行业的EDI应用

服饰行业备受关注的物流环节中最重要的一个问题即为库存管理&#xff0c;服饰行业的企业需要搞清楚如何加快周转率&#xff0c;解决供应链的库存挤压难题。强大需求背景之下的科技革命、互联网发展以及产业变革不断演进&#xff0c;使得企业认识到产业供应链安全可靠、自主可控…

快速了解ChatGPT(大语言模型)

目录 GPT原理&#xff1a;文字接龙&#xff0c;输入一个字&#xff0c;后面会接最有可能出现的文字。 GPT4 学会提问&#xff1a;发挥语言模型的最大能力 参考李宏毅老师的课快速了解大语言模型做的笔记&#xff1a; Lee老师幽默的开场&#xff1a; GPT&#xff1a;chat Ge…

python执行shell

0x00:前言 正常一个网站分为服务端和客户端&#xff0c;因为是正向的&#xff0c;所以服务端是在目标机器上的&#xff0c;客户端则是攻击者机器上&#xff0c;在这里要感谢MiaGz大师傅&#xff0c;这里很多都是参考了MiaGz大师傅的文章写出来的&#xff0c;进行了一点个人修改…