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.hqwc.cn/news/236596.html

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

相关文章

训练 CNN 对 CIFAR-10 数据中的图像进行分类-keras实现

1. 加载 CIFAR-10 数据库 import keras from keras.datasets import cifar10# 加载预先处理的训练数据和测试数据 (x_train, y_train), (x_test, y_test) cifar10.load_data() 2. 可视化前 24 个训练图像 import numpy as np import matplotlib.pyplot as plt %matplotlib …

代码随想录第二十一天(一刷C语言)|回溯算法组合

创作目的&#xff1a;为了方便自己后续复习重点&#xff0c;以及养成写博客的习惯。 一、回溯算法 1、种类 排列、组合、分割、子集、棋盘问题 2、回溯步骤 &#xff08;0&#xff09;回溯抽象 回溯法解决的问题均可以抽象为树形结构&#xff08;N叉树&#xff09; &…

plist文件在线生成网页配置苹果ios系统ipa文件下载

您可以进入首页—工具箱—plist文件在线制作 您可以进入控制台—plist文件 ●也可以直接访问&#xff1a;咕噜分发内测平台-苹果ios系统应用安卓apk安全漏洞扫描提供商 ●应用名称_包体的bid-下载地址-图标地址 ●如果不知道怎么查看苹果包名 可以通过咕噜分发【工具箱】-【IOS…

基于AT89C51单片机四位加法计算器的设计

1&#xff0e;设计任务 利用AT89C51单片机为核心控制元件,设计一个四位加法计算器&#xff0c;设计的系统实用性强、操作简单&#xff0c;实现了智能化、数字化。 1&#xff09;、通过4*4矩阵键盘输入数字及运算符&#xff1b; 2&#xff09;、可以进行4位十进制数以内的加法…

MyEclipse控制台console不停的自动跳动控制台界面,解决方案

有时候Eclipse启动&#xff0c;控制台console不会自动跳出来&#xff0c;需要手工点击该选项卡才行&#xff0c;按下面的设置&#xff0c;可以让它自动跳出来(或不跳出来)&#xff1a;方法&#xff1a; 一、windows -> preferences -> run/debug -> consol…

【shell】正则表达式和AWK

一.正则表达式 通配符匹配文件&#xff08;而且是已存在的文件&#xff09; 基本正则表达式扩展正则表达式 可以使用 man 手册帮助 正则表达式&#xff1a;匹配的是文章中的字符 通配符&#xff1a;匹配的是文件名 任意单个字符 1.元字符&#xff08;字符匹配&…

API管理:smart-doc 与 新版 torna 集成

使用 docker-compose 搭建 torna 环境 torna 介绍 项目地址&#xff1a;https://gitee.com/durcframework/torna torna 是一个企业接口文档解决方案&#xff0c;目标是让文档管理变得更加方便、快捷。Torna采用团队协作的方式管理和维护项目API文档&#xff0c;将不同形式的文…

发现一个好用的搜索引擎【非凡搜索】无广告

# 非凡搜索&#xff0c;小众、无广告、简洁。号称“不收集、不传播任何个人信息”。 非凡搜索 作为一个程序员&#xff0c;经常需要搜索一下技术资料&#xff0c;在国内大部分搜索引擎中搜索时&#xff0c;往往夹带各种广告或目的不明确的结果&#xff0c;这个搜索引擎无疑是一…

chrome vue devTools安装

安装好后如下图所示&#xff1a; 一&#xff1a;下载vue devTools 下载链接https://download.csdn.net/download/weixin_44659458/13192207?spm1001.2101.3001.6661.1&utm_mediumdistribute.pc_relevant_t0.none-task-download-2%7Edefault%7ECTRLIST%7EPaid-1-13192207…

Clickhouse Join

ClickHouse中的Hash Join, Parallel Hash Join, Grace Hash Join https://www.cnblogs.com/abclife/p/17579883.html https://clickhouse.com/blog/clickhouse-fully-supports-joins-full-sort-partial-merge-part3 总结 本文描述并比较了ClickHouse中基于内存哈希表的3种连接…

申请免费的域名SSL证书

1.&#xff0c;选择SSL证书提供商 首先&#xff0c;您需要选择一个提供免费SSL证书的可信赖服务提供商。比如JoySSL就提供了永久免费版本的SSL证书。 2&#xff0c; 注册账户并登录 在选择了JoySSL&#xff0c;您需要在网站上注册一个账户。完成注册后&#xff0c;使用您的账…

uniapp微信小程序实现地图展示控件

最终实现效果&#xff1a; 地图上展示控件&#xff0c;并可以点击。 目录 一、前言 二、在地图上展示控件信息 点击后可进行绘制面图形 1.使用cover-view将控件在地图上展示 2.设置控件样式&#xff0c;使用好看的图标 3.控件绑定点击事件 一、前言 原本使用的是control…