花钱买不到系列-linux信号[2]卷

上一篇信号,我们知道了,进程需要保存信号,信号究竟是什么?什么又叫做发送信号呢?什么又叫做进程保存信号呢?那么,实际上呢?给大家一个基本的思考切入点,就是我们常见的信号呢,一到31,一共31个,那么这31个信号进程要保存,那么我们可以要保存其实就是是否收到了对应的信号,所以这里有两组概念,一个是否收到信号。第二个对应的信号是什么?所以呢,我们就可以采用位图的方式进行我们对应的信号相关的处理。所以有一张位图之后呢,那么比特位的内容那么是否为零为一代表的就叫做我们是否收到信号概念?

那么,比特位的位置啊,第一个第二个一直到第n个那么代表的就叫做我们哪一个信号,所以呢,我们发送信号呢,那么本质上就是去修改。进程PCB当中的信号位图结构,当我们理解到这点时候呢?那么一次性的就把信号这个窗口纸就全部捅破了。那虽然有也有一大堆的概念,我们其实不懂啊,但是我们对于信号发送,我们就有了自己的观点,所谓的信号发送本质上它,不叫发送,而应该叫做信号写入,那么所谓的发送信号呢?本质上就是修改特定的位图,由零变一就完成了发送信号。那么,再衍生出来的呢?那么,我们的进程PCB呢?它是内核数据结构,那么只有谁能够修改这个内核数据结构呢?答案是,只有操作系统。既然只有操作系统有这个权利,

那么发信号的这个角色谁来发呢?就一定是操作系统。虽然我们未来呢,会讲各种各样信号发送的过程,但是从内心里我们永远要记住信号的发送都必须直接让操作系统发。虽然未来我们可能经会见各种各样的发送信号的场景,但最终所有的场景都必须转化为操作系统。好,所以当我们体会到这点的时候呢,那么基本上我们上节课核心的内容呢,大家也基本上已经全部都能够get到了啊。

在上一篇给大家在写代码的时候呢,就写了一个简单的循环代码,然后通过我们对应的ctrl c终止了这个进程。所以我们也终终于能理解ctrl c呢,它终止进程呢,那么本质上呢,其实是终止的,是我们对应的前台进程。那么也就意味着呢,那么这个ctrl c呢,实际上是通过信号的方式来终止我们对应的进程的好。好同那么所以ctrl c是一个我们对应的信号。

我们来进入下一个概念。正式的来谈一谈产生信号都有哪些方式,这是今天要做的一个重要的工作。

那么有哪些呢?首先第一个,信号产生的方式呢?那么第一种就是上一篇讲的,通过键盘产生产生各种各样的信号。

先把我们常常见的信号的产生呢?那么有一些。键盘的组合键先给大家一说,把这个一说呢,然后紧接着把第二种,第三种,第四种产生信号的方式都教给大家。讲完之后,我们再回过头谈一谈衍生出来的一个子问题啊。

那么我们来看一下,下面这份代码是熟悉到不能再熟悉,就最近一份测试代码。

编译之后呢,我们运行起来呢,它当前呢,就开始按照我们的预期就进行我们对应的死循环跑了啊。

上节课我们讲了ctrl c啊,那么直接就是终止这个进程没有问题,当然呢,除了ctrl c之外还有一些组合键呢,比如我们的ctrl \,那么我们叫做ctrl 、,那么也可以直接终止这个进程啊。

那么我们的ctrl \,你们这里可以会出现quit,我的是出现退出,一样的,退出的英文就是quit,

然后我们把它再跑起来,下面呢,我们再新起一个我们对应的终端随便吧进程pid打出来。

那么其中呢?我们的ctrl刚刚ctrl \?它呢,就叫做我们那么发送我们的三号信号,输出退出啊。所以呢,那么键盘本质上那么是一个硬件,它通过我们的组合键?被操作系统识别之后呢?操作系统将该信号解释成组合键,解释成信号向目标进程发,信号目标进程呢?在合适的时候就处理这个信号,而其中呢,对于二号或三号信号的一个处理动作,默认就是终止进程。

好,再继续来进入下一个。那么产生信号的第二种方式好,那么第一种我就说完了啊。那么第一种呢会衍生出一个话题。这一个话题呢,我先不展开来说,然后稍后呢,我再来重点谈一谈,它那么下面呢,我们来谈第二种话题,就叫做我们呢,其实除了你可以通过键盘。向前台进程发送信号,所谓的前台进程呢,就是它会影响你的shell,那么因为linux下规定,用户进行shell交互的时候,只允许有一个。

前台进程默认情况下bash本身也是一个进程啊,那么默认情况下呢,就是这个bash在进行运行。而当我们执实际上自己在运行时,所以你执行这些东西就没有任何用了,你的进程变成前台了,而bash自动被切到后台。好,所以呢,那么你在查的时候可以通过命令来查。

命令:ps ajx | grep main(比如你后面想查其他进程,main就是要查的进程)。

然后呢?你会看到呢?main它的父进程是4067,ps ajx | grep 4067,那么它就是我们对应的。这4067这个进程就是bash的进程呢,它现在变成变到了切到后台,那么切到后台呢,bash所以它对应的这个s后面就不再带加号了啊。

这个我们之前呃,就是关于它的状态部分我们提过,不说了来,所以呢,那么因为它是前台,所以ctrl c ctrl \,你都可以直接终止这个进程啊。那我现在呢?就是明白你说的这个了,然后呢?我们现在要考虑的一个问题就是,除了你说的这种方式之外,还有没有其他方式可以产生信号呢?

那么我们第二种呢,我们就叫做我们可以通过系统调用。通过系统调用发送信号,那么第一种,当然就是通过我们的键盘发送信号。

第二种呢?通过系统调用发,我们有三个系统调用给大家介绍一下。第一个叫做kill

有一个系统调用就叫做kill它的作用呢,就是send single to a process。向目标进程发送信号,其中呢?我们要能重点理解的是,首先发送信号的能力是操作系统的能力。是操作系统向目标进程发信号的。但是你有这个能力并不一定你有使用,你这个能力的权利。那么一般呢?是谁决定使用操作系统向目标进程发信号呢?那么答案是用户。

这个道理呢,就有点儿像呢,你们未来呢,是有能够写代码的能力的。但是是谁使用你这个能力,那么答案是不是你使用这个能力,而是你的老板?你的老板让你干什么你就要干什么,这就叫做操作系统,它具备这个能力,但是它的能力不是为自己准备的,而是为用户准备的。

然后我们学习这些知识不,其实不仅仅给自己在学,其实你未来呢,是要那么给公司提供我们对应的能力的啊,所以呢,我们一定要记住,既然操作系统它有这个能力,但是它一定要对外提供自己的能力,它就只能通过系统调用接口的方式来让我们的用户向目标进程发送信号,因为操作系统不相信任何用户。那么他呢,对外提供服务的时候呢,只能采用系统调用的方式。

第一个参数,就是你要向哪个进程发目标进程的PID。第二个参数呢,就是你要向目标进程发几号信号。

我们来自己写一个kill。

需求:我现在写一个一直运行的程序,用我自己写的kill命令,以向目标进程发送任意信号啊那叫做kill系统调用函数。

kill,系统调用代码。

测试代码。text.c 

  测试结果

所以呢,相当于我们自己实现了一个kill命令,所以,我们要发送信号的时候,不一定非得采用我们所对应的叫做键盘,也可以直接采用我们对应的系统调用。用接口来向目标进行发信号好,那么这里就可以得到两点知识了,第一点我们平时用那个kill命令。实现的时候底层用的就是这样的kill系统调用。第二个你呢,那么我们更加印证了信号的发送是由用户发起,而操作系统执行的。所以我们才调系统调用。

所以呢,通过系统调接口向目标进程发信号,我们可以。自己写了一个叫做kill命令,当然呢,你也可以挑一下其他信号。

下一个叫做raise接口?那么它呢?其实是发送一个信号图给调用者,它参数只有一个,就是信号那么没有了。

它是给自己发送指定的信号。所以呢raise这个接口,很简单,叫做给我们的自己发送我们任意信号。

上面就一个简单的一个程序,跑起来之后5秒钟后,raise就会给自己发一个3号信号。把自己干掉

三号信号呢,默认的处理动作叫做core这个呢代表的也是进程退出啊,有人说那这个你一次说的给。term,三号信号呢,它也是终止那这俩终止有什么区别呢?这是第一个问题。第二个问题有人说。那你讲了这么多信号,好像所有的信号处理动作都差不多啊,那有什么意义呢啊?不要着急,这个问题后面都会说啊。

我们看到这个raise,它就是自己给自己发,那么就是我们对应的封装接口,如果让你们自己写一个raise。用kill来实现。

实现逻辑就是第一参数就是自己的进程id,所以getpid()获取一下自己的i进程d就可以了。 

        我们再来一个,abort。它是一个在C语言当中给我们提供的一种方式,一般我们称之为终止进程的方式。

about它的作用是引起进程退出。

那么它怎么退出的呢?这个接口呢,是自己给自己发指定的信号。

那么abort是对应我们信号的6号。所以是发指定的信号,用kill封装一下就是如下

基于这一点呢,我们终于知道了,第二个叫做我们可以通过系统调用。向目标发送进程来发送,这个就没有问题啊,那么下面呢,我们需要花点时间来再谈一个问题叫做啊。那么,关于信号系处理的行为的理解。

当我们一个进程收到信号之后,大部分情况下,进程收到信号,默认的处理动作。这是第一个第二个那么信号的意义呢?

我好奇,你说这信号每一个呢?它就是收到之后我就直接终止了。那终止了,有什么意义呢?那为什么要就是要有这样这么多不同种类的信号呢?

我们信号的意义是不是由处理信号的动作决定的?在大家写c++或者java的时候,家肯定也听过一个东西叫做异常。

那么绝大部分你自己不论是写C++还是写JAVA,只要你抛异常。大部分大部分情况下,那么所谓的异常啊,一旦那么发出去的时候。基本上这个进程也就直接终止了。但是呢,为什么会有这么多种类的异常呢?所以信号的意义呢,并不是由处理动作一样,或者不一样来决定。信号的意义在于信号的不同代表不同的事件。对事件发生之后那么。处理动作可以一样。这个能理解吧。

所以,好比我这个进程是终止了,但是呢,那么我是收到几号信号终止了呢?那么不同的信号可能代表的就是不同的终止原因。最后只要出信号,那我们就大不了我们都终止。这个也不能有问题吧?

我们接下来要谈的第三,第四种呢?就会更加佐证这样的观点。

一会儿我们再回过头重新看一下这句话啊。下面呢,我们再来火速谈一谈第三种产生信号的方式。我们就称之为叫做硬件异常产生。来说一下啊,信号产生不一定非得用户显示的产生。不一定非得让你动动手指头ctrl c ctrl 、,也不一定非得让你动动手指头写代码,用系统调用。那么有时候呢,信号呢,它会在操作系统内部自动会产生啊,来下面我写一段代码。

你们认识这种代码吗?上面代码发生除零了。以前呢,一旦除零了,我们的进程直接就崩溃了。

也如我所料啊,是进程直接就终止了。终止之后呢,它打出了一段错误,我的叫浮点数例外,是因为的系统语言是中文,你们的会叫floating point exception浮点数错误,那么用我们现在的这个话说呢,它就叫做我们除零了。

其实呢,除零呢是一旦出异常进程就退出。学习语言的时候呢,我们见到的现象也是我们当时认知的边界啊,那么下面我的问题就是。为什么除零啊?除零会终止。那么在一个不太本质的观点,其实就是因为当我们除零的时候。我们当前进程会收到来自即操作系统的信号好。(我告知大家的)

来那么下面我们先来解决第一个问题,为什么除零它会终止这个进程呢?说了因为进程收到了来自操作系统的一个信号,叫"floating point exception"。是对应我们的信号表里面那个信号?

是8号信号,sig是信号,FPE是"floating point exception"的三个单词因为字母缩写,所以除0,操作系统就发了一个叫做八号信号,我收到八号信号,为什么要终止呢?

 

我们再来看一看,那么它代表的就是我除零了可能引发的一个我们对应的问题,这找一那么如果我们的信号是7,action这个动作是,core也是一种终止方式。core和Term这两种方式的差别,我一会就会说。也叫做终止进程。

换言之呢,所以我们收到这个信号时,那么操作系统在合适的时候就要终止这个进程好。那么这样呢,那么有两个问题,第一个啊,那么你怎么证明啊?当前你进程就收到了所谓的我们叫做的8号信号呢,好。

开始证明:信号1卷的时候,我们曾经讲过一个我们简单的接口叫做signal啊,它有一个作用,就它可以捕捉任意信号,操作系统未来让这个进程处理时。默认处理动作是终止进程,现在我想让它捕捉一下我们对应的信号。来看看是不是我说的8号信号。

操作系统会给我这个进程发送一个我们对应的八号信号,默认处理动作是终止,可是我现在呢,把这个我们fpe这个信号,给我们自定义捕捉了。

就是自定义了,那么我们未来就不会再执行你终止进程的任务,而执行handler,打印这句话。

如果没有除零是不会被调用handler,handler它仅仅是注册了一个未来的方法。当某种条件就绪或发生的时候呢,我们就自动去调用曾经注册好的方法。

以前呢,我们没有注册默认使用的,就是除灵时终止。

跑起来后,那么这里就又有问题了。进程怎么不退出了?一直往后打,你们可以你们可以试一下,看看是不是一样的效果。

这个进程收到确实收到八号信号了。因为一个进程,对一个信号的处理动作只能设置一种啊,那么现在也对应除0了,以前呢?是八号信号默认处理动作是终止。现在变成了你要默认处理动作,是handler,也就是打印这句话,那打印这句话的时候,你这个进程终止了吗?

对不起,为什么不终止了呢,因为你的处理动作当中没有终止进程这样的动作啊。这是第一个。那么换言之呢,我们如果想收到信号,不想让进行终止呢,我们目前是可以对这个信号做自定义捕捉的。

自动定义动作之后呢,那么你可以不让进程它终止,这是其一。

其二呢,大家发现。我不是就除了一次零吗?你操作系统怎么跟编诗一样,一直在给我发送八号。那么当前呢,我只除了一次零,但后续呢,我会一直收到所谓的八号信号。那么所以这是一个现象啊。

那么下面呢,我们把这个问题复盘一下,然后我们再往根儿上再追一追啊。首先我们确实,为什么我们以前除0的时候,我们进程会异常崩溃呢?现在就知道了是因为一旦除0了,本质上除0呢,是由操作系统来给目标进程,它会把除0动作识别成我们对应的信号。然后把除0的这个动作解析成信号发送给我,我收到之后,就会执行当前执行默认动作,默认动作就是终止,所以我就退出了。我可以捕捉它,通过捕捉我也证明了,确实我收到的是八号,但现在呢,有两个保留问题。

第一个问题,就是就是我只发了一次八号信号呀,我不是只除了一次0吗?怎么我们在进行后续处理的时候,我虽然捕捉了,但是它捕捉的时候疯狂的在进行,我给我信号,我这个捕捉函数一直在被调用啊,这是为什么?那么这是第一个。第二个呢?除0的时候,当前进程会收到来的操作系统收那么给它发的8号信号啊。

现在第二个问题就是啊,操作系统,如何得知应该给当前进程发送?8号信号的。这句话翻译过来的那么说过来呢,就是操作系统怎么知道?

所以我现在就特别好奇啊,你诉我一个结论,说进程收到了,操作系统的信号没问题。可是操作系统,它怎么知道我除0了,然后因为知道我除0了,然后决定给我发送所谓的叫做八号信号呢?操作系统怎么知道的?我进程调度的好好的,那么我在我的代码当中呢?正常的在进行执行,你这不是除零了吗?那么操作系统怎么知道的呢?

这块儿,就跟我们对应的硬件是有关系的啊,下面呢,我们花一点点时间来谈一谈,叫做如何理解除零。

好,下面我们来看一看啊。那么这是我们对应的CPU,这是你当前的代码。CPU内呢,是有很多的寄存器的。比如说通用寄存器eae、bec、edx这样的寄存器啊。那么这就是CPU内的相关的寄存器

其他的我就不画了。那么,当我们有了这个之后呢?那么接下来呢?我们要执行的一段代码呢?那么,比如说我们int a = 10  和0,取到寄存器,我们的CPU内,除了要能够进行数据保存。将来那么一定是充满大量的计算的,所以每一次在CPU上进行对应的运算时。那么它还得保证你这次运算有没有问题,所以CPU内部有一个东西叫做状态寄存器。

那么状态寄存器当中因为它也是一段儿数据,只不过这段儿数据呢它不属于你代码当中的数据。而衡量的是你这一次的运算结果,比如说呢,你这次在运算运算的时候呢,如果当前呢,你是除零,那么计算机除零就相当于除无穷小。相当于呢,10除零就直接数据无穷大,那么最后呢,就会引起状态寄存器当中叫做溢出标志位,由零被设为一。

因为寄存器呢,那么它也是由二进制序列构成的,它里面的比特位呢,都有它的含义,其中呢,

状态寄存器当中有一个溢出标记位。溢出标记位能默认为零为零,就代表这次运算呢?是没有问题的,没有溢出的。而如果你除零了,那么CPU运算的时候立马就直接发现要溢出。溢出之后,状态计算器被置为一,就代表的是本次计算是处于溢出状态,那么所以呢,计算完之后的这个运算结果没有意义,不需要被采纳。

10除0呢,其实是可以计算的。只不过结果是不对的啊,这就好比10除0,

我给你写个五,你怎么知道10除0=5是对的呢?或者不对的呢?那你是不是只能够衡量它的计算过程,有没有出错啊?所以CPU内有一个状态寄存器,当你10除0这个运算,那么叫做计算的时候呢?因为它是除零就相当于除无穷小,然后呢这个数据呢就会变得结果就变得很大,大到我们寄存器无法保存,所以CPU就说算了,我干脆不保存,因为这个计算结果不正确,所以呢,我直接将状态寄存器当中的溢出标记位由零改成一。证明本次计算是非法的,那么至此呢?我们就那么相当于发生了一个叫做CPU的运算异常。CPU它就触发了一个运算异常啊,然后呢?那么你说CPU发生了运算异常,操作系统要不要知道?为什么要知道,原因很简单,因为操作系统是软硬件资源的管理者。所以CPU呢?那么你的运算状态,这样的信息也是要被操作系统知晓的,当你出异常的时候呢,我们所对应的操作系统识要识别到这个异常。

操作系统呢?CPU告诉我说异常了,那我去看看,你说操作系统看什么呢?看态寄存器内部数据?那么操作系统是无法识别有没有出异常的?因为这些保存的都是数据,数据是中性的啊,但是呢,我们的状存器当中溢出标志位,操作性异常诶,这个标志位被置1了。被质1了怎么办?操作性能立马就识别到 CPU内抽错了,第二,是谁导致CPU出异常呢?是不是当前正在调度谁是谁引起的这个异常?那么是不是这个进程它就有问题?所以呢,操作系统呢,立马能够知道一硬件上CPU发生溢出了,那么二知道是谁导致的这个溢出?然后呢,操作系统再来向目标进程修改标记位,发送八号信号,所以这个进程收到报告信号后续再处理就终止自己了。

所以这就叫做为什么我们以前叫做我们以前讲那个学习C、c++的时候呢,为什么我们以前写的代码呢?有一个问题。就是程序呢,一旦出现除零我们的进程会崩溃,原因是一旦除零在硬件上CPU先出异常,立马被操作系统识别。操作系统将溢出标志位,这个异常转化成我们对应的信号,发送给目标进程。目标进程在后续合适的时候进行处理对应的信号。进而让自己退出。

好,那你算是说服我了啊,说服我什么呢?叫做如那么操作系统,怎么知道要发八号信号呢?就是操作系统怎么知道我除0了呢?啊,因为CPU会异常啊,那么CPU会异常呢?那所以操作系统它识别到行。那我就姑且没有问题了,

那么可是呢,刚刚还有一个问题。这个问题呢?就叫做如果我自定义捕捉这个信号了,那么我发现呢,我只除了一次零。你这货,怎么一直都在给我那么一直都在给我发八号信号呢?也就是说我刚刚在进行我们对应的运算时,发现他怎么给我发了这么多?

首先我们可以根据这样的一个事实呢,我们得出来一个另一个结论啊,这个结论就叫做收到收到信号。啊,收到信号那么那么不一定会引起进程退出,首先我们是不是通过它那么证明了,当我收到信号时。我的进程如果不想退,可以不退吗?答案是可以的。那么收到信号那么不一定会引起进程退出啊。这是第一点。

第二点,那么我们曾经呢,还说过一个东西,那么如果进程没有退出。那么没有退出。记住了,进程如果没有退出,那么它是不是就有可能还会被调度。

第三个,CPU内部的寄存器只那么只有一份,那么寄存器中的内容属于当前进程的上下。

那么说白了,就是CPU内的寄存器呢?它有一份儿,但是寄存器内的数据的内容呢?属于进程的上下文啊。那么所以呢,一旦出异常,那么你有没有能力或者动作去修正。你现在发现寄存器当中溢出标志位置一了,你有把它置零吗?你能把它置零吗?对不起,都做不到啊,那么为什么呢?因为状态寄存器是由CPU自己维护的啊,那么用户呢,没办法,也没有权利去改它啊。这是第一。

第二,所以你没有能力去维护和修正这个问题,但是你的进程又没退出,而且计算机还是你的上下文。所以,当进程被进行切换,有无数次我们对应状态寄存器被叫做保存和恢复的过程。当我们进程因为你没退出嘛,所以你照样还会被调度,你只要被调度,那么你对应上下文就要保存和恢复。每一次恢复的时候。都让我们的操作系统识别到了CPU内部的状态寄存器。中的溢出标志位。当你每一次你进行把你的上下文恢复到CPU寄存器上的时候呢?每一次一恢复,那么这个溢出标注都是一,因为你没改嘛,当你在退出时,或者你被切下去的时候,你要保存上下文,当你恢复上来呢,这个寄存器的溢出标注又是一。

所以操作系统,它又识别到这个那么有溢出标志,然后呢,那么它此时又会给你进行发信号。那么所以我们呢,因为不退出,就正常被调度,因为被调度,所以异常标志一直在,因为一直在,所以操作系统一直发。所以你才能看到那么这里一直都是死循环,一直都在打这个信号。

所以简单一点点呢,就是你没有你这个进程呢,那么异常了。你是无能为力的,你暂时也没什么办法。所以呢就是如果你还想调度,你不想退出,那行,那可能操作系统就要经常的给你发这个信息了。

所以默认情况下,大部分信号一出异常之后,大部分信号都是终止这个进程了啊,但是呢,我们很明显能感受到不同的信号,比如说我收到SIGFPE,我就立马意识到是不是出零了,对吧?

好,那么这是第二,这第三个。那同学们,所以本质上讲呢,你在语言上写的C语言代码C++代码,最终呢,那么它在底层呢,都属于硬件异常。然后呢?那么而导致操作系统那么将硬件问题转化成软件问题,向你的目标进行发信号,从而干掉你这个进程,这就是为什么在座以前代码一旦除0进程会崩溃的原因。

下面再继续除了这个例子之外呢,还有一个最常见的例子啊。

这个指针变量呢?当前指向为空。如果我们当前p等于null,看一下大家对于C语言掌握的怎么样了,int* 它会帮我们去定义一个变量,所以p呢是会开辟空间的,所以你把一个null(0值)放到这个里面呢,其实是不报错的,p因为它有空间。

但是你一旦仅引用呢*p,代表的是你要访问零号地址处所对应的,比如说四个字节访问零号地址,但一般进程是不让你访问的啊,好用它呢来代表我们那么之前所说的。这个问题呢,我们称之为野指针。

就相当于数组越界了,组越界了,还有遍历数组的时候咱们越界,还有指针访问的时候也指针问题,我们来研究一下它。在那个平台下面跑它大概率都会直接崩溃的啊。那我的问题就是他为什么会崩溃呢啊?

在语层面上,我们认知它的时候就说它为什么会崩溃呢?因为你也指针了。那我此时如果我再问为什么野指针就会崩溃呢啊?那有人说那这你这不扯吗?野指针就崩溃啊。

我以前就这样学的呀,那是因为我们之前只能站在语言角度去理解它啊,所以这个知识我能不能再往深挖一点呢?答案是可以的啊,当时为什么不讲呢?原因是当时我们没有办法讲。今天我们就能讲了,为什么野指针崩溃?原理和刚刚的这个我们对应的除0是一样的。

野指针会崩溃,那么毫无疑问,一定是因为我收到了对应的信号。那么,我们再来继续kill -l,找一找,那么我们叫做segmentation fault,我们在这里称之为段错误。

当然了,你试着找断错误,对应的就是几号信号呢,注意seg,答案是11号信号啊

那么对应的就相当于呢?如果我除零了,那么其实我呢就会收到11号好,那么问题来了,

你怎么证明你确实?异常的时候收到11号信号呢,好那么很简单,我们把它呢再捕捉一下

如果运行的时候,它也是在不断的去打啊,这个呢,我们一会解释,现在呢,

我不想让他这么打了,所以我想让你的是收到这个信号之后别给我跑了,直接终止吧。所以exit

因为操作系统会给我们当前进程,叫做发送我们对应的指定的叫做11号信号。

这就是结论,那么下一个那11号信号呢?我们再详细查一查11号信号的默认处理动作好,动作呢,也是终止进程,命令man 7 signal

它的默认动作也是终止,那么代表的是非法的内存引用啊。那么说白了,它也是终止进程的啊,那么下一个也是最本质的问题,那么操作系统怎么会因为它怎么知道我也指正了呢?操作系统操作系统,它要解析野指针,它就收到了野指针这样的访问,然后转化成信号。那么给我发一个11号信号。

现在的问题是操作系统怎么知道呢?也很简单啊,我们应该还记得曾经我们讲过,这是一个进程,每一个进程呢,都有自己的pcb,页表,

这个页表负责构建一种映射关系好,那么其中这是映射关系,我把pcb,物理内存,虚拟地址空间画出来

第一,我们刚刚用的指针,比如说int*p,p也是个变量,需要开辟空间的。那么最后呢,我们再对p = nullptr,最终我们对p变量做解引用。访问它对应的零号地址啊,这个地址是什么地址呢?

注意这个解引用之后,所对应的地址我们称之为虚拟地址。那么换句话说呢,所谓的指针本质就是虚拟地址。所以,你是通过虚拟地址的方式来访问物理内存的。

我们曾经讲过一个知识,叫做虚拟地址,要向我们对应的物理地址要进行我们对应的转化,是通页表和一种硬件叫做mmu 装换,那么其中呢?mmu叫做内存管理单元。虽然我在这儿画着呢,但实际上mmu这个硬件单元,它是集成在CPU当中的。

mmu是通过读取页表的,形成我们对应的叫做物理地址,然后再去访问我们对应的物理地址的啊,好不过我们为了形象呢,一般画在这儿。

就是当我们采用想去访问即引用是零号地址啊,因为null就是零。那么,访问零号地址时,经过我们页表映射发现,当前进程不允许访问我们对应的叫做零号地址的啊,那么不允许你访问,那么我当然可以拦截你,不让你访问。但更重要的是,你为什么会访问呢?所以操作系统呢?就觉得你犯错了,那你就得付出相应的代价。所以mmu这个硬件,就会直接因为我们对应的越界访问,而mmu因为越界或者野指针访问。进而发生异常,这个硬件呢发生异常。发生异常之后,那么紧接着操作系统,它要不要知道当前这个硬件出异常了,计算机当中的所有异常操作系统都应该知道。同样的mmu 一旦出异常了,操作系统也要知道。

所以这里的mmu 一旦出异常,那么操作系统立马识别到发生异常了,那么并且是因为给你这个进程转化,从虚拟到物理转化时发生的异常。所以操作系统立马将该异常转化成我们对应的叫做11号信号发送给目标进程。

所以这个地方呢,就叫做操作系统,怎么知道我野指针了呢?因为当你野指针的时候,那么也会引起虚拟到物理地址之间转化时,对应的硬件mmu报错。进而被操作系统识别到报错,进而将报错转化成信号发送给进程,进而让你的进程实现终止。

那么我们现在,再重新谈一谈,那么我们一般在CC加加当中。那么,一旦除0或野指针,那么为什么我的程序直接崩溃呢?那么我们现在就更清楚了,是因为当我程序运行异常操作时,我的程序呢,就会触发我们操作系统内CPU或者内存管理单元的相关报错,进而被操作系统识别到。进而会向我们的目标进程发送信号,我的进程就自动被终止了,我可以选择不终止,它可以捕捉它,但基本没有意义。好同学们,那么如上就是硬件异常,那么会产生信号的发送。硬件异常信号产生的信号发送,我没有掉系统接口,

那么我有没有自己主动去发?没有,这是由你的软件行为自动触发的。在操作系统内部,它自动工作。

像我们C加加或者JAVA里面有一种嗯,有一种知识就是。这种知识呢,我们叫做啊异常,是可以被抛出的,那么也可以被捕捉的。那么我们一般呢,抛出异常,我们可以用什么throw,然后捕捉异常呢,我们可以catch它,那么抛出异常和捕捉异常,这东西是不是特别像抛出信号?啊,那么发送信号和捕捉信号这样的动作非常像,事实告诉我们大部分情况下,我们如果写一些异常类的代码,如果你的逻辑里面有抛出异常的逻辑的话,一旦出异常了,其实你什么都做不了。大部分情况下,你就是打一句日志信息,打一句提醒信息,然后你就退出了。

那么这就跟一个进程出异常了,大部分情况下,你这个进程退出那么最多你可以打一句话啊,其实是类似的,但是那为什么大部分情况下异常都退出了?然后我们还要再去捕捉这个异常呢啊,都是退出那么什么还要有这么多种的异常呢?因为异常的不同,可以代表我们是因为什么原因,导致的什么异常,方便我们去追溯原因,就好比我今天,都知道进程在进行退出的时候呢,那么其中呢?它收到的大部分信号都会让我直接退出,但是收到不同的信号可以代表的就是不同的原因,比如说我如果收到了断错误,我呢就立马想到可能是野指针了。如果我发现我的进程或程序直接出现了浮点数溢出报错,我呢,就认为我除0了,所以根据我收到信号的不同呢,我就得知引起我进程退出的原因是什么,进而可以。反向的让我去进行定位问题。

这就是为什么大部分信号默认的处理动作都是终止进程。但是为什么还要有这么多不同种类的信信号呢?原因就是这些不同的信号代表不同的事件,那么未来呢,可以通过信号的不同来标定。这个进程是曾经经历了什么?那么是因为什么原因导致的异常进而我可以快速去修正它?好同学们,这就是信号的第三个关键知识点。叫做硬件产生。

还差最后一个第四种产生信号的方式,我们称之为软件。

语言上的异常和系统级别的信号有关系,但不是100%说你包含我,或我包含你的,这是两种不同的处理策略,这是第一,那么你们平时写的那种异常呢?那是属于单进程,在你的这个上下文当中的啊,不能和信号混在一起这是地儿。

如果你能捕捉信号了,那么此时呢?那么你照样要进行我们那么进程退出嘛,对应的就是处理。啊,它们两个是完全不同的处理场景啊,就是你捕捉信号是你自己在捕捉你的啊,然后我们去进行退出码,获取是我们想拿到别人那么退出时收到的信号是两种场景。这是第三。

我们来谈第四种也是最后一种产生信号的方式啊。

我们来谈第四种叫做软件条件产生信号,我们现在呢,第一,通过键盘可以产生,第二系统调用,三第三硬件可以产生。第四软件也可以产生。

什么叫做软件也可以产生呢?

我们讲过一个叫做管道,进程通信里面,管道通信,我们说过一种场景我们叫做如果今天我们的读端关闭。那么写端,如果一直写,那么这就叫读端关闭唉,写端一直写。画出来

曾经呢,给大家说过。读端如果关了写端,如果写因为操作系统,它不允许任何浪费或者不高效的表现或者现象。或者是那么场景,其中呢,你今天把它读关闭了,那么如果你一直在进行写入。那么其中呢?这个写的数据,因为没有人读了,所以你写入的数据便没有任何意义。所以如果呢读端关闭,我们写端一直写呢,那么操作系统就不允许你这么干了。操作系统呢,它呢就直接会终止你这个进程啊,直接就干掉啊。那么怎么终止呢?它是向我们当前的写进程的发送sSIGPIPE 13号信号。

像这一种那么所谓的进程操作系统,还有管道,这些东西,仅仅是因为读端关闭了,这一软件条件而所触发的?因为读端将自己的文件描述符关闭了,我不让你写了,那么其中就是因为读端关闭这样的软件条件而导致这个进程受信号。所以这种场景呢,我们就称之为软件条件,会触发信号。

就是那今天我们要讲的那么软件条件是哪一些软件条件呢?今天我们要讲一个定时器的那么软件条件。

那么在我们linux当中呢?它允许我们呢?可以给我们当前进程设定alarm。叫做设定闹钟,其实就相当于可以在那么设定好一个特定的时间段内呢,然后呢?当我们到了你所设定的那个闹钟的时间点之后呢?那么当前你的这个进程呢?就会自动收到一个叫做single alarm信号。设定闹钟就好比给进程定闹钟设了一个五秒的闹钟,五秒之后这个进程会收到single alarm。收到single alarm的默认处理动作,也是终止进程。

我们不废话,先写例子,然后再那么解释它的详细细节。要用到的接口alarm alarm。

它的作用呢?就是设置一个时钟时刻,那么未来呢?可以那么发送一个对应的信号。发送的就叫做single alarm信号。

那么参数就一个按秒为单位。那名称叫single alarm编号是14号信号。

上面代码,我就设定了调用这个函数,就设定了闹钟,这个闹钟是给未来的,所以调了这个alarm,并不是立马执行,比如说晚上你睡觉。到十一二点了,你要休息了,你定了一个第二天早上七点的闹钟,

当你把闹钟设定好的时候呢,那么你闹钟这个动作你设完了,这个闹钟并不响。而是到明天七点的时候它才会响同样的当你调alarm的时候呢,就代表的是这个alarm那么闹钟呢,而是5秒之后向我这个进程发送信号啊。·

概过了5秒钟左右,然后alarm clock叫做闹钟响了。那么刚说了默认情况下,我们对于我们14号闹钟信号,它默认的处理动作呢?也是终止进程。ngle alarm也叫14号信号,那么它的作用呢?就是终止进程。

它会给我们进程呢?发送一个我们对应的那么呃,就是14号信号,我们确实也如你所说,我也看到了,在我们大概5秒左右,那么我们闹钟响了之后,我这个进程就终止了。这个情况我们就叫做基于软件条件所设定的闹钟。

我再把上面的代码再改一下,

我写这一整份代码。它的目的是什么?就是我写这份代码,设了个闹钟,当下闹钟不响,一秒之后响在一秒期间,我的进程疯狂被调度,然后疯狂的再去执行打印我们对应的i的。新啊count累加之后的值,那么当它不断在那么叫做递增的时候呢?那么我们一秒钟闹钟响之后,当前进程也就随机退出了,好其实就叫做我们可以用来统计啊,叫做统计。

那么一秒左右,我们的计算机能够将我们的数据。累加多少次?是不是这个道理?因为一秒之后我会收到那么single alarm信号。我默认就终止我的进程了,终止我的进程,就相当于直接退出了啊,退出的时候那么在退出期间呢,那么我们有一秒时间就疯狂在打印。我一秒响后,我们发现它帮我们统计到5万多次,不管执行多少次都是5万多次,我们的操作系统呢?它当前只能同只能跑五万多次。四万多次多不多呢?那么事实上它并不多啊,

下面我把代码给大家做一个调整,把作为全局

对信号捕捉,一旦收到之后,我想让它做一件事情,就是输出i好。那么在这一秒期间呢?我也不打印。那么然后我就疯狂的will循环ctrl加加紧接着当我们闹钟响的时候呢,我再把对应的这个数字再给我们打出来,已经把这个累加了,那么若干次的一秒通知累加了一秒的这个数据再给它进行打印。就可以了。那么它确实如你所说那么打印出来的时候呢,它最后累加的值是这么大好,这一秒期间呢,我就让它疯狂加加就可以。最终我们看到的值呢?这应该算下来5亿多了啊好。刚刚你才累加到5万多,

现在你变成了5亿多,就是整整速度,大概要差了啊,最起码有个啊1万倍吧。算下来就是十的四次方数量级别的差别啊,那么下面的问题就是。为什么我们刚刚第一种方案会非常慢呢?为什么第一种的时候它只打印到我们对应的5万多,是因为我们当前呢?那么在进行打印时。是要进行printf输出,叫做访问外设,所以外设太慢了。所以第一次它的时候会有大量的时间都在IO。所以拖慢了我们对应统计的一个节奏啊,那么今天的这个外设呢?可不仅仅是显示器。你今天远程登录的是云服务器,所以当你在打印的时候,它实际上是把输出的信息通过网络发送到你的本地显示器上,所以它其实呢IO就更慢了。一般在那个啊,虚拟机上基本上呢打印出来呢,一个就正常配置的电脑打印上个七八万十几万是可以的。但是如果是云服务器的话,它的打印的效率就更低了,因为要经过网络同学们,所以通过这样的一个小例子呢啊,帮助同学们呢。感受一个事实。就是IO其实很慢。

那么有人说和时间片轮转也有关系啊,那么时间片轮转呢?操作系统默认给每个进程的时间段轮转的是该有的时间是多少,它就是多少啊,那么主要是因为,你就是你要分清楚因果啊。是因为你有IO呢,那比如说你有等待外设的时间呢,我把你切走了,那么还是因为我要切走你啊,然后你才等待外设的。事实上呢,是因为你等待外设,那么就是所以我才把你切走的,所以时间片轮转这个影响的本质也是IO引起的啊,所以这个呢,大家不用太纠结啊。那么这块呢?我们无法很精确的去衡量它,那么无法很精确的去衡量它,但是呢,我们能够以一个力度较粗的方案,那么你可以感受一下这个外设有多慢。是第一个。

第二个,如果一个比较仔细的人,上面代码又会发现一个问题,我在写这份代码时。故意在我们对应的捕捉信号,哪里没有终止这个进程,如果我给它加上终止进程呢,那么大家也就能看到,那我加个终止呢,那么exit。

  

他一秒钟退出没问题那我就打印出来了。

 如果不终止。相当于直接进行我们对应的,你就打印捕捉一下,那么执行完之后你就打印就完了,然后,我们确实发现也是一样啊,一秒之后,它现在就收到这个信号了啊

但是没有跟之前他疯狂的再去打印,没有了,只打印一次呢,那么就意味着这个single cache函数只被调了一次,就意味着我们只收到了一个single alarm信号。这是其一。其二呢,我不是设了闹钟吗?那么闹钟响了之后,就证明当前。这个闹钟只响一次,那么一次性闹钟设置响完了,它就不再响了,换句话说呢,我们现在的这个闹钟呢,它就是一个一次闹钟,一旦响了就不再响了。

好又因为我们当前呢,没有设定我们对应的退出,所以它执行完之后,它不退出了,那么single alarm信号默认虽然是退出,但对不起,我们暂时不退出了,那么下面呢?就是那如果我想让我们的闹钟呢?一直在打印。

所以我们可以在捕捉的同时。alarm (1),也就是说呢,当你的代码运行的时候,那么设置一个alarm。那么开始跑跑的时候呢,那么其中呢,我们一旦收到alarm信号打印这句话,打印之后,然后再重新给自己设个闹钟,一旦那么设置了之后,一秒之后再触发再执行,它设置完之后再去打印,打印之后再进行我们设置,所以此时我们就可以再通通过重复设置alam,然后呢?我们就可以直接实现每隔一秒去打印,这个逻辑是不是非常像我们对应的sleep 1秒钟啊?同学们。听好那么其中呢,我们就可以通过这样的方式去实现一个简单的sleep。

我们就称之为软件条件产生的一个信号。那么,其中呢?我们对应的闹钟函数,还有我们对应的叫做single alarm信号呢?我们可以通过alarm来设置。那么这个函数呢?设定一个闹钟,也就是在内核seconds秒之后呢?

给当前进程发送single alarm信号。那么,信号的默认处理动作就是终止你可以捕捉啊这个函数的返回值是零,或者是以前闹钟剩余余下的秒数啊,它是一个返回值。

有时候呢,那么我们的闹钟呢,有可能会提前响,那么有人说怎么会提前响呢?来举个例子,你设了个闹钟。是明天早上七点的,结果第二天早上五点的时候,你爸就把你叫醒了,当你爸把你叫醒的时候,

你一看时间,你就跟你爸说,你说爸还有俩小时闹钟才响,你叫我干啥?那么其中呢,你爸把你叫醒时,叫你相当于进程被提前唤醒了,那么此时呢?你在设你对应的这个闹钟函数呢?就会给我们返回我们对应剩余时间。好,那么有人说,那我定的闹钟怎么会提前唤醒呢?生活当中有我爸我妈叫我让我起床看书,让我学习呢,对吧啊?那我计算机里怎么会呢?那我如果设了一个十秒的闹钟,这个进程在运行的时候呢,那么然后我自己手动给它发一个信号。那不就是他提前响了吗?,就这个意思。

如果这个闹钟seconds设为零,它表示取消闹钟。好,这就好比呢,那么你设了一个明天早上七点的闹钟,然后呢,那么你后悔了,你说唉,算了,明天还是不设闹钟,睡个懒觉吧,所以你把闹钟呢,直接取消了。那么,其中对应的就是你给alarm设定了一个叫做零,

就好比呢?那么我们设定个十秒之后响的闹钟。那么大概过了五秒呢,我又不想设了,然后我就直接alarm 0, alarm 0之后呢,那么它对应得到的返回值就是我的闹钟,还剩下的时间,同时还把我当前的闹钟取消了。这就叫做取消好,那么这个呢?大家下来自己去验证一下啊,

如上就是我们关于目前我们叫做那么single alarm信号,那么它的一个。我觉得这块代码呢,理解起来并不难,那么下面我要给同学们谈一谈,一个小小的扩展啊。你说设闹钟就设闹钟,凭什么啊?

好,下面我来给大家把这个解释一下,收到一个信号,读端把文件描述符关了,那是一种软件行为,这种软件行为,那么对于我们的另一侧写入进程来看呢?它就是它是否能写的条件,如果对方那个进程呢?他把这个就是文件描述符关了,那我写入的条件就不满足了。

那么我此时收到single ,这叫做软件条件,那么第二点呢,你又给我讲什么single alarm,你说我们会收到我们对应的。操作系统来给我设定的闹钟好,那么你说这叫做软件条件触发的一个信号的发送好,那么你刚刚呢也给我测试了一下它的一些代码级别的应用,其实我还是不太理解,那么为什么设闹钟就称之为软件条件呢?

我们第一个,闹钟,那么在计算机领域的闹钟啊,其实就是用软件实现的

那么下面我们来解释一下,如何理解操作系统的闹钟,这样的概念啊。好,那么首先alarm这个接口呢?它是属于系统调用的设定的,这个闹钟是在操作系统内部设定好的。

好,我们来给大家要来解释一下了,首先啊,程序员特别喜欢起名字啊,那么这个大家已经领教过了啊,那么像我们之前讲的很多东西里面呢,它都有程序员起的名字。闹钟也是一个啊,那闹钟呢,本质上它是一个对我们现实生活之中迁移到计算机当中的概念。

首先我们要问大家一个问题,你可以设闹钟,别人可不可以设呢?是不是当然可以啊?那么你这个进程设了个闹钟,其他进程其他其他进程父进程子进程爷爷进行孙子进程啊。那么,那么所有的这些进程呢?是不是他如果愿意,他都可以通过alarm这个系统调用向操作系统内部设定闹钟?所以任意一个一个进程,都可以通过alarm系统调用,那么在我们的内核中设置闹钟

既然呢,每一个进程都可能通过alarm系统调用来设定闹钟,那么是不是操作系统内可能会存在着很?很多的闹钟啊,那么操作系统要不要管理这些闹钟呢?答案是要啊,那么要怎么办呢?那么我们怎么管理呢?先描述再组织。

这句话我们已经那么说烂了,所以当我们在操作系统内部,那么你设定闹钟的时候。其实操作系统内部要为闹钟创建特定的数据结构对象

当我们在创建的时候,我们来一个伪代码啊,

when,在内核中代表的就是什么时候超时,这是个时间戳。比如说你当前的时间戳呢,是1000,你设定闹钟是100秒之后,那么其中呢,这个when它就是1100。叫做未。的超时时间,

第二个int type,理解成闹钟类型,那么代表的是一次性的还是周期性的,周期性的,而我们刚刚看到的那么叫做一次性的,那么就说明用完就不要了。如果是周期性的,那么他经常也要在用啊,再下来呢,那么我们这个闹钟呢,我们也可以再加上,

比如说我们叫做task struct*p 代表的就是这个闹钟是。是哪个进程相关,

我们可以再设置一个叫做struct alarm* next。

所以呢,那么当我们在最开始时,操作系统呢,它可以在内部维护一个struct alarm*head。

那么你现在操作你的进程不是设定一个闹钟了。

我们叫做struct alam用它的类型定义一个对象 myalarm。然后呢,给它里面的字段填未来的时间是多少异常.....,所以就有了对象,有了对象之后呢,因为它里面也有各种指针,那么对应的当前这个闹钟也被设置好了。我们可以把这个闹钟放入到我们对应的队列当中啊,或者特定的数据结构当中。

然后呢,当其他人也上闹钟,直接用特定的数据结构连接起来。连接起来,然后呢我们的操作系统会周期性的检查这些闹钟。那么,检查闹钟的时候是怎么检查的呢?操作系统会获取当前的时间戳,如果当前的这个时间呢?它已经超过了alarm.when的时间,当前超时了超时了怎么办呢?

操作系统直接叫做发送single alarm。14号信号给谁发呢?那么发送给我们这里有一个alarm.p,就可以发送给特定的进程了,所以,我们要理解这个闹钟的话,完全最终操作系统内部对闹钟的管理呢,就变成了对链表的增删查改

两个问题,第一个问题呢啊,实际上呢,在内核当中呢,它管理闹钟呢,有很多种管理方式啊,最经典的管理方式呢,诸如时间轮战的管理策略。还有比如说最大堆最小堆啊,比如说我系统里存在了100个闹钟,我可以把这100个闹钟呢,按照问未来超时的时间,那么建立最小堆,那么是不是未来超时的最短时间就是你的堆顶的那个闹钟?

比如说我们最短的,有的设5秒,有设10秒15秒,有设20秒,那么我建立按未来超时时间设定我们的那么最小堆的话,这个最小堆的就是顶部是不是就相当于最近一次要超时的,所以呢,操作系统呢,它未来要检测某些对应的闹钟,要超时的话。很简单

它直接只检查堆顶,堆顶没有超时,所有的节点都没超时,如果堆顶超时了,把堆顶拿走。发送信号,然后回过头让这个堆重新再调整,然后再检查这个堆顶,如果还是超时的再拿走,直到它不超时了。那么我们就可以把所有的堆那么以及闹钟就管理好了。

对我们来讲呢,那么有了先描述再组织呢,那么最后就对数据结构有了概念。那么,对闹钟的理解呢?我们也就很好理解了,所以呢,整个的过程是不是操作系统要定期的检查超时条件?超时呢,是操作系统这样的软件去检查闹钟对应的一个那么维护所维护的这种软件集合。时间到了的时候。

操作系统会从我们对应的的结构当中去找我们对应的超时的闹钟,那么它的这种行为呢?全部是由软件构成的。而我们的条件体现在就是超时这个条件,所以它就叫做软件条件。那么如上就是闹钟的理解啊。那么基本上呢?我们也不用看它的源代码,然后呢?就靠着这六个字,我们就能够那么很容易去理解,现在那么我们计算机当中很多很多不好理解的概念啊。

就这六个字,先描述后组织。

那么同样的所谓的闹钟,就是操作系统内维护的大量的数据结构对象。那么当有大量的时候,操作系统需要把大量的数据和对象组织成特定的数据结构,最后呢,变成了对闹钟本身的管理,变成了对某种数据结构的增删查改。如果今天你想写,你用队列也可以做闹钟,你要检测那便利一下,超时的就把它处理,并且从列表当中拿走。如果你不想这么干,那你可以使用更高效的数据结构啊。

来那么当我们明白这点的时候,那么我们的信号的产生,我们便全部讲完。

如上,就是我们信号产生的所有情况。目前为止我们学到了常见的五种发送信号的方式。

第一种呢?叫做我们对应的,通过我们的键盘。

第二种通过kill命令。

第三种呢,通过我们对应的就是硬件异常。

第四种呢,我们就可以称之为软件条件啊。

第五种呢,一个系统调用。

第一个:所以不管你产生信号的方式有多少,那么最终,所有产生信号最终都是由操作系统来执行。因为操作系统是进程管理者,只有操作系统有权利去那么向目标进程写信号。

第二个:信号不是立即处理,那我们现在有个话题呢,就是那它是什么时候处理的?那么,目前呢,只能告诉大家,在合适的时候,那么这个合适的是什么时候呢?我们后面讲信号捕捉的时候再谈。

第三个:如果信号不是被立即处理的。信号是不是要被暂时保存下来呢?记录在PCB当中。

一个进程没有收到信号的时候,能否知道自己对应的合法信号做何处理呢?各位今天那么你能够识别红绿灯啊,问一下,红灯亮了该怎么办呢?你告诉我红灯亮了,要等一等。那么各位,我们现在见到红灯了吗?没有,但是你知不知道红灯来了,该怎么办?当然是知道,这就叫做在你还没有看到红灯时,你其实早就已经知道红灯是亮了,你该怎么处理了。

这就是信号最大的特点之一就在于信号还没有产生的时候,早就应该知道信号产生之后我们该做什么工作,是由程序员默认。系统程序员默认已经在代码当中写好了。

那么如何理解操作系统上进行发信号呢?那么说白了其实就是操作系统直接去修改目标进程的PCB信号位图。

如上就是我们的关于信号的发送的所有话题。

最后一个,那么我们上面有一个话题,我说我暂时先不谈,等我们把信号处理讲完。见到了一个我们对应的信号在进行收到之后,它默认的处理动作大部分都是终止。那么其实当然也有其他的。

比如说ignore,比如说continue,比如说stop这些啊,这些我们暂时不管,但大部分呢都是终止。但关于终止呢?我们见到的有两种终止方式,一种?是用terminal表示终直接就是终止的啊,还有一种叫做Core。它们两个都是终止,那么它们两个有什么区别呢?

最后一个问题进程退出时的,核心转储问题。

就是终止的时候我也能理解,那么确实,但是我在看那个官方手册的时候呢,它上面有一个终止是term,还有是core。那么,这种终止的这种方式和扣终止方式有什么区别呢?

我要和大家来谈一个终止的区别,看官方手册。

就是一二就是这种trm的,三是core,通过代码演示一下,比如说我们的段错误啊,段错误呢,那么其中我们再找一下,就11号信号,它也是core的那么,这个断错误比较好演示,所以我就用这个断错误啊,非法地址的引用,我们再做个测试啊。

那么下面我们谈下一个话题,叫做核心转储。好,那么首先我在我的代码当中写个最简单的代码。

我们数组范围就是10,因为这个数组我在定的时候只有十个元素,你现在访问到100个元素了。那么,此时就叫做越界好,那么我们来试一试,到底它能不能出现我们的越界呢?运行一下。

嗯没有反应。我们把它呢跑起来,程序有没有崩溃呢?没有,你不是越界了吗?我把它再改改,改成1000。运行看看

发现还没还没有崩溃啊,

改成一万。在运行看看

这次出来了啊来先告诉我。你不是定义了十个元素吗?为什么一百一千的时候它没有月没有报错呢?而是正常就跑了。

有点奇怪,数组范围不是10吗,可是为什么到100到1000的时候,它最后没有报错呢?而且代码编译也通过了。最后运行的时候也正常在跑啊。

各位,在数组这里越界是不是一定会导致程序崩溃呢?那么答案是不一定。因为实际上数组,编辑器在编译你的代码时,究竟在栈上给你开辟多大空间?这个和编辑是强相关的。你要十个元素,那么它确实给你申请的就是十个元素,指的是这个数组的十个元素。但是呢,并不代表给你这个代码块儿或者函数分配的那个栈结构是十个字节。它的字节数可能很大。所以呢,你即便越界了,你照样是在你的有有效栈区里面,所以它就没报错,除非你访问了一个完全不是你的空间。

比如说,现在访问的时候访问到了系统的地址空间当中,或者访问到一个不让你访问的区域,那么此时呢?操作系统就能识别出来。所以操作系统在识别越界的问题上,有可能也识别不出来啊,从而,出现了一些把数据改掉了。但用户还不知情的情况。

有很多的现象,那么我们在纯语言角度,我们能解释它,那么有很多我们在语言上无法再深入解释的,是需要到系统。操作系统里面找答案的。也有一些呢,它是在我们对应的编辑器当中去找答案的,那么计算机那么你想把它学好就单纯光把语言学好是不够的。

那么因为我们一定会面临各种我们对应的奇奇怪怪的问题。引起问题产生呢?可能并不仅仅是语言层面上引起的。它可能是在系统角度上引起的,如果你解释不了它,那你就解决不了它能理解吧,所以呢,那么真正有经验的程序员呢?和没经验的程序员,他们的差别呢,其实就在这里,本质上还是一个知识的宽度,深度和我们跨学科的一个能力啊。

像这种term,它就叫做正常结束。也就是操作系统不会给我们做额外的工作,而以core的这种,它,那么就代表的是操作系统,那么它除了要终止我,它还要做其他的工作。

有人会说那么我终止的时候,我也没见他做其他工作呢啊。至少目前我们发现他好像给我打出来了一个错误描述。但光光这种错误描述还不够。我还想看到更多现象。

我要给大家解释一下,在云服务器上默认,如果进程是core。退出的,我们暂时看不到明显的现象,那么如果如果想看到啊,那么我们需。需要打开一个选项,ulimit -a

我们其中可以看一下。有limit呢,是一个查看我们当前的这个系统呢,给我们当前用户所设置的各种资源上限。其中呢,包括我们的管道pipe的大小,单位是512字节,你实际测试可能有点差别,因为这个东西跟那么你当前用户有关,也跟操作系统有关啊。那是以实际测试为准啊,再下来呢,还有一个就是open files,你打开的最多的文件个数是10万个,云服务器一般它能够让我们打开的文件描述符是调大了的,因为它未来要适配网络,而我们如果你们装的是虚拟机的话,你这里查你打开的文件个数可能是很少的,比如说32这样的数字啊。

那么其中像这些数据呢?尤其是这种那么关于我们网络相关的内容呢?一般这个都是需要修改源代码,由运维的专门负责。

那么,其中有一个重要的选项就叫做core file size。而如果你用的不是云服务器这个数字呢,有可能是一个比较大的数字啊。比如说64,128,256啊,那么正是因为我们设置扣fail size为零。这个东西就叫做云服务器,默认关闭了核心转储。

我们来看一看。这里呢,我们叫做那么云服务器呢,为什么我们看不到呢?因为云服务器默认关闭了core call file选项啊。

那所以呢,如果你想看到,那么怎么办呢?那么你需要做nlimit -a,你可以看一下后面这个参数,

可以通过-c设置。比如你要打开,ulimit -c [多大]

后面的数字就是你想打开多大,最终呢,我改成1024,那么所以如果你想看到呢,那么这个动作呢,我们就叫做打开云服务器的core file选项啊,

我们的操作系统当中形成一个最大为1024个block的数据块。

我们同样的代码,再运行。

相比较之前我们在运行的时候只有一个segmentation foot断错误,现在,我们发现segmentation foot。后面又跟了一个叫做扣dump扣dump,然后ls再看我们当前目录下多,同时还多了一个文件。第一我们看到输出的报错里出现了一个core dump。第二,当前目录下多了一个core文件,

先谈第一个,这个呢core dump,那么我们称之为扣表示核心。dump称之为叫做存储啊,那么我们称之为核心转储,转储到哪儿了呢?

在这里,在当前目录下存在了一个以core命名的,那么后面跟了个数字。这个数字是什么鬼呢啊?我们猜也能猜得出来,它叫做引起扣问题的进程的叫做PID。所谓的核心转储呢?就是当进程出现异常的时候,我们将我们内存中,对应的时刻在内存中的有效数据,那么转储到磁盘中好这个呢?我们就称之为核心存储。

那么换句话说呢,我们的进程呢,它在运行的时候呢?出现如果异常了。以前的正常term呢?终止是就是正常终止了,如果是core呢?如果你打开了扣选项,那么进程终止的时候,它还多做了一些工作。就是把我这个进程在内存当中的一些二进制有效数据给我dumped到磁盘当中,那么这个就叫做核心转储好。给我们形成的这个文件它通常以core命名,以我的PID作为我们文件的后缀,然后就形成了一个临时文件。而该临时文件呢?我们自己用文本编辑器打开呢?它就是乱码,那么这个乱码呢?我们人是无法识别的。

其中呢,那么你形成这个核心转储。你的意义是什么呢?就为什么我们要有一个核心转储?那么一旦我们进程在运行的时候出现崩溃的情况,其实我们更想知道的是。为什么会崩溃?在哪里崩溃?那么我们其实最想知道的是这个。

所以呢,操作系统呢,为了便于我们后期做调试,它会将我们程序进程在运行期间出现崩溃的。

那代码的相关上下文数据全部给我们dump到磁盘当中,支持调试,那么所以为什么要核心转储呢?原因就是为了支持调试。

那么再下一个问题呢?就是如何支持呢?你说支持就支持,那么如何支持呢?那么所以为了能够让我们的代码调试起来。所以我们需要给我们的代码呢,带上-g选项,一个代码要被调试,必须得-g。

 然后我们再运行。此时直接就崩溃了,后面core dump。

然后确实发生了核心转储,然后进行调试。

进到这个那么叫做调试环境当中了,那么我们其中那么不用再花太多的时间去在我们的代码当中去定位,你在哪一行报错了。

直接core-file [文件],如ccore-file ore.11139 确定。

它自动帮我们那么评判到当前的代码终止呢,是因为收到了11号信号引起的断错误。进那么这个报错呢,它是在main.c的第27行,就是在这一行代码处出现的报错。直接就帮你找到了,那么这种我们直接快速定位问题的方式,我们称之为叫做事后调试

直接在GDP上下文中直接core-filfe [文件],那么就可以直接找到问题所在了,

那么因为是核心转储,所以它呢,一般在进程终止时,它只会去帮我们去检测是core方式终止的进程,大家都可以试一试。

下面呢,我们同样的在打开的状态下呢,我们把我们对应的代码再改一改,就让它正常去运行好,

就死循环。

 我们呢,除了kill -9退出的,我们还有二号进程,那么我们发送个二号信号,给进程18000

发完之后呢?我们发现当前进程呢?什么反应都没有,然后再看当前目录也没有核心转储

我们来最后再来看看。

那么,我们的二号终止是trm的,那么那么我们的11号是core的,那么term和core这两种退出都是退出?它们两个之间的区别是什么呢?core,是可以被核心转储的,是可以后续快速定位问题的。也是我们写代码当中最常见的问题。

term而一般这种终止呢,就是我们主动的那么正常下的,我们杀掉进程,所以这就是term和core退出之间的差别。

调试技巧

关于核心转储往后呢,如果你的程序出异常了,那么你呢,可以先确认一下这个异常,那么。进程出现异常,一定是收到了信号,首先确认是几号信号,然后呢,再确认信号是core还是trm?如果是core,你可以把核心传输打开,然后呢,直接让它再运行一次,形成核心传输文件,直接GDP定位错误的行数。啊,所以呢,你也就不用再花大量的时间去找错误的代码在哪里了啊?这也是一种调试策略啊。

那么至此,核心转储搞定,大家剩下的自己下来再做测试啊来。如上就是我今天要分享的最重要的内容。

总结:

关于信号呢,我们一共要学习如下几个话题。我们要学到了信号产生,信号保存信号处理,而我们呢?目前讲完了信号的产生。下一个话题当然叫做信号的保存,再下来一个呢,我们叫做信号到我们叫做处理,当然前置性的我们还有一个小话题叫做预备。

那么其中呢,把预备搞定了,信号产生搞定了,那么下面我们要聊的,当然就是信号的保存喽,在我们讲信号保存之前呢,我需要给大家再来回一个小问题。这个小问题呢,可以说是大家到现阶段都在思考的问题,也是上一篇,结尾抛出来的问题啊。

因为根据你所讲的,目前一共有一到31个普通信号。收到信号,如果默认的时候大部分信号都是终止进场啊,但是如果我今天呢,把我们对应的信号,那么对它进行自定义捕捉。然后呢?那我不退出这个进程

那么换句话说呢,我捕捉一个信号,但是不让这个进程退出。那么此时这个进程,是不是也就无法被信号所杀死?也应该给大家在现在要解决一下了,是个小问题,可以聊一下。

如果我对所有的信号都做自定义捕捉,然后我不让它执行默认动作,自定义捕捉时打一句话就完了。我也不做任何的动作。

就是我对所以情况都进行捕捉了,是不是这个进程就无法被杀死了。上面代码就是for循环就是捕捉所有的信号,如果我不发信号,当前进程就正常执行这段代码,

如果在我运行打印这句话期间,我不发一个信号,那么这个satchSig函数是不会被调用的,能理解。它只是设置了一个方法罢了啊。

我们来试一下啊,没有发信号,就一直在这里打印。

 

我们来一个ctrl c,我还终止不了它了,

代码再改一下把他的进程id打出来和收到什么信号一起打出来

 

其他的不一试了,除了19号信号是吧进程暂停,其他的信号都不行了。 

如果操作系统允许你这么干,那么其中,这就是我们操作系统本身的一个问题。如果我今天写一段恶意代码呢,那么带来的一个结果就是这个进程无法被干掉了。那么那你你就只能眼睁睁看着它,那么在你的系统当中做各种坏事情。

所以操作系统的设计者也考虑到了,所以我们有一个管理员信号。叫九号信号,那么你再怎么强势,都对九号信号设定捕捉不了,所以在操作系统内是禁止对九号信号做捕捉的。你即便你手动做了,系统也不会给你设置的,它至少要保留一个信号,用来做管理员信号。来杀掉所有的异常进程。

下面我们正式进入信号部分的第三个大话题叫做信号的保存,

那么我们把信号发送,说了五种方法。然后还讲了一些周边知识发送,最终我们也都知道是操作系统发的?

那么下面一个问题呢,就是我们要专业一点,给大家把一些概念要做一下相关的校正。

第一个:那么实际执行信号的处理动作呢?我们称之为信号抵达。

实际执行信号的处理动作,我们称之为信号抵达,比如说你自定义的这个执行这个catchsig函数,我们叫做递达该信号,或者呢,你做终止一个进程,那么终止这个进程的过程,我们叫做递达信号

第二个:信号从产生到递达之间的状态,叫做信号未决。

递答叫做deliver,那么未决就是pending,什么意思呢,你不是告诉过我信号不是被立即处理的,所以进程呢,它就可能需要在那么信号产生之后,信号被处理或叫做信号被抵达之前。这段时间窗口这个信号呢,它处于一个已经被收到但尚未被处理的状态,我们称之为信号未决。

第三个:进程可以选择阻塞某个信号。

这是一个全新的概念啊,进程可以选择阻塞某个信号,我们俗称block这个信号,那么如果一个我们对应的信号它被阻塞了。它将永远保持在未决状态,如果收到了一个信号之前呢,

第四个:它是已经被阻塞了,那么如果收到时这个信号永远不会被抵达。直到进程解除阻塞,才执行对应的抵达操作。

未决说白了就是它暂时把信号保存起来了。我们暂时没有把它立即进行递达嘛。

有一个概念,叫做进程阻塞信号,那么进程阻塞这个信号是什么意思呢?给大家举一个例子啊好。

今天假设我就是高中教室里,高中的班主任兼数学老师,然后呢,我在上课的时候呢,我给同学们说,今天我讲了三道题。那么大家呢?那么现在把这里的第一道题,第二道题和第三道题下来呢,那么用第二种方法自己再做一做。

我在做什么呢?我在给大家布置作业,然后呢?那么在我们信息时代并不是特别发达的年代,那么所以我们坐在底下的同学呢?都拿出了自己的小本子。然后呢,记下了这数学老师给我布置的作业好,那么在课堂上课还没有上完,然后我把作业布置完之后,我就继续讲课了,那么你把我的那么布置给你的作业记下来了,同学为什么不立即开始写作业,

在课堂上的时候,我给你布置作业了,你为什么不立即写呢?你现在就拿出你的其他卷子或者是你对应的其他的本子,把我这三道题赶紧解了,你为什么现在不不做呢?是因为当前你有更重要的事情,那就是你正在上课。所以呢,那么你只是把对应的作业记下来了,那么你要在后续合适的时候,那么再做处理什么合适的时候呢?

那么你回到家里面,饭吃完了,晚上没事了,然后把这个这三道题一写,这叫做在合适的时候处理。

我给你布置作业的过程,就叫做我在给你发信号,那么把这个我的作业我布置的作业记在本子上,这叫做保存这个信号,称之为将该信号设置为未决状态。

当你回到家合适的时候,你开始准备处理这个作业了,写这个作业了,这叫做信号抵达

特别讨厌这个老师。这个老师以前骂过我,我不喜欢他。可是我又很害怕他将来找我秋后算账。所以呢,他给我布置作业啊,我的心理特征就比其他同学多了一条,就是你给我布置作业,那我也记下来。我也给你未决起来。

那么可是呢,我回到家的时候啊,我就是不做。我也上了物理课,我也上了英语课。物理老师呢,

是一个那么很帅的男老师,我特别喜欢他,我觉得他的作业我要写,那么英语老师是一个很漂亮的老师,我也很喜欢他,这两个老师我都很喜欢,所以呢。他们的作业呢,我就正常的去写了,可是你的作业呢,我记下来了,但我觉得你太丑了,我不喜欢你。我把你的作业记下来,但就是不处理,那么这种情况呢,我们就称之为叫做阻塞这个作业

阻塞这个作业,并不是说这个信这个作业永远不处理啊,而是在后续合适的时候呢?我们在。解除阻塞之后才处理,就好比呢,如果我一直都很讨厌你这个数学老师,那你所未来布置的所有作业,我都不会写的,但是我会记下来。因为我怕你秋后算账,说那么你还欠我多少作业,我还得给你补啊,所以我把它记下来,我就不做。后来呢,那么我突然发现呢,你虽然长得丑,但是你心地很善良,然后呢,跟我谈了很多次话,对我帮助还挺大的。那么算了,

我打算解除我内心对你的偏见,你给我布置的作业呢,我都会写了,历史作业也补了。解除阻塞。

我们学过两个概念,第一个概念刚刚讲的阻塞。第二个概念我们之前讲的信号处理时有三种处理动作,第一种叫做我们的默认,第二种自定义,第三种忽略。所以阻塞和忽略是相同的概念吗?答案是是不同的。

那么阻塞,指的是呢,该信号永远不会被抵达。直到解除阻塞。

而忽略呢,本身就是抵达的一种,就好比呢好还。还是刚刚的例子,那么你给我布置数学作业,那么回到家里那么布置作业时候把作业记下来,回到家里的时候呢,那么我直接选择的选择忽略你这个作业。那么我处理了吗?我写了吗?答案是那么你可以认为成我的处理动作虽然没写,但是我处理了这个作业,我直接忽略它。因为我不怕你作业,不写也没事儿,所以这就叫做忽略。

而阻塞呢,那么它其实是并没有处理这个信号,而只是暂时把信号处于未决状态。

这就是阻塞和忽略最后一个问题。阻塞和未决根本就不是一种状态,未决就是未决,那么阻塞它就是阻塞。

如果我当前没有信号产生可以阻塞这个信号吗?答案是可以的,这就好比呢,如果你这个数学老师没有给我布置作业。我也照样可以讨厌你,所以我如果没有收到所有的作业,今天讲的这个阻塞某个信号,和以前讲的进程阻塞。是两个概念,那么这两个概念的区别依旧是老婆和老婆饼的关系,是完全没有任何关系的。

不要理解错了,不是进程阻塞,而是对某种信号做阻塞啊。这个概念,光光谈这对概念呢?其实还是不好理解的。

下一篇博客结合我们在操作系统当中表示我们的未决、递达和阻塞这三个概念。

对应三张图来看看就很容易明白了。

概念上的理解呢呃,除非那个概念实在说不清楚,否则的话,那么常规的概念,我们还是要把它交代清楚的啊。下节课呢,我们再基于上面的这个概念,然后再重新理解一下,那么我们这批那么可能看起来好像很复杂的概念,其实一点都不复杂啊。下一个再继续好,同学们一分钟总结,我们下播今天呢,我们重点讲的内容呢,是帮助同学们去理解我们对应的信号产生的那么四五种方式。

我个人认为呢,信号产生的方式当中,最重要的对大家带来最大的具有洞见性的一个。那知识点就是我们对异常的理解啊,除0野指针,最终都要表现在硬件上,被操作系统识别,操作系统将异常转化成信号,向目标进程发送。进程便会在合适的时候处理该信号,另外呢就包括软件条件,包括我们对应的键盘,包括系统调用,这些都好理解啊一句话。所有的信号,最终都是操作系统去发送的,而并非你。

产生信号的那么多方式,并不代表那么我们这些方式都可以直接去修改pcp,必须通过操作系统。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/489044.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

普洱生茶保存的最佳方法是什么?

编辑搜图 请点击输入图片描述(最多18字) 普洱生茶保存的最佳方法 普洱生茶,作为中国茶文化中的瑰宝,具有独特的口感和丰富的营养价值。然而,要想让普洱生茶保持其优良的品质,正确的保存方法至关重要。本文…

git-怎样把连续的多个commit合并成一个?

Git怎样把连续的多个commit合并成一个? Git怎样把连续的多个commit合并成一个? 参考URL: https://www.jianshu.com/p/5b4054b5b29e 查看git日志 git log --graph比如下图的commit 历史,想要把bai “Second change” 和 “Third change” 这…

涉及大空间场所消防安全!UL 268B图像型烟雾探测器性能标准

近年来,随着经济的快速发展,大空间基础设施密度增加,包括购物中心、体育馆、会展中心、机场、飞机库、隧道等民用建筑,以及发电厂、烟草行业和煤炭工厂等工业建筑。此类建筑的空间高度往往超过12米,有的甚至达到30至40…

渗流监测站—发现和处理潜在的安全隐患

TH-SL1渗流监测站是一种用于表征土壤和地下水系统的设备,它可以监测土壤中水分、水压、温度等参数数据,以深入了解土壤属性和水分运动规律等信息。该设备通常由传感器、数据采集设备和数据处理/存储设备组成。 渗流监测站有多种应用场景,如水…

RocketMQ学习笔记:零拷贝

这是本人学习的总结,主要学习资料如下 马士兵教育rocketMq官方文档 目录 1、零拷贝技术1.1、什么是零拷贝1.2、mmap()1.3、Java中的零拷贝 1、零拷贝技术 1.1、什么是零拷贝 使用传统的IO,从硬盘读取数据然后发送到网络需要经过四个步骤。 通过DMA复…

【QT入门】 Qt代码创建布局之分裂器布局详解

往期回顾: 【QT入门】 Qt内存管理机制详解-CSDN博客 【QT入门】 Qt代码创建布局之水平布局、竖直布局详解-CSDN博客 【QT入门】 Qt代码创建布局之栅格布局详解-CSDN博客 【QT入门】 Qt代码创建布局之分裂器布局详解 一、什么是分裂器布局 在Qt中,分裂器…

⨯ EPERM: operation not permitted, link ...

新增区块链相关包后,项目在部署的时候报错,报错内容如下: 报错信息: ⨯ EPERM: operation not permitted, link /Users/XXX/.cache/act/be662ca67b3f7553/hostexecutor/node_modules/bigint-buffer/build/node_gyp_bins/python…

11.测试教程-自动化测试selenium-3

文章目录 1.unittest框架解析2.批量执行脚本2.1构建测试套件2.2用例的执行顺序2.3忽略用例执行 3.unittest断言4.HTML报告生成5.异常捕捉与错误截图6.数据驱动 大家好,我是晓星航。今天为大家带来的是 自动化测试selenium第三节 相关的讲解!&#x1f600…

基于javaweb(springboot)汽车配件管理系统设计和实现以及文档报告

基于javaweb(springboot)汽车配件管理系统设计和实现以及文档报告 博主介绍:多年java开发经验,专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐…

【机器学习】引领未来的力量:技术革新与应用探索

🧑 作者简介:阿里巴巴嵌入式技术专家,深耕嵌入式人工智能领域,具备多年的嵌入式硬件产品研发管理经验。 📒 博客介绍:分享嵌入式开发领域的相关知识、经验、思考和感悟。提供嵌入式方向的学习指导、简历面…

端口的学习

端口是什么口?【网络常识3】_哔哩哔哩_bilibili 简化后的数据包: 软件和端口是1对多的关系。 http默认端口是80 https默认端口是443

二十一、软考-系统架构设计师笔记-真题解析-2019年真题

软考-系统架构设计师-2019年上午选择题真题 考试时间 8:30 ~ 11:00 150分钟 1.前趋图(Precedence Graph)是一个有向无环图,记为: → {(Pi,Pj)Pi mustcomplete before Pj may start}。假设系统中进程P{P1,P2,P3,P4,P5,P6,P7,P8},且进程的前趋图如下&…

(2022级)成都工业学院Java程序设计(JAVA)实验二:类和对象

写在前面 1、基于2022级软件工程/计算机科学与技术实验指导书 2、代码仅提供参考 3、如果代码不满足你的要求,请寻求其他的途径 运行环境 window11家庭版 IntelliJ IDEA 2023.2.2 jdk17.0.6 实验要求 1、 控制台菜单。在 Exp02_x 类中添加一个方法 menu_x&…

【Chrome控制台】network选项卡的使用

首先打开调试面板「windows:F12;mac:commandoptioni」,找到Network选项卡,其中是对网络相关的数据信息。 录制 控制台内容区域左上角红色按钮就是录制按钮,默认是开启状态,表示监听整个页面运行过程中所产…

【直播课】2024年PostgreSQL CM认证实战培训课程于4月27日开课!

课程介绍 了解关注开源技术,学习PG以点带面 Linux/Andriod(操作系统)、Apache/Tomcat(应用服务器)、OpenStack/KVM(虚拟化)、Docker/K8S(容器化)、Hadoop(大…

【Java - 框架 - Lombok】(2) SpringBoot整合Lombok完成日志的创建使用 - 快速上手;

"SpringBoot"整合"Lombok"完成日志的创建使用 - 快速上手; 环境 “Java"版本"1.8.0_202”;“Lombok"版本"1.18.20”;“Spring Boot"版本"2.5.9”;“Windows 11 专业版_22621…

泛型编程的启蒙之旅

个人主页:日刷百题 系列专栏:〖C/C小游戏〗〖Linux〗〖数据结构〗 〖C语言〗 🌎欢迎各位→点赞👍收藏⭐️留言📝 ​ ​ 讲模板之前呢,我们先来谈谈泛型编程: 泛型编程:编写与类…

【LeetCode热题100】98. 验证二叉搜索树(二叉树)递归阶段总结1

一.题目要求 给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。 有效二叉搜索树定义如下: 节点的左子树只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。 二.题目难度…

Keycloak介绍

1.什么是Keycloak Keycloak是一个开源的身份和访问管理解决方案,它提供了单点登录(SSO)功能。 Keycloak 支持多种标准协议,包括 OpenID Connect 和 OAuth 2.0,这使得它能够与各种服务进行集成,以提供身份…

几个常用的AI工具

人工智能大模型的出现对人类社会产生了深远的影响,这些影响既包括积极的方面,也包括一些潜在的挑战: 1. **提高效率**:AI大模型能够快速处理大量数据,提高工作效率,尤其在数据分析、自然语言处理等领域。 2. **辅助决…