目录
进程保存信号以及方法
三个问题解决:
接收信号保存(pending表
信号是否阻塞(block表
信号动作保存(hardler表
信号功能解释
核心转储
生成核心转储文件
core文件的使用
为什么云服务(生产环境)核心转储功能关闭呢?
信号的产生方式
键盘
系统接口
kill
raise
abort
软件条件产生信号
管道
闹钟
硬件异常产生信号
除0错误
野指针、空指针的访问:
进程信号,在我还未理解前,根本无法理解一个进程是任何可以收到信号的,是以什么方式收到信号的。
理解进程信号
首先理解信号,通俗的来说信号类似一种通知,当你收到通知你知道应该下一步的动作。
绿灯亮了,过马路。上课铃响了,进教室。而在我们的进程和人一样,时刻准备着接收信号。
那么进程如何接收信号呢?接收信号又该如何做呢?
我们看看有哪些信号:
我们将信号分为:普通信号(1~31)以及实时信号(34~64),我们日程编程中更重于关注普通信号,在进程收到信号的时候并不是一定是立刻处理该信号,而是在合适的时候再去处理。
比如:打王者在推高地,但是突然妈妈叫你吃饭了,你会立刻处理放下手机去吃饭吗?所以我们需要将接收到的信号先保存起来,在合适的时候处理。普通的信号可以这样做,但是如果是特殊场景:你妈妈拿着棍子叫你吃饭,你就要立刻放下手机,处理妈妈给你发的信号。这就需要实时信号。
我们这里不讨论实时信号先,这里我们先抛出俩个疑问:
1:我们怎么知道什么信号来了?某个信号来了,我们应该对应做什么呢?红灯停绿灯行这个信号是教育日积月累学习的,上课铃响了我们要就去教室。这里没有看见红绿灯我们也知道这个方法,没有听到上课铃我们也知道响了进教室。这都是别人告诉我的,
2:当前没空,能不能等等在做,将信号先保存起来。但是怎么保存呢?
3:怎么甄别信号等待
先说第二个问题,我们发现普通信号只有32个,我们只是需要知道这32个信号某个信号是否来临。所以linux采用了位图的方式保存32个信号是否来了的检查机制。我们用4个字节就可以保存32个信号是否来临
用一个int就可以保存32个信号是否方式。位图真nb
那么这些比特位的反转谁来完成的呢?
来来来:一个共识先:只有OS才有权力给进程发(写)信号!!!
那么收到信号之后的处理存放在哪里呢?
也是类似pending表一样使用一个位图,保存对应pending表中信号产生后是否需要阻塞。
被阻塞的信号在解除阻塞后会被执行。当进程调用系统函数或其变体之一来阻塞信号时,被阻塞的信号将被添加到进程的信号屏蔽字中。进程的信号屏蔽字指定了哪些信号当前被阻塞,无法被递送给进程。当解除对某个信号的阻塞后,如果该信号此时已经到达,则会立即执行信号处理函数。如果该信号在阻塞期间被重复递送多次,那么只有在解除阻塞时最后一次递送的信号会被执行。需要注意的是,如果某个信号一直处于阻塞状态,那么它将不会被执行,直到解除对该信号的阻塞。因此,如果某个信号被阻塞了很长时间,而在解除阻塞之前已经递送了多次该信号,那么只有最后一次递送的信号会在解除阻塞后被执行。
在Linux中,信号处理方法是保存在一个称为信号处理函数表(Signal Handler Table)的数据结构中。该数据结构是一个数组,每个数组元素对应一个信号的处理方法。数组的索引即为信号的编号,因此可以通过信号编号来访问对应的信号处理方法。当接收到一个信号时,os内核会根据信号编号在信号处理函数表中查找对应的处理方法,并执行该方法来响应信号。
在大多数 Linux 系统中,Signal Handler Table 的大小是固定的,通常为 32 或 64 个元素。这意味着最多可以为 32 或 64 种不同的信号注册处理函数。每个元素对应一个信号的处理函数,可以通过信号的编号来索引到对应的元素。当然,这个数量可能因操作系统版本、内核配置或特定的系统限制而有所不同。
信号的3个状态:
1、信号递达(Delivery):信号递达指的是进程实际执行信号的处理动作。
2、信号未决(pending):信号从产生到递达之间的状态。
3、阻塞(block):进程可以选择阻塞一个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对该信号的阻塞,才能执行递达的动作。
信号产生和阻塞没有直接关系,信号递达和解除阻塞没有直接关系!
我们以32个信号画图
注意当信号处理后,我们会改变比特位。
今天的重点不是这个,我以后再写一篇关于三表的文章(其实现在知识水平不太够)
让我们输入一份指令,打开信号手册
让我们一行一行看:
Signal:信号的名称
Value :信号的编号
Action:信号处理动作
退出进程和忽略,是个中国人应该可以看得懂字面意思吧?
我们来谈谈核心转储
核心转储(Core Dump)是指在程序崩溃或异常退出时,将程序在崩溃时的内存状态保存到一个特殊的文件中,以便进行后续的调试和分析。
当程序发生崩溃或异常退出时,操作系统会为该进程生成一个核心转储文件。核心转储文件通常包含了程序在崩溃时的内存映像,包括堆栈、寄存器状态、全局变量等信息,以及其他与程序运行状态相关的信息。
核心转储文件对于调试和分析程序崩溃问题非常有用。开发人员可以使用调试器(如GDB)加载核心转储文件,以还原程序崩溃时的内存状态,查看堆栈跟踪信息,检查变量的值等。这有助于定位和修复引起程序崩溃的问题。
讲通俗:就是进程发送错误的时候,会把核心的错误信息保存下来,当我们打开debug调试该错误程序时,可以依靠核心转储快速找到删除信息。
核心转储?好像进程等待时候有这个玩意。
(20条消息) wait/waitpid(重点)介绍_云的小站的博客-CSDN博客
当进程异常退出的时候,父类使用waitpid可以获取core dump与退出信号
运行程序并且查看运行后当前目录文件
让我们查看,一下11号信号。
SIGSEGV是我们野指针空指针访问是会产生的保存信号,但是11信号是会产生的Core的,但是为什么我们的程序,打印的core dump为0呢;
一般而言,云服务器(生产环境)的核心转储功能是被关闭的!
我们将核心转储功能打开
再次运行程序
这个时候核心转储就产生了,当前目录生成了一个新的文件。
这个core文件就是我们的核心转储文件,里面保存了核心错误信息,让我们看看如何使用
因为gdb需要在可运行程序有debug,所以我们重新编译文件加入调试信息。
进入gdb调试模式
我们的核心转储功能就是为了在大型文件中方便程序员可以快速定位错误信号产生处。
来来来,我们多运行几次会错误的代码。
每次错误退出都会dump一个核心转储的文件,看看文件大小,卧槽!!比源文件都大!!
在实际生成环境我们的一些程序是在服务器上面跑的,一般一些进程崩溃会有重启功能。
如果一重启就生成核心转储文件然后程序崩溃,然后再重启再生成再崩溃。你也不想一觉起来,外存就满了吧??所以我们的核心转储功能再实际生产环境一般是关闭的,在研发环境才是打开核心转储功能的。
每次进程跑着的时候我们总是信号ctrl+c将前台进程关闭,这是为什么呢?
这是一种叫做键盘中断:键盘中断是指在计算机系统中,当用户按下键盘上的某个键时,会产生一个中断信号,通知操作系统有键盘输入事件发生。操作系统会相应地处理这个中断信号,并将键盘输入事件传递给相应的应用程序或处理程序。
中断是计算机系统中的一种机制,用于处理来自外部设备或其他程序的异步事件。中断可以打断正在执行的程序,引起处理器转而执行与中断相关的处理程序。中断机制使得计算机系统能够并发处理多个任务,提高了系统的响应能力和效率。通过合理利用中断,可以实现设备驱动程序、操作系统服务等功能。
具体后面再说。
既然操作系统可以接收键盘输入的abcd键的时可以中断访问,当然组合键ctrl+c当然也可以识别,当我们按下Ctrl+C时就会查看当前前台是否存在运行进程,有则将他关闭,那么ctrl+c属于什么信号呢?
属于SIGINT、2号信号
验证一下?
先介绍个函数signal:
handler: 改变的行为(函数地址
返回值==handler;
传入的函数的唯一的参数sig是改变执行行为的sugnum信号编号。
这个函数可以改变进程接收到signum信号的默认行为。
打个比方:本来闹钟响了起床,你变成闹钟响了跳了个舞。
理论:执行该程序会一直循环打印我们的cnt累加数,当进程收到SIGINT信号时候,会调用CarchSignal函数。来运行程序。
按下Ctrl+C:
验证了我们的Ctrl+C就是2号信号,我们改变了当前进程接收到2号信号的行为,打印了“进程收到信号:2”,然后继续运行,并不退出程序,我们成功改变2号信号的退出进程行为。
我们介绍一些是我们用户可以使用的用户级接口函数
pid_t pid :发送目标进程的pid编号。
int sig :需要发送的信号。
记得有个kill -9 pid的命令吧,其实底层就是用这系统接口。
启动kill程序,给一个进程发送9号命令,强制杀死他。
这是我们自己的kill命令,让我们使用自己的mykill杀死其他进程试试
这个命令可以理解为对kill命令的上层封装,哪个进程调用该函数,发送的信号就是对他自己作用的。
写个伪代码
int raise(sig_t sig)
{
int ret=kill(getpid(),sig);
return ret;
}
就是一个封装的函数罢了。
这个更过分也是一个封装函数,都不用指定信号类型是什么。谁调用直接向他发送6号信号。
void raise()
{
kill(getpid(),6);//raise(6)
}
还记得管道通信的时候吗?四个情况:
怎么被强制杀死的呢?其实就是OS意识到一个管道文件的读端被关闭了,而写端还写往管道文件中拼命写入数据,这是一种无用功,资源浪费,所以OS会向写端进程发送信号强制杀死写端进程。这个信号就是13号信号,我们写个父子进程验证这段逻辑。
子进程写入数据,父进程读取数据,我们把父进程的读端关闭,然后waitpid等等子进程结束,使用waitpid函数去取子进程退出信号。
运行程序:
确定了当读端关闭,进程收到的是13号信号。
这是一种异常发出的信号,我们的软条件还有一种是主动的要求OS向进程发送信号
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动 作是终止当前进程。但我们一般都是吧这个闹钟函数的行为更改,改变进程运行某段时间后就会跳到函数中打印一些日志信息。
运行程序
发现进程运行后五秒打印了日志。发现后面再也没有打印日志了,这也说明进程写入到pending表的信号在执行方法时就会删除信号标识。
但是我们希望每五秒打印一次日志,所以我们修改函数catchSiagnl.
执行行为的过程中再次标记后5秒再次发生闹钟信号
这样我们的定时日志就完成了。
软件条件可以产生信号的一个常见例子是计算机程序中的事件触发器或信号处理器。这些程序可以通过特定的条件或规则来检测输入,并在满足条件时生成相应的信号。
什么是硬件异常?
举了个例子:
我们访问野指针空指针的时,进程会立刻异常退出,但是我们为什么从来不探究一下为什么退出呢?
一般而言,发送信号后进程就会终止进程,你也可以设计进程不被终止,但是他也无法再去执行后序代码,当进程离开CPU资源的时候,会带走上下文文件,状态寄存器中的位图也是属于这部分,如果进程没有退出,那再一次使用CPU资源时候,OS还是会发现有异常,继续先进程发送异常信号,然后进程再离开资源不运行后序代码也不退出,周而复始的浪费CPU资源,这是一种资源浪费。
非法访问也是硬件错误产生的信号。
我们在对页表查找时候MMU就会找不到对应的物理地址,在MMU这个硬件中也存在寄存器,硬件大部分都有属于自己的寄存器。这些寄存器中也保存这状态位图
一样的我们也可以设置该进程收到非法访问信号时候不退出进程,这个野指针的访问信息不会被保存在特定的位置,操作系统无法执行后续的指令,会一直卡在报错位置。