引言:
北京时间:2023/4/23,最近学习状态不怎么好,总是犯困,没精力的感觉,可能是病没有好彻底的原因,也可能是我内心因为生病而认为摆烂理所应当,反正最后导致摆烂,课现在越来越跟不上了,并且刚刚蓝桥杯出成绩了,我肯定不是帮我自己看的,好奇看看而已,哈哈哈!主要是没怎么做题,大多数的时间都花在上课和总结博客上了,但是不怕,来年再战,我们才大一,还有时间,不怕,哈哈哈!所以今天我们就一起来学习一下新知识,有关信号产生等知识!
深入信号有关知识
在之前的学习中,我们就学习了一些有关信号的知识,例如在谈进程终止的时候,进程的退出码和退出信号,此时进程的退出信号就是我们该篇博客要谈的信号,我们一起深入看看信号是如何产生,如何执行,和如何保存等知识
什么是信号
在日常生活中,存在着大量的信号,特别是随着年龄的增大,我们能明白的信号就越来越多,像基本的红绿灯,闹钟…等,这些就是日常生活中的信号,通过这些信号规范人们的一些行为,所以我们通过这点可以明白,当一个信号没有产生之前,人们就已经拥有了处理该类信号的能力,这个动作是后天培养形成的,不是天生就有的,所以明白,在系统内部也是一样,当操作系统发出某种信号给进程的时候,该进程就拥有了这种信号对应的处理能力,具体原因如下述所说:
Linux系统下查看信号指令:kill -l,得到下图:
如上图所示,我们明白各种信号都具有相应的编号,并且通过位图有关的知识,此时我们知道,传送信号的本质就是在传送位图结构,通过位图结构的形式,此时就可以通过相应的判断语句和按位与的知识,进行特定的功能设定,如果此时参数携带了对应的信号,那么它就会满足相应信号对应的if语句,此时该参数就可以执行对应if语句中的代码(功能),这样就完成对应的信号被执行,所以这也就是为什么进程拥有处理对应信号的能力,因为本质就是程序员在设计进程的时候,已经就通过相应的判断条件和具体判断条件中的代码将每一个信号进行了编号和设定
信号为什么需要保存
首先,我们要明白,系统内部和日常生活中一样,无时无刻不存在各种信号和执行信号的程序,并且明白,信号是随时产生的,这个信号什么时候产生我们是不知道的,所以在信号产生前,人/程序可能不能立马执行这个信号,因为人/程序此时可能在执行别的信号对应的功能(具体以优先级排序),但是这个信号我们也不能漏掉,等执行完该信号就需要接着执行对应的信号,所以这个信号必须在后序合适的时候被处理,那么此时这个信号就需要被保存起来,这样才能是程序正常运行或者说是生活正常进行,并且明白,信号的产生对于进程来说是异步的,表示的就是信号的产生和进程没有任何关系,信号产生的同时,程序也在执行,而不是程序必须等待相应信号的产生才可以运行
进程该如何管理这些已经产生但未被处理的信号呢?
简简单单,还是先描述,再组织,用位图结构去描述一个信号,并且此时要明白,因为使用位图的结构来表示各种信号,所以发送信号的本质就是写入信号,直接修改特定进程pcb的信号位图中特定的比特位,由0变成1,此时表示的就是对该进程发送了某种信号,所以得出结论,位图结构中比特位的位置表示信号的编号,比特位的内容表示是否收到信号,并且要明白,是只有操作系统可以对该信号进行写入,也就是明白,当操作系统想要对某个进程传输信号的时候,本质上只是对进程pcb上的某位图结构进行修改,具体如下述所说:
具体来说,操作系统通过在目标进程的 PCB 中设置一个名为“信号位图”的数据结构来表示是否有某种特定信号需要被处理。这个位图是一个二进制数组,其中每个比特位表示是否有相应的信号需要被处理。当操作系统向目标进程发送信号时,它会修改目标进程的信号位图来指示有一个或多个信号需要被处理,当目标进程收到信号时,它会检查自己的信号位图以判断是否有该信号需要被处理。如果有,该进程将执行相应的信号处理程序来响应该信号。因此,在操作系统中,通过改变进程 PCB 上的信号位图来表示传递信号。
信号的产生
明白了上述知识,此时信号传递的本质我们就搞定了,接下来,我们就来看看与信号产生有关的知识,首先明白一点,就是当一个信号产生之后,一般有三种处理信号的方式:1.执行默认动作 2.忽略该信号 3.执行自定义动作,就类似于日常生活中,你调了一个闹钟,当这个闹钟响了的时候,你会有不同的执行结果,1.起床 2.继续睡 3.重新设置闹钟,所以同理在操作系统内部,当一个程序收到了信号之后,这个信号可以有不同的执行方式,不一定要按照默认设定的动作执行,如下:使用 signal() 接口,就可以更改对应信号对应的执行动作,实现让信号拥有多种不同的处理方式
具体使用方式:
头文件:#include<signal.h> 调用方式: sighandler_t signal(int signum, sighandler_t handler);第一个参数signum表示的是特定的信号,第二个参数handler表示的是对应参数执行的自定义处理动作,也就是一个函数指针,通过第二个参数这个函数指针,让对应的信号可以通过该指针找到对应的函数,进而执行该自定义动作,具体代码如下图所示:
运行程序,现象如下所示:
可以看出,此时当程序再次收到2号信号(也就是Ctrl+c
)信号时,并没有让该进程终止,而只是打印了一条语句,也就是我们自定义实现的函数接口,表明,该进程默认的2号信号对应的动作已经成功被signal()
接口替换了
键盘产生信号
我们平时在使用键盘进行输入数据的时候,计算机是如何知道我输入的数据是什么呢?此时操作系统是通过按压键盘让CPU产生硬件中断信号的方式,让CPU获取到对应键盘输入的数据,然后进而进行处理和存储,具体如下图所示:
系统调用接口产生信号
第一个系统调用接口:kill()
,功能:向目标进程发送特定的信号,具体使用方式如下图所示:
头文件:#include<sys/types.h>#include<signal.h>调用方式:int kill(pid_t pid,int sig);此时明白了该接口的使用方式和功能,此时我们就可以自己实现一个终止某个进程的代码了(只要我们将9号信号传递给对应的进程pid就行),具体代码如下:
此时使用了该代码,我们就可以运行该代码,然后将对应的信号(9)传递给一个正在运行的进程,此时就可以直接让这个进程终止,如下图所示:
此时根据对应的进程pid,使用signal
程序中的kill()
系统调用接口,此时就可以将对应的9号信号传递给对应的进程,让该进程被终止
第二个系统调用接口:raise()
,功能:发送一个指定信号给调用程序,具体使用如下图所示:
接口调用方式:int raise(int sig);
表示的就是向调用该接口的程序发送指定的信号,具体代码如下图所示:
此时上述代码表示的意思就是使用raise()
接口循环向该程序中发送2号信号,然后因为我们又使用了signal()
接口将2号信号对应执行的默认动作改成了myhandler()
函数,所以此时该程序运行起来的现象就如下图所示:一直循环执行handler()
,也就是一直获取到2号信号
第三个系统调用接口: abort()
,功能:直接终止一个进程,具体使用方式如下图所示:
具体代码如下图:
运行现象如下图所示:
表示当执行完该接口语句的时候,此时程序就退出,导致后面的语句并没有被执行,所以表示当执行了abort()
接口后,该进程就随着退出了
软件条件产生信号
字面意思,软件方面的条件,举个例子,我们在之前学习有关匿名管道知识的时候,我们知道,当我们将进程间通信的环境搭建好了之后,两个进程之间完成通信还受到了读写规则的约束,比如,一个进程需要读取数据就必须等待另一个进程向管道文件中写入数据,并且当一个进程的写端被关闭之后,操作系统为了提高效率,就会将对应读端进程给关闭,此时关闭该进程使用的就是13号信号 SIGPIPE ,所以此时的13号信号表示的就是一个软件条件信号,表示的就是有某一个进程不满足对应的条件需要被终止,此时为了终止这个进程,就需要产生对应的软件条件信号去终止它
所以接下来我们认识一下新的软件条件信号,SIGALRM信号和产生该信号的 alarm 接口,具体使用方式如下图所示:
接口调用: unsigned int alarm(unsigned int seconds);
表示的作用:设定一个闹钟,也就是告诉操作系统在多少秒之后,给当前进程发送一个SIGALRM
信号,并且注意:SIGALRM信号的默认处理动作是终止当前进程,具体代码如下所示:
如上图代码,此时可以让该接口在规定的时间内(1秒)发送一个14号SIGALRM信号给该程序,进而让该进程终止,此时利用这一点,我们就可以计算一下我们的系统在一秒中内可以就算几次,并且可以使用该接口自己设计出一个闹钟,获取到该程序对应剩余闹钟响起的时间,所以14号信号SIGALRM信号就是一个经典的软件条件产生的信号
硬件异常产生信号
硬件异常可以产生信号,例如,当计算机系统的硬盘驱动器出现故障时,它可能会发送一个信号给操作系统来指示发生了错误,类似地,其他硬件组件如内存、CPU等也可能在出现异常情况时向操作系统发送信号,这些信号通常用于告知操作系统需要采取一些措施来解决问题或仅仅是通知用户有问题发生,这也就是为什么野指针问题,访问越界问题最终程序会崩溃,本质就是因为收到了一定的信号,该信号会导致程序终止