引言:
北京时间:2023/6/26/13:35,昨天12点左右睡觉,本以为能和在学校一样,7点左右起床,设置了7点到8点30时间段内的4个闹钟,可惜没想到啊,没醒,直接睡到了12点,看来下次不能给自己太高的期望,哈哈哈!在家没办法呀,习惯睡到12点了,想要解决这个问题,最好的方法就是早睡,今天争取在11点前睡觉,然后看看明天能不能在8点前起床,作息必须调整成和学校一样,这样才不会耽误我们更新博客,上篇博客是这个月16号就要写完的,可惜硬是拖到25号才发,由于考试和放假等原因,摆烂至今,哎,无奈!但,一切还有待挽回,只要把这个暑假给利用好,那么就无伤大雅,无论是为了今天能够早睡,还是实现自己的期望,正式进入该篇博客的主题,剩余相关信号产生和信号保存、信号处理等知识讲解。
信号产生剩余知识
上篇博客由于各种原因,剩下了许多知识没有讲解完,那么只能在该篇博客中进行回顾和深入,但本质都已经讲解过了,只不过是进行一定的深入和概念转换而已,所以接下来,我们就把先把上篇博客剩余有关知识进行讲解,然后再学习有关线程相关的知识吧!
什么是云服务器
谈到云服务器,当时在学习Linux时,购买了一个腾讯云的云服务器,至今已经快要过期了,当时只购买了半年,早知道买一年甚至两年了,续费价格挺让我苦恼的,哈哈哈!不过问题不大,在该篇博客我们只要知道,云服务器是基于虚拟化技术的计算资源,通过云平台提供灵活的计算、存储和网络资源。可以为用户提供一种高效、可靠和经济的方式来托管应用程序、运行服务以及处理大规模的计算任务,优势在于灵活性、可扩展性、弹性伸缩以及简化的部署和管理过程。云服务器这么好用,也这么有用,可惜我对其功能的使用方式可以说是一窍不通,只知道用来登录一下Linux系统,哈哈哈,想笑,具体能玩出什么花样来,在没接触过之前,我也不晓得,但是我知道肯定很厉害,具体需要我们深入学习,等接触了网络等知识时,才能有一定的自我理解,并且云服务器相关知识在之后的博客中,我们也会重点了解,这里不过多介绍。
获取core dump标志位
无论是在上篇博客,还是之前学习有关进程控制和进程等待相关知识时,我们都知道,可以通过waitpid获取到一个进程的退出状态,并且由于该退出状态是一个位图结构,该位图结构又包含了退出信号、退出码和core dump标志位等信息,所以此时我们就可以通过对该退出状态的位图结构进行位运算,得出对应的退出信号、退出码和core dump标志位,如下代码所示:
注意:如果是使用云服务器,同理,一定先要将对应的核心转储功能给打开,只有打开之后,发送对应的Core行为状态信号,操作系统才会发生核心转储功能,如上图所示,具体关闭和打开云服务去上的核心转储功能,在上篇博客中我们有详解介绍,这里就不多做赘述,并且core dump标志位,我们具体也见到了,有关core dump相关知识,我们就讲到这里,下面正式进入另一块重要知识学习。
什么叫做阻塞信号
注意,此时的这个阻塞信号和我们之前学习的有关进程状态(阻塞,挂起)毫无关系,该知识是一块全新的知识,并没有任何相似知识,要进行区分,不过,这块知识和我们之前学习有关信号在进程pcb中存储肯定是有一定关系的,在之前的博客中,我们就学习了相关信号执行和存储等知识,当然有信号存储,信号执行,那么肯定存在着信号阻塞或者是信号忽略等概念的存在,所以下述知识也算是在完善信号相关知识,具体如下所述。
信号其它相关常见概念
同理,在之前,我们学习了相关信号执行动作(默认、自定义、忽略),但是在计算机中,信号在被执行中,还存在着许多的概念,如信号递达,信号未决,信号阻塞等!虽然听着这些概念非常的高大上,但本质这些概念分别表示什么内容,我们都已经熟练掌握了,如下:
·信号递达(delivery):执行信号的处理动作,当然也就是上述所说的三种信号执行动作,默认处理动作,自定义处理动作,忽略动作本质都是一种信号递达行为,总的来说信号递达就是收到信号后会执行某一个动作
·信号未决(pending):信号从产生到递达之间的状态,当然也就是之前所说信号产生之后,操作系统将信号编号写入进程pcb对应位图结构,等待进程执行该信号的过程
·信号阻塞:同理,进程可以选择阻塞某个信号,被阻塞信号将保持在未决状态,直到进程解除对该信号的阻塞,该信号才可被递达(注意忽略不叫做阻塞)
那么具体如何理解进程阻塞信号呢?
本质就是因为在信号递达过程中,操作系统可以向进程pcb中写入任意一个信号,那么这也就决定着,操作系统可以选择让该进程阻塞某一个或者多个信号,这样做的意义在于,当该进程会导致操作系统产生某个信号,或者操作系统会发送某个信号给该进程,那么由于该进程对该信号设置了阻塞,此时该进程就允许不再递达该信号,直到该信号被解除相应的阻塞状态,该信号才允许被递达。注意:如果某一个信号处于阻塞状态,那么该信号一定未决,但信号阻塞并不会影响信号未决(好比你不做作业,但是并不影响老师给你布置作业),并且信号阻塞也叫作信号屏蔽。
如何理解忽略动作
在日常生活中,忽略动作表示你知道某件事需要你去完成,但你却消极对待,不重视,不关心,例如:老师布置了一份作业,你将作业记在了笔记本上,但你在完成它时,就直接用线划掉,表示自己完成了该作业。而在Linux操作系统看来,忽略动作,本质就是使用signal系统调用接口让某一个信号的默认执行动作从终止进程变为忽略,也就意味着忽略动作就是将某个信号通过signal函数接口将其默认动作设置为SIG_IGN,当然此时的SIG_IGN表示的就是信号忽略。同理注意:虽然信号忽略和信号阻塞都是什么都不干,但是信号阻塞,那么该信号一定是处于非递达状态,而信号忽略,该信号却是处于递达状态。
信号保存
搞定了上述知识,此时进入信号相关知识中最后一部分,当然也是最重要的一部分知识,信号保存,无论是在上述讲解中,还是在之前的学习过程中,都一直强调进程保存信号的知识和操作系统向进程写入信号的知识,操作系统是如何写入信号,我们通过对位图结构的理解,有了一定的了解,但是对于进程pcb中的位图结构具体是如何保存该信号,我们并不清楚,所以接下来我们就深入探讨一下有关进程进行信号保存的相关知识吧!如下:
首先明白,对于操作系统来说,管理一个进程,最重要的就是管理该进程的进程pcb,而某一个进程的进程pcb是非常复杂的,其中包含了该进程对应的各种数据,内容较为繁杂,其中对于存储信号来说,在进程pcb(task_struct)中,存在着一张表,如下图所示:
如上图所示,此时我们就意识到,之前我们了解的有关信号保存,只是让对应的信号编号通过操作系统写入到pending位图中,同理pending位图中比特位的位置表示的就是对应的信号编号,比特位的内容表示的就是该信号是否被写入。并且发现除了pending表,还存在另外两种表,其中阻塞表表示该信号是否被阻塞,只有在未被阻塞的前提下,该信号才会发生递达,如果该信号被阻塞,那么就算该信号存在于pending表中,也不会被递达,最后就是自定义动作表,该表表示的是一个数组,该数组的数组下标同理表示信号编号,下标内容表示信号递达动作(函数指针),同理可以是默认动作,也可以是自定义动作或者忽略动作,具体该下标中的递达动作是什么,我们可以通过系统调用接口signal进行替换,但一般是SIG_DFL(默认动作)。此时明白了上述三张表分别代表什么之后,我们就可以发现,一个信号在进程中是如何被保存和一个进程是如何识别一个信号,也就是明确该信号应该如何被执行,是否阻塞,是否执行默认动作,并且可以很好的明白,底层实现代码本质就是在通过判断语句(if)进行判断,最终判断一个信号符合那种情况,然后被进程执行而已。
详解信号列表
什么是sigset_t
明白上述pending和block两个位图结构,是允许我们直接进行控制的,只不过由于它们属于位图结构,所以如果我们想要对其进行控制,就需要使用位图数据类型,当然也就是sigset_t数据类型,并且明白sigset_t也叫做信号集,而用于控制pending位图结构的叫做未决信号集,用于控制block位图结构的叫做阻塞信号集,本质来说,sigset_t就是用于控制内核中的block和pending两张位图结构表,但,由于我们自己进行位运算,容易出现一系列的问题,为了避免这个问题,操作系统提供了许多的位运算接口给我们使用,如下代码所示:
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
总而言之:上述几个接口就是用于控制位图结构,也就是对sigset_t类型的数据进行增删查改,可以通过上述接口设计出任意的符合我们要求的位图结构。
信号集读取控制函数
明白了上述知识,此时我们就知道,对于位图结构来说,也存在相对应的增删查改操作,可以使用相对应的接口对位图结构进行控制,所以同理上述所说,如果我们需要控制block或者pending表,就可以使用相对应的接口,但是此时会面临一个问题,那就是如何获取到block或pending位图结构,并且将其修改呢?所以接下来就涉及到两个信号集读取控制函数,如下所述:
1.sigprocmask
其中 sigprocmask
是一个用来读取并且修改block
表的函数接口,头文件signal.h,基本调用方式:int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
其中第一个参数how表示想怎么改,其中附带了三个参数选项,SIG_BLOCK、SIG_UNBLOCK、SIG_SETMASK
,其中SIG_BLOCK表示的是将某一个信号编号添加到阻塞信号集(block)中,而SIG_UNBLOCK表示的是将某一个信号从阻塞信号集中解除阻塞,SIG_SETMASK表示的是直接将一个位图结构替换阻塞信号集(block位图),具体如下代码所示:
从上图我们就可以发现,在sigprocmask中,该接口的第二个参数表示的是一个sigset_t类型的位图结构,该位图结构的目的就是用于控制block位图结构,通过SIG_BLOCK、SIG_UNBLOCK、SIG_SETMASK等选项实现与其对应的位运算,并且其中SIG_BLOCK表示的就是mask|=set
,SIG_UNBLOCK表示的就是mask&=~set
,SIG_SETMASK表示的就是mask^=set
,而最后一个参数oset表示的就是没有进行位运算之前的block位图结构,作为的是一个输出型参数,方便之后将位图结构复原(当然也就是解除阻塞状态或者是重新将某个信号编号设置为阻塞状态),本质也就是获取调用sigprocmask接口之前的block位图结构。
2.sigpending
明白上述sigprocmask接口是用于读取和控制阻塞信号集(block表),那么同理,pending表也需要被读取和控制,此时控制pending表的接口就叫做 sigpending
,同理头文件signal.h,基本使用方式:int sigpending(sigset_t* set);
显然此时通过该调用方式,发现,该接口只有一个参数,所以此时间接明白,该接口不允许我们直接修改pending位图,而只支持我们获取pending位图,和sigprocmask接口不同的是,sigprocmask接口不仅支持我们获取block位图结构,还支持我们修改该位图结构,本质也就是操作系统支持我们对某个进程进行阻塞某个信号的操作,而不支持我们直接向进程写入信号的操作,所以该接口的目的非常的简单,为了提供我们pending位图结构,也就是可以让我们知道,目前该进程中有哪些信号正处于pending(未决)状态。如下代码所示:
当然由于上述我们在屏蔽信号时,只屏蔽了2号信号,所以才会导致发送2号信号该进程不被终止的同时,pending位图的2号比特位由0置1,当然同理,我们也可以将1到31号信号全部给屏蔽掉,但是此时会发现,有的信号就算是被屏蔽了,进程也会执行该信号的默认动作,所以明白,对于操作系统和进程来说,有的信号不允许用户自定义控制,就算该信号被阻塞,其依然需要执行。