Linux 进程信号篇

文章目录

  • 1. 生活中的信号
  • 2. 信号的概念
  • 3. 信号的产生
    • 3.1 系统调用
    • 3.2 软件条件
    • 3.2 异常
    • 3.3 `Core`和`Term`的区别
  • 4. 信号的保存
  • 5. 信号的处理
    • 5.1 地址空间的进一步理解
    • 5.2 键盘输入数据的过程
    • 5.3 理解OS如何正常运行
      • 5.3.1 OS如何运行
      • 5.3.2 如何理解系统调用
    • 5.4 内核态和用户态
  • 6. 可重入函数
  • 7. volatile关键字
  • 8. `SIGCHLD`信号

在这里插入图片描述

1. 生活中的信号


日常生活中,等红绿灯、上课铃响…其实都是在给我们发送信号,绿灯亮了给我们发送可以通行的信号,我们才能通行;红灯亮了,给我们发送禁止通行的信号,我们就要停下来。而有时会有特殊情况,临近吃中饭,你正在做作业,爸妈喊了你一声,你就知道要去吃饭了,但手里头就差一点作业就完成了,于是你暂时不管爸妈叫你吃中饭的信号,完成手里的作业再去吃中饭

我们对上述信号粗犷的理解:

  1. 信号可以随时产生
  2. 我们认识信号
  3. 我们知道信号的处理方式
  4. 我们可能正在处理要紧的事情,把到来的信号暂不处理

2. 信号的概念


信号是Linux系统提供的一种向进程发送特定事件的方式,需要进程识别和处理

Linux系统中,总共有64个信号,其中34~64称为实时信号,不作为本篇文章的重点

在这里插入图片描述

根据信号的定义,进程需要对信号进行处理,进程处理信号有三种方式:

  1. 默认动作
  2. 忽略动作
  3. 自定义捕捉

对于默认动作,让进程根据OS规定好的处理方式去处理,通常是终止进程,也有忽略信号的

其中终止进程有两种行为,CoreTerm,现在我们就认为它们都是终止进程,后面会将它们区分

在这里插入图片描述

忽略动作就是不管信号,很简单

自定义捕捉:我们自己设计好对应信号的处理方式(参数为int,返回值为void的函数),使用signal捕捉信号,当进程收到指定的信号时,会去调用我们自定义的方法

在这里插入图片描述

我们拿2号信号举例,它的默认动作是终止进程,相当于键盘上的ctrl+c

在这里插入图片描述

3. 信号的产生


信号的产生总共有5种方式

  1. 使用kill命令向指定进程发送信号
  2. 键盘输入,比如ctrl+\,相当于3号信号
  3. 系统调用
  4. 软件条件
  5. 异常

3.1 系统调用

这里介绍几个系统调用函数

kill是命令,同时也是一个函数,可以向指定进程发送指定信号

raise函数向自身进程发送指定信号

abort函数向自身进程发送6号信号

在这里插入图片描述

可以看到,abort函数向自身发送了6号信号的同时,还强制终止了当前进程

注:既然我们可以使用signal函数捕捉信号,能不能把所有信号都捕捉了?

如果把所有信号都捕捉了,会导致什么结果?可能会发生进程永远无法退出的情况,OS考虑到了这点;经过测试,我们发现9号信号(SIGKILL)和19号信号(SIGSTOP)无法被捕捉

在这里插入图片描述

3.2 软件条件

在进程间通信篇中,我们谈管道的4种情况时说到,当管道的读端关闭了,写端还在继续写,OS就会认为写的数据没意义,直接终止进程;这其实是OS向进程发送了13号信号(SIGPIPE)

在这里插入图片描述

针对软件条件,再介绍一个函数:alarm,可以把它理解为一个闹钟,设定好时间,它就会在未来向进程发送14号信号(SIGALRM)

在这里插入图片描述

借助alarm函数,我们进一步理解I/O很慢这个的观点,之前我们总说输入输出是很费时的做法,那到底有多费时,通过比较下面的两份代码,就知道有输入输出和没输入输出,它们的效率相差之大了

在这里插入图片描述

对于alarm函数,它是进程设置的闹钟,而进程在OS中是有多个的,也就意味着闹钟在OS中也有多个;哪个闹钟属于哪个进程,OS需要将它们管理好;因此,闹钟在OS中也有自己的结构体,并以小根堆的结构组织起来

一个进程只能设定一个闹钟;alarm函数的返回值表示上一个闹钟还剩多少时间;alarm(0)表示取消闹钟

在这里插入图片描述

3.2 异常

进程异常我们在进程等待时提到过,进程退出有三种情况,进程正常退出,结果正确/错误,进程异常退出

今天,我们从信号的角度,从三个问题,进一步理解进程的异常

  1. 程序为什么会崩溃?
  2. 崩溃了为什么会退出?
  3. 程序崩溃了能不能不退出?

以我们现在的知识,理解上面的问题十分容易,程序崩溃是因为我们进行了非法的访问或操作,OS向进程发送了信号,该信号的默认动作是终止进程,因此我们的程序直接退出了,最典型的有Floating-point exception(SIGFPE)、Invalid memory reference(SIGSEGV)

我们能使用signal函数捕捉信号,因此程序崩溃了可以不退出,但肯定是不推荐的,具体是为什么,下面会讲

在这里插入图片描述

可以看到,不管是除0收到的SIGFPE信号,还是野指针收到的SIGSEGV信号,它们的默认动作是终止进程,但我们进行了捕捉,程序没有退出;但是,为什么运行结果是一直在执行handler方法,不应该是执行一次handler方法,然后死循环执行主函数后面的代码吗?这就关系到CPU了,两种情况,我们分别解释

我们知道,代码最总是交给CPU去执行的,CPU的运算工作主要分两种,算数运算和逻辑运算,而在CPU的内部,有着各种各样的寄存器,有保存数据的,指向一段代码的…其中有个名为eflag的寄存器,它包含多个位,这些位的值为1/0,有个位叫做溢出标志位,用来表示当前的运算结果是否溢出

当我们的代码执行到10/0时,将10和0分别放到CPU的寄存器中进行计算,我们知道,10/0是不合法的,CPU也知道,因此将eflag寄存器中的溢出标志位置为1,而OS作为CPU的管理者,CPU运算时出了问题,它当然得知道并处理这个问题,于是它读到eflag寄存器的溢出标志位为1,就明白当前的运算结果不合法,给进程发送了信号;此时,该进程的时间片到了,要进行进程切换,我们知道进程切换最重要的就是寄存器需要保存和恢复进程的上下文数据,它也照常这样做,将10和0保存好,下次调度时再恢复,这时大家就应该明白了,下次调度时,运算的还是10/0,溢出标记位置1,OS向进程发送信号,如此往复,就出现了死循环打印handler方法

在这里插入图片描述

对于野指针的问题,也类似,但也有不同;首先介绍三个寄存器,MMU、CR2、CR3

在这里插入图片描述

虚拟地址到物理地址的转换过程中需要MMU的参与,将空指针转换成物理内存时,CPU产生异常,同时将异常地址放在CR2中,OS检测到CPU的异常,给进程发信号,然后进行进程切换,又是对空指针的转换…

在这里插入图片描述

到这里我们也就知道,为什么程序崩溃时推荐直接终止,所谓终止,其实就是释放掉寄存器中的错误数据,终止CPU的异常

关于异常,就说到这

3.3 CoreTerm的区别

进程异常,采用默认动作终止进程时,有两种类别,TermCoreTerm就是终止进程,而Core在终止进程的同时,还会在进程的当前工作路劲下生成一个以进程pid为后缀的core文件(Ubuntu的版本不同,生成的core文件格式可能不同,这里以Ubuntu22.04为例),记录了程序的崩溃信息

在云服务器下,默认是不允许生成core文件,使用ulimit -c size命令允许生成core文件

在这里插入图片描述

此时运行程序,如果程序异常退出,则会生成core文件

在这里插入图片描述

注:如果显示的错误信息后有core dumped,表明core文件已经形成,但在当前目录下没有core文件,表明默认core文件的生成路径不在进程的工作目录下,使用下面的命令即可

sudo bash -c "echo core > /proc/sys/kernel/core-pattern"

上面的部分,我们还有些问题需要解决

  1. 为什么默认关闭生成core文件?
  2. core文件有什么用?

core文件记录下了程序的崩溃信息,在哪一行崩溃,崩溃的原因、上下文等等,导致文件大小比较大,如果一个程序崩溃了就重新运行,这样下去,磁盘空间很容易就被core文件占满,因此一般Linux都是默认关闭生成core文件

当程序崩溃时,使用gdb调试,都是一行一行进行,有了core文件,使用core-file core.pid命令就能直接定位到崩溃的那一行了,方便我们调试了;这种调试方式我们称为事后调试

在这里插入图片描述

进程崩溃退出时生成debug文件,这种技术我们叫做核心转储,该debug文件是进程退出时的镜像文件

关于core,在进程等待中提到过,进程退出会留下退出码和退出信号,其中异常退出时有一个标记位用来表示是否生成了core文件

在这里插入图片描述

在这里插入图片描述

有时进程在做要紧的事,对到来的信号暂不处理,但会在合适的时候处理,这里合适的时候具体是什么时候,会在后面会详谈,现在我们要知道,要处理的前提是记得这个信号,也就是进程得保存信号

4. 信号的保存


初步理解信号的保存:

信号在进程中用位图的方式保存,比如给一个整型,最高位不用,第131位分别代表131号的信号;一个进程是否收到信号,由bit位的内容决定,1表示收到,0表示没有;因此,发送信号就是将信号位图由0变为1,而该位图在内核当中,只有OS能改变该位图的内容;发送信号的本质是OS发给进程信号,也可以叫做写信号

在正式说信号的保存之前,先给出几个概念:

  1. 执行信号的处理动作叫做信号递达
  2. 信号从产生到递达之前的状态叫做信号未决
  3. 进程可以选择阻塞(被阻塞的信号将永不递达,直到解除阻塞状态)某个信号
  4. 阻塞和忽略不同,忽略是处理信号的一种动作,而信号一旦被阻塞,永不被处理

进一步理解信号的保存:

在进程的pcb中,有一条属性指向了三张表,分别是block、pending、handler表,其中block和pending表都是位图的结构,bit位的位置代表几号信号,bit位的内容表示进程是否收到/阻塞该信号;而handler表是一张函数指针数组表,数组的下标代表几号信号,数组的元素指向handler方法

在这里插入图片描述

未来进程处理信号时,从pending表的1号信号开始,检查信号bit位是否为1,如果为1,横向向左检查该信号是否被阻塞,如果没有,就去调用handler方法;依次往下,直到处理完所有信号

讲完理论,下面用代码加深理解:

先介绍一种类型和几个函数

sigset_t,它是Linux提供给用户的一种类型,实际是位图的封装

在这里插入图片描述

在这里插入图片描述

上述代码结果:最开始屏蔽2号信号,当给进程发送2号信号时,2号信号确实没有递达,一直处在未决的状态,过了10秒,我们解除对2号信号的屏蔽,进程立即处理了2号信号并将pending表的2号位置0

通过代码结果,得出结论:

  1. 当信号的屏蔽被解除,进程会立即处理解除屏蔽的信号
  2. 在执行handler方法(递达)之前,就会将pending位置0

5. 信号的处理


前面我们已经使用过signal捕捉信号,处理动作有三种,SIG_DEL/SIG_IGN/handler;同时,我们还提到过,进程可能正在忙,会保存到来的信号,在合适的时候进行处理,所谓合适的时候,就是进程从内核态切换为用户态的时候处理

其中,内核态和用户态是什么?首先给大家描述一个轮廓:进程在执行主函数的某条语句时,遇到了中断、异常或系统调用(后面就拿系统调用举例),而进入内核;执行完系统调用后,再返回到用户前,进行信号的检测,如果信号的默认处理动作是忽略或终止,那很简单,直接返回或终止进程,但如果是自定义捕捉,就要先切换为用户,执行完handler方法后,再返回内核,最后返回到主函数,继续往下执行

在这里插入图片描述

  1. 为什么执行系统调用要进入内核?

    系统调用的函数是OS提供的,都是OS的代码,必须由OS亲自执行

  2. OS能不能直接去调用用户的handler方法?

    不能,OS不相信任何人,不清楚handler方法写的是什么内容,万一是访问用户没有权限访问的内容,不就通过OS访问到了;因此,用户的代码只能有用户自己执行

  3. 为什么要返回到内核再返回到主函数,能不能执行完handler后直接返回到主函数?

    不能,执行handler方法的是不同于主函数的流程,并不知道应该返回到主函数的哪个位置

在这里插入图片描述

简单描述一番,在详谈用户态和内核态前,先讲几个相关知识

5.1 地址空间的进一步理解

我们以32位机器为例,物理内存总共4G,OS分配给每个进程4G的地址空间,其中1G属于内核空间,3G属于用户空间;我们自己的代码通过页表映射到物理内存,这个我们能理解,但使用的各种系统调用都是系统的代码,进程是怎么找到系统的代码的?

当电脑开机时,OS是第一个启动的软件,加载到了物理内存;同动态库的加载类似,OS的代码会加载到每个进程地址空间中,只不过OS的代码是加载到进程的内核空间中,同时会有一张内核级页表,构建了内核空间与物理内存的映射关系;也就是说,系统的代码在进程的地址空间上;执行我们自己的代码,是在用户空间上来回跳转,通过用户级页表找到内存中的代码;执行系统的代码,是在内核空间上来回跳转,通过内核级页表找到内存中的内核代码

每个进程都是自己的用户级页表,但所有进程公用一张内核级页表

在这里插入图片描述

总结上述内容:

  1. OS本身就在进程的地址空间中
  2. 无论进程如何切换,我们总能找到OS
  3. 访问OS,本质是在进程的地址空间上进行的
  4. 访问内核空间,就能找到OS的代码和数据,但OS不相信任何人,访问会收到约束,这里的约束就是我们只能通过系统调用访问内核空间

5.2 键盘输入数据的过程

当进程在运行时,按下ctrl+c,OS会向进程发送信号,进程收到后直接终止,这里的问题是,OS怎么直到我们按下了ctrl+c,它是怎么收到我们按下的信息的?

OS在启动时,会优先向内存中加载一批操作硬件的方法,比如读磁盘、读键盘…存放在函数指针数组中,我们把这个函数指针数组叫做中断向量表;电脑中的每个硬件都有自身的中断号(同时对应着中断向量表的下标),当我们按下键盘时,会触发的硬件中断,向CPU发送中断信号,CPU接受该信号,并在内部存放该硬件的中断号,CPU根据中断号,去执行内存中对应的方法,OS就拿到了键盘的数据

在这里插入图片描述

看到这里,想想信号的发送、处理过程,当进程出现异常,OS给进程发送指定信号,进程根据handler表处理,是不是跟硬件中断有点类似,实际上,信号是模拟实现软件版的硬件中断

5.3 理解OS如何正常运行

5.3.1 OS如何运行

在上面硬件中断的基础上,理解OS如何运行就非常容易;在CPU外部,会有一个时钟,每隔几毫秒向CPU发送中断,CPU内部寄存器就会存放执行调度方法的中断号,进而去调用中断向量表中的调度方法,如此一直循环;OS的本质就是死循环时钟中断,不断调度系统任务

在这里插入图片描述

5.3.2 如何理解系统调用

在内核中,有一张函数指针数组表,存放着所有系统调用的内核函数,内核函数对应的下标我们叫做系统调用号,未来只要拿到系统调用号,就能进行系统调用了

上面时钟中断属于外部中断的方式,CPU内部寄存器也可以自己形成中断号,我们把CPU自身形成中断号的方式叫做内部中断,或者陷阱/缺陷,时钟中断的方式叫做外部中断;不论是内部中断还是外部中断,其目的都是让CPU内部的寄存器形成一个中断数字

我们用的系统调用其实是内核函数的封装,系统调用内部提供了该内核函数的系统调用号;当有系统调用时,触发CPU内部中断,CPU去执行内存中的系统调用方法,OS拿到系统调用号和系统调用函数指针数组,就能完成系统调用

在这里插入图片描述

5.4 内核态和用户态

在CPU内,有个名为code segment的寄存器,记录代码区的范围,其中有两个bit位,可以表示0或3;当值为0时,表示内核态,当值为3时,表示用户态

结论十分简单,但问题是,前面我们说用户是无法直接访问内核空间的,怎么做到不让用户访问内核空间?进程访问地址空间时,必然要通过CPU去执行,如果访问的是内核空间,CPU检查cs寄存器,看是否处于内核态,如果不是,CPU就直接拦截;因此,系统调用函数内部肯定会将用户态设置为内核态

6. 可重入函数


之前我们学过链表的增删查改,以下图的插入函数为例,在插入过程中,执行完第一条语句,此时该进程的时间片到了,进行进程的切换,从用户态变为内核态;在变回用户态前,进行信号的检测,检测到信号并执行handler方法,但如果handler方法也是链表的插入,处理完后,该链表结构为图2;再返回到语句2,执行完链表结构为图3,此时p2节点就找不到了,导致了内存泄漏的问题

在这里插入图片描述

在这种情况下,我们把insert函数称为不可重入函数;大部分情况下,涉及到全局变量/数据修改的函数都是不可重入函数;可重入函数的概念与之相反

7. volatile关键字


在这里插入图片描述

上述代码,编译器优化和不优化出现两种结果,这是为何?

CPU负责代码中的逻辑/算数运算,如果不进行优化,CPU将内存中的flag变量的值先存放到寄存器中,再进行判断;也就是说寄存器的值随着flag的变化而变化

如果进行优化,寄存器第一次读到flag的值后,不再读取内存中的flag值,CPU往后都是直接判断,所以即使我们修改了内存中的flag值,寄存器的值始终不变化,也就是说寄存器隐藏了内存中的真实值

在这里插入图片描述

要解决这种情况,就要使用volatile关键字,对flag变量进行volatile修饰,表示保持该变量在物理内存可见性;这样,不管编译器如何优化,都是先读内存中的flag值到寄存器,再判断

在这里插入图片描述

8. SIGCHLD信号


子进程在退出时,会留下自己的退出码和退出信息,同时,他还会给父进程发送SIGCHLD信号;如果我们不管子进程的退出码和退出信息,同时不想自己处理子进程的僵尸问题,在捕捉信号时使用SIG_IGN,这样进程退出不会有僵尸问题

在这里插入图片描述

在这里插入图片描述

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

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

相关文章

容器技术-docker4

一、docker资源限制 在使用 docker 运行容器时,一台主机上可能会运行几百个容器,这些容器虽然互相隔离,但是底层却使用着相同的 CPU、内存和磁盘资源。如果不对容器使用的资源进行限制,那么容器之间会互相影响,小的来说…

容器技术-docker2

容器化技术Docker Docker介绍 官网: docker.io docker.com 公司名称:原名dotCloud 14年改名为docker 容器产品:docker 16年已经被更名为Moby docker-hub docker.io docker容器历史 和虚拟机一样,容器技术也是一种资源隔…

为什么前端传了token,后端一直获取不到?一直报跨域错误?

这是我的前端代码 这是我的后端拦截器 那就需要了解一下 预检请求 对于非简单请求(如PUT、DELETE或包含自定义HTTP头的请求),浏览器会先发送一个OPTIONS请求到目标服务器,询问是否允许该跨域请求。这个过程称为预检请求。 当opt…

(超详细)数据结构——“栈”的深度解析

前言: 在前几章我们介绍了线性表的基本概念,也讲解了包括顺序表,单链表,双向链表等线性表,相信大家已经对线性表比较熟悉了,今天我们要实现线性表的另一种结构——栈。 1.栈的概念 栈:一种特殊…

熊猫烧香是什么?

熊猫烧香(Worm.WhBoy.cw)是一种由李俊制作的电脑病毒,于2006年底至2007年初在互联网上大规模爆发。这个病毒因其感染后的系统可执行文件图标会变成熊猫举着三根香的模样而得名。熊猫烧香病毒具有自动传播、自动感染硬盘的能力,以及…

动手学深度学习(Pytorch版)代码实践 -计算机视觉-39实战Kaggle比赛:狗的品种识别(ImageNet Dogs)

39实战Kaggle比赛:狗的品种识别(ImageNet Dogs) 比赛链接:Dog Breed Identification | Kaggle 1.导入包 import torch from torch import nn import collections import math import os import shutil import torchvision from…

x-file-storage一行代码进行文件上传,摆脱阿里云,腾讯云,华为云等不同云的学习,简单高效

问题: 不使用x-file-storage时如果使用某个云首先需要学习他的sdk,这样很麻烦,而x-file-storage集成了各种云的上传,只需要进行配置即可一行代码进行上传 使用 官方地址:X File Storage 一行代码将文件存储到本地、FTP、SFTP、…

【小沐学AI】Python实现语音识别(whisperX)

文章目录 1、简介1.1 whisper1.2 whisperX 2、安装2.1 安装cuda2.2 安装whisperX 结语 1、简介 1.1 whisper https://arxiv.org/pdf/2212.04356 https://github.com/openai/whisper Whisper 是一种通用语音识别模型。它是在各种音频的大型数据集上训练的,也是一个…

时间复杂度计算

要求算法的时间复杂度时,我们可以分析给定表达式 的阶。让我们来逐步分析: 分析阶的定义: 当我们说一个表达式的时间复杂度是 ( O(g(n)) ),我们指的是当 ( n ) 趋近无穷大时,表达式的增长率与 ( g(n) ) 的增长率相似。…

两数之和你会,三数之和你也会吗?o_O

前言 多少人梦想开始的地方,两数之和。 但是今天要聊的不是入门第一题,也没有面试官会考这一题吧…不会真有吧? 咳咳不管有没有,今天的猪脚是它的兄弟,三数之和,作为双指针经典题目之一,也是常…

SolidWorks强大的工程设计软件下载安装,实现更为高效的设计流程

结构分析:SolidWorks作为一款强大的工程设计软件,集成了有限元分析(FEA)工具,这一工具的运用在工程设计领域具有举足轻重的地位。FEA工具在SolidWorks中的集成,使得工程师们能够便捷地对零件和装配体进行精…

第三十八篇——复盘:如何把信息论学以致用?

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么? 四、总结五、升华 一、背景介绍 信息论是一个好的学科,里面穿插的知识符合这个时代我们应该具…

STM32F1+HAL库+FreeTOTS学习2——STM32移植FreeRTOS

STM32F1HAL库FreeTOTS学习2——STM32移植FreeRTOS 获取FreeRTOS源码创建工程窥探源码移植 上期我们认识了FreeRTOS,对FreeRTOS有了个初步的认识,这一期我们来上手移植FreeRTOS到STM32上。 获取FreeRTOS源码 进入官网:https://www.freertos.o…

vue+go实现web端连接Linux终端

vuego实现web端连接Linux终端 实现效果 实现逻辑1——vue 依赖包 "xterm": "^5.3.0","xterm-addon-attach": "^0.9.0","xterm-addon-fit": "^0.8.0"样式和代码逻辑 <template><a-modalv-model:visib…

短视频矩阵系统:打造品牌影响力的新方式

一、短视频矩阵概念 短视频营销革命&#xff1a;一站式解决策略&#xff01;短视频矩阵系统是一款专为企业营销设计的高效工具&#xff0c;旨在通过整合和优化众多短视频平台资源&#xff0c;为企业呈现一个全面的短视频营销策略。该系统致力于协助企业以迅速且高效的方式制作…

【ARM】MCU和SOC的区别

【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 了解SOC芯片和MCU芯片的区别 2、 问题场景 用于了解SOC芯片和MCU芯片的区别&#xff0c;内部结构上的区别。 3、软硬件环境 1&#xff09;、软件版本&#xff1a;无 2&#xff09;、电脑环境&#xff1a;无 3&am…

MySQL高级-MVCC-原理分析(RC级别)

文章目录 1、RC隔离级别下&#xff0c;在事务中每一次执行快照读时生成ReadView2、先来看第一次快照读具体的读取过程&#xff1a;3、再来看第二次快照读具体的读取过程: 1、RC隔离级别下&#xff0c;在事务中每一次执行快照读时生成ReadView 我们就来分析事务5中&#xff0c;两…

java第三十课 —— 面向对象练习题

面向对象编程练习题 第一题 定义一个 Person 类 {name, age, job}&#xff0c;初始化 Person 对象数组&#xff0c;有 3 个 person 对象&#xff0c;并按照 age 从大到小进行排序&#xff0c;提示&#xff0c;使用冒泡排序。 package com.hspedu.homework;import java.util.…

LLM——10个大型语言模型(LLM)常见面试题以及答案解析

今天我们来总结以下大型语言模型面试中常问的问题 1、哪种技术有助于减轻基于提示的学习中的偏见? A.微调 Fine-tuning B.数据增强 Data augmentation C.提示校准 Prompt calibration D.梯度裁剪 Gradient clipping 答案:C 提示校准包括调整提示&#xff0c;尽量减少产生…

数据结构与算法笔记:实战篇 - 剖析Redis常用数据类型对应的数据结构

概述 从本章开始&#xff0c;就进入实战篇的部分。这部分主要通过一些开源醒目、经典系统&#xff0c;真枪实弹地教你&#xff0c;如何将数据结构和算法应用到项目中。所以这部分的内容&#xff0c;更多的是知识点的回顾&#xff0c;相对于基础篇和高级篇&#xff0c;其实这部…