Linux信号超详细剖析

预备知识:

一、信号产生(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.rhkb.cn/news/207207.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【android开发-01】android中toast的用法介绍

1&#xff0c;android中toast的作用 在Android开发中&#xff0c;Toast是一种用于向用户显示简短消息的轻量级对话框。它通常用于向用户提供一些即时的反馈信息&#xff0c;例如操作结果、提示或警告。 Toast的主要作用如下&#xff1a; 提供反馈&#xff1a;Toast可以在用户…

每日一练2023.12.1——帅到没朋友【PTA】

题目链接&#xff1a;L1-020 帅到没朋友 题目要求&#xff1a; 当芸芸众生忙着在朋友圈中发照片的时候&#xff0c;总有一些人因为太帅而没有朋友。本题就要求你找出那些帅到没有朋友的人。 输入格式&#xff1a; 输入第一行给出一个正整数N&#xff08;≤100&#xff09;&…

【MySQL】视图:简化查询

文章目录 create view … as创建视图更改或删除视图drop view 删除视图replace关键字&#xff1a;更改视图 可更新视图with check option子句&#xff1a;防止行被删除视图的其他优点简化查询减小数据库设计改动的影响使用视图限制基础表访问 create view … as创建视图 把常用…

分布式锁,分布式锁应该具备哪些条件,分布式锁的实现方式有:基于Zookeeper实现、Redis实现、数据库实现

文章目录 分布式锁0-1分布式锁--包含CAP理论模型概述分布式锁&#xff1a;分布式锁应该具备哪些条件&#xff1a;分布式锁的业务场景&#xff1a; 分布式锁的实现方式有&#xff1a;基于Zookeeper - 分布式锁实现思想优缺点基于Redis - 分布式锁实现思想实现思想的具体步骤&…

Linux驱动开发——网络设备驱动(实战篇)

目录 四、 网络设备驱动实例 五、DM9000 网络设备驱动代码分析 六、NAPI 七、习题 书接上回&#xff1a; Linux驱动开发——网络设备驱动&#xff08;理论篇&#xff09;-CSDN博客 &#xff08;没看过上面博客的同学&#xff0c;skb是linux对于网络套接字缓冲区的一个虚拟…

【微服务 SpringCloudAlibaba】实用篇 · Nacos配置中心

微服务&#xff08;6&#xff09; 文章目录 微服务&#xff08;6&#xff09;1. 统一配置管理1.1 在nacos中添加配置文件1.2 从微服务拉取配置 2. 配置热更新2.1 方式一2.2 方式二 3. 配置共享1&#xff09;添加一个环境共享配置2&#xff09;在user-service中读取共享配置3&am…

vue2 el-table 封装

vue2 el-table 封装 在 custom 文件夹下面创建 tableList.vue直接上代码&#xff08;代码比较多&#xff0c;复制可直接用&#xff09; <template><div class"mp-list"><el-tableref"multipleTable"class"mp-custom-table":dat…

Ubuntu18.4中安装wkhtmltopdf + Odoo16配置【二】

deepin Linux 安装wkhtmltopdf 1、先从官网的链接里下载linux对应的包 wkhtmltopdf/wkhtmltopdf 下载需要的版本&#xff0c;推荐版本&#xff0c;新测有效&#xff1a; wkhtmltox-0.12.4_linux-generic-amd64.tar.xz 2、解压下载的文件 解压后会有一个wkhtmltox文件夹 3…

【opencv】计算机视觉基础知识

目录 前言 1、什么是计算机视觉 2、图片处理基础操作 2.1 图片处理&#xff1a;读入图像 2.2 图片处理&#xff1a;显示图像 2.3 图片处理&#xff1a;图像保存 3、图像处理入门基础 3.1 图像成像原理介绍 3.2 图像分类 3.2.1 二值图像 3.2.2灰度图像 3.2.3彩色图像…

党建引领·和谐共建——赤岗街首届微型社区养老服务公益博览会开幕

服务咨询平台&#xff0c;让社区长者更便捷地了解到养老相关政策、信息。 本次活动由赤岗街公共卫生委员会、赤岗街道办事处、中国老龄事业发展基金会老年维权基金管理委员会主办&#xff0c;珠影社区居委会、广州市穗星社会工作服务中心、广州市盈泽信息科技有限公司承办&…

4/5G互操作 EPSFB讲解

今天我们来讲一下4/5G之间之间互操作&#xff0c;以及5G的EPSFB是基于什么实现的~ 目录 4/5G互操作 重选 切换 基于覆盖的切换 基于业务的切换 两个面试问题 想要加快4G切换5G的速度&#xff0c;调哪个参数怎么调高效&#xff1f; 想要减慢5G切换4G的速度调哪个参数怎…

项目计划阶段项目管理文档(风险管理、软件估计、立项、计划)

项目计划过程支撑文件&#xff1a; 1、风险和机会管理表 2、软件估计书模板 3、立项通知 4、项目计划书 软件开发全文档获取&#xff1a;点我获取 1、风险和机会管理表 2、软件估计书模板 3、立项通知 4、项目计划模板

Spring简单的存储和读取

前言 前面讲了spring的创建&#xff0c;现在说说关于Bean和五大类注解 一、Bean是什么&#xff1f; 在 Java 语⾔中对象也叫做 Bean&#xff0c;所以后⾯咱们再遇到对象就以 Bean 著称。这篇文章还是以spring创建为主。 二、存储对象 2.1 俩种存储方式 需要在 spring-conf…

A++ 敏捷开发-1 如何改善

1 如何改善 敏捷开发过程改进案例 5月 A公司一直专门为某电信公司提供针对客服、线上播放等服务服务。 张工是公司的中层管理者&#xff0c;管理好几个开发团队&#xff0c;有5位项目经理向他汇报。 他听说老同学的团队都开始用敏捷开发&#xff0c;很感兴趣&#xff0c;便参…

kafka中的常见问题处理

文章目录 1. 如何防⽌消息丢失2. 如何防⽌重复消费3. 如何做到消息的顺序消费4. 如何解决消息积压问题4.1 消息积压问题的出现4.2 消息积压的解决⽅案 5. 实现延时队列的效果5.1 应用场景5.2 具体方案 1. 如何防⽌消息丢失 ⽣产者&#xff1a;1&#xff09;使⽤同步发送 2&…

C语言——有一个3*4的矩阵,要求求出其中值最大的那个元素的值,以及其所在的行号和列号

#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h> int main() {int i,j,row0,colum0,a[3][4]{{1,2,3,4},{9,8,7,6},{-10,10,-5,2}};int maxa[0][0];for ( i 0; i < 3; i)//行&#xff08;row&#xff09;{for ( j 0; j < 4; j)//列&#xff08;colum&#xf…

Microsoft Remote Desktop高效、安全、稳定的远程办公解决方案

在今天的数字化时代&#xff0c;Remote Desktop远程办公已成为许多人的日常生活。无论你是因为工作需要&#xff0c;还是因为在家中需要访问公司服务器&#xff0c;微软远程连接软件都是一个理想的选择。 微软远程连接软件Remote Desktop是一款高效、安全、稳定的远程办公解决…

sublime Text使用

1、增加install 命令面板 工具(tool)->控制面板(command palette) -> 输入install ->安装第一个install package controller&#xff0c;以下安装过了&#xff0c;所以没展示 2、安装json格式化工具 点击install package&#xff0c;等几秒会进入控制面板&#xff0…

R语言30分钟上手

文章目录 1. 环境&安装1.1. rstudio保存工作空间 2. 创建数据集2.1. 数据集概念2.2. 向量、矩阵2.3. 数据框2.3.1. 创建数据框2.3.2. 创建新变量2.3.3. 变量的重编码2.3.4. 列重命名2.3.5. 缺失值2.3.6. 日期值2.3.7. 数据框排序2.3.8. 数据框合并(合并沪深300和中证500收盘…

B 树和 B+树 的区别

文章目录 B 树和 B树 的区别 B 树和 B树 的区别 了解二叉树、AVL 树、B 树的概念 B 树和 B树的应用场景 B 树是一种多路平衡查找树&#xff0c;为了更形象的理解。 二叉树&#xff0c;每个节点支持两个分支的树结构&#xff0c;相比于单向链表&#xff0c;多了一个分支。 …