Linux相关概念和易错知识点(25)(信号原理、操作系统的原理、volatile)

目录

1.信号的产生

(1)kill

(2)raise、abort

2.对block、pending、handler表的管理

(1)信号集(sigset_t)

(2)block表的管理

①操作相关的函数

②sigprocmask

(3)pending表的管理

(4)handler表的管理

3.操作系统的原理

(1)硬件中断

①中断控制器

②中断号

③保护现场、恢复现场

(2)时钟源

①主频

②时间片

(3)异常处理

(4)软中断

①软中断和硬件中断

②系统调用表

③缺页中断

④异常、陷阱

(5)用户态和内核态(重点)

①用户区、内核区

②用户态和内核态之间的切换

③函数跳转

(6)捕捉信号流程

①用户态和内核态的切换

②对状态切换的理解

③对执行流概念的深化

(7)volatile

①可重入函数

②volatile、编译器优化

4.信号拓展

(1)SIGCHLD

(2)SIGALRM


1.信号的产生

(1)kill

信号究竟是如何产生的?我们已经知道信号的各种上层规则,但对于信号的来源还不是很熟悉。

看一下下面的代码,就能很快理解了。

kill本身就是个系统调用,而不仅仅是一个指令int kill(pid_t pid, int sig);就可以实现对特定的pid进程发送sig信号,当信号发送成功时返回0,失败时返回-1。

也就是说所谓的kill指令,底层还是kill函数系统调用(指令->系统调用)。因此我们可以理解,当bash进程要kill掉其子进程时,就是调用的kill函数实现的。如当管道的读端关闭,系统会直接杀掉进程,就是使用的kill发送SIGPIPE信号

(2)raise、abort

int raise( int )意思是谁调用,就给自己发送这个信号,也就是说raise(9)可以杀掉自己。

void abort( void )意思是谁调用,就给自己发送SIGABRT 6号终止信号。相当于raise(6)

2.对block、pending、handler表的管理

在介绍了三张表的功能和调用流程之后,我们需要进一步讲讲如何修改这三张表,因为信号从接收到发送的全过程都由这三张表控制,管理这三张表本质上就是在管理信号的处理。

(1)信号集(sigset_t)

未决(pending)和阻塞(block)表都可用相同类型来存储,因为它们本质都是位图。这个位图的结构体是sigsei_t,也称为信号集这个类型在两张表有不同含义:在pending表表示是否有接收到该信号,在block表表示是否阻塞该信号阻塞(block)信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的屏蔽是指阻塞。

我们只需记住信号集的本质是位图,是未决(pending)和阻塞(block)表中位图的专属类型。 

(2)block表的管理

注意以下的函数都是针对block表的信号集!

①操作相关的函数

首先,我们需要自己创建一个sigset_t的变量,再对这个位图进行如下处理:

int sigemptyset(sigset_t *set);将位图清0; int sigfillset(sigset_t *set);将位图填为全1

int sigaddset(sigset_t *set, int signum);添加对signum号信号的屏蔽(本质就是修改位图,将对应位改为1);int sigdelset(sigset_t *set, int signum);删除对signum号信号的屏蔽

上述返回值都是0为成功,-1为错误

②sigprocmask

接下来我们要让我们的改动生效,上述所有的改动都是用户自己的修改,并没有写到内核中去。

int sigprocmask(int how, const sigset_t *newset, sigset_t *oldset);

how由如下宏定义决定功能:SIG_BLOCK增加传入的newset中状态为1的信号的阻塞;SIG_UNBLOCK解除传入的newset中状态为1的信号的阻塞;SIG_SETMASK直接用newset覆盖block表中的位图。注意这个newset就是刚才我们进行各种处理后的sigset_t变量(输入型参数)。oldset是输出型参数,是修改前的位图,帮助我们恢复原来的位图。

除了上述用法之外,sigprocmask(0, NULL, &oldset)可以获取当前的block表

下面的代码,我利用阻塞表阻塞了2号信号的处理,使得信号无法被处理

(3)pending表的管理

int sigpending(sigset_t *set);用于获取pending表,set是一个输出型参数,该函数只提供内核中的pending表而不提供修改。其返回值0表示成功,-1表示失败。

int sigismember(const sigset_t *set, int signum); 判断signum对应信号是否有效。pending和block表都可以用,因为它们的位图的数据类型一致。我们需要手动传入sigset_t,这需要我们使用sigpromask、sigpending获取当前block、pending表。其返回值是1为真,0为假,-1为错误

(4)handler表的管理

signal函数可以进行信号捕捉,进而修改handler表,这里提一句即可。

int sigaction(int signum, const struct sigaction* newact, struct sigaction* oldact);

对于这个结构体,通常flag设置为0,sa_handler设置为指定的void(int)函数指针,使用为sigaction(2, &act, &oldact),即将信号2的默认处理方式替换为自定义处理,并且得到一个oldact用于备份。

struct sigaction的sa_mask成员还可以顺便帮我们添加要屏蔽的信号,我们自己设置sigset_t,在后续调用sigaction函数时,除了相应pending表被修改,block表也会加上sa_mask里面的几个屏蔽的信号。

3.操作系统的原理

要进一步理解信号,我们要对OS进一步深挖,来解释为什么我们键盘输入信号后OS能够及时的处理?是否需要OS一直等待键盘输入?同步异步是如何实现的?

(1)硬件中断

OS怎么知道键盘输入数据了?是否需要OS一直等待键盘输入?事实上,OS是启动的第一个软件,它不会轮询设备,设备数量太多了,键盘、磁盘、显示屏等,而是外设提醒OS后OS再来处理。

①中断控制器

以键盘输入为例,键盘按下会首先触发硬件中断(由硬件电路实现),这个硬件中断的信号不会直接传给CPU(物理上键盘并没有直连CPU),而是传给中断控制器(和每个设备直连),中断控制器再向CPU发送信息,进而被OS获取。

注意中断控制器可不会关心键盘按什么键,它只会告诉CPU发生了中断,CPU也不会关心,它收到中断后只会告诉OS有外设准备好了,之后在OS的管理下,键盘给CPU传信息,进而实现给OS传信息。至于键盘输入了什么,那是OS之后需要干的事

再以磁盘为例,当磁盘想要给进程发信息时,虽然磁盘的寻址需要时间,要定位要准备,但这段时间OS不管,继续执行自己的任务,仅当磁盘准备好后中断控制器直接向CPU发送信号,执行读取操作即可。这样,硬件和OS实现了并行执行。

这里我们就会发现,信号和硬件中断有相似之处,信号是纯软件模拟中断的行为,硬件中断是靠软硬结合实现的(绝大部分是硬件)。

②中断号

对于中断控制器而言,每个设备都对应一个中断号,中断控制器利用中断号触发高电平告诉CPU谁中断了。这些中断号OS明白对应哪些设备,此时OS就回去调用对应的处理方法。要管理这些处理方法,OS有一个中断向量表IDT,这是一个函数指针数组,中断号就相当于数组下标。当中断控制器告诉OS中断号后,OS直接由IDT调用中断服务处理中断。这些中断服务包括读取硬盘、网卡等。方法内容由OS自定或安装驱动确定,同时我们也要知道中断号和中断处理方法是相对固定的,是软硬结合共同维护的。

③保护现场、恢复现场

在OS接收到中断控制器的信号,确定要处理中断之后,OS会将当前CPU各种寄存器的数据保存在中断的上下文,方便后续处理。保存好寄存器数据后,OS就会根据中断向量表找方法,执行中断处理例程(利用中断号查表)。处理完后就会恢复现场,继续执行任务。

OS通过中断实现了不主动轮询外设。所有外设(不含内存,它是存储器)都是这么处理的。

(2)时钟源

进程可以在OS指挥下调度执行,那么OS自己被谁指挥呢?时钟源。

①主频

我们已经知道了所有外设的信息是如何进入进程被处理的了,其中有一个外设,每隔一段极短时间就会给CPU发硬件中断,这个外设就是时钟源,它具有一个固定的中断号,以及一个固定的中断服务:进程调度。时钟源会一直推动OS进行进程调度,OS就是基于不断中断,调用中断向量表工作的。现在时钟源已经被集成在CPU内部,时钟源的触发频率就是CPU主频的概念(如14900K的6GHZ)。主频速度越快,处理任何操作都会更频繁。

OS在时钟的推动下自行调度,相当于说OS可以不做任何事,启动之后直接让自己进入死循环,利用时钟和其它外设触发中断推着它进行运行,执行方法。

总结:对于OS来说,只要完全没有进程,它就陷在死循环中,但只要有进程,就会一直忙着调度,这是时钟源的中断推着它进行的。我们因此可以说操作系统就是躺在中断中运行的。与此同时,OS也会自己去fork一些进程,这是内核固定进程,会定期去检查OS运行状态,定期把内核缓冲区数据进行刷新、检查闹钟等操作。 

②时间片

时钟中断是固定时间的,假设为1ns。进程的调度都设置了时间片,轮询着调度以尽量保持公平。OS要实现时间片,只需设置int count,当count == 1000(1微秒),每次时钟源中断触发进程调度都会让count--,count减到0后直接切换进程。

时间片就是在主频下的计数器,是以时钟中断为基础构建的。

(3)异常处理

我们已经知道,程序的崩溃就是靠信号终止的。

其中野指针触发段错误,操作系统直接用11号信号杀掉进程,我们捕捉信号后发现OS一直在触发信号。

同理,a / 0也会崩溃,会触发13号信号SIGFPE终止进程。

为什么OS知道我们的进程的内部出错了,为什么OS会一直触发信号?

以a / 0为例,CPU中有寄存器,a -> eax,0 -> ebx, 计算结果 -> ecx。同时还有个状态寄存器Eflags,有溢出标记位,默认为0,保证会把结果正常返回。当溢出标记位为1,OS就知道CPU硬件内部出错了,OS找到对应的进程后就要用信号杀掉损坏CPU的进程。

当我们捕获信号之后,我们的进程没有退出,直到时间片到了,进行进程切换时会OS会保存寄存器数据,以便下次调用恢复。其中Eflags的标记位也被保存了,OS不会修改它。由此以来,每当调度到这个进程时,CPU都会显示异常,OS会一直尝试杀我们的进程,这导致了死循环调用信号处理。

同理,OS怎么判断野指针呢?CR3寄存器保存页表起始地址,虚拟 -> 物理地址的转换是MMU内存管理单元操作的,它集成在CPU中。当访问野指针(虚拟地址)时,MMU转换去读0,发现无法访问,于是触发了硬件错误,OS知道后发送信号尝试杀掉进程。当后续再轮换调用时,MMU的错误被完整地继承了下来,所以OS会循环发送信号。

(4)软中断

①软中断和硬件中断

上述都是外部硬件中断,需要硬件设备触发。有没有仅靠软件进行中断的呢?

为了让OS支持系统调用,CPU专门设计了对应的汇编指令(int或者syscall),在没有外设下,可以让CPU内部触发中断逻辑。这些汇编指令就可以写在软件中,通过汇编指令 + 中断号推动CPU执行中断向量表方法,这就叫软中断。硬件中断和软中断仅仅是触发方式变为汇编调用,其余流程一致,这很好理解。

两种触发方式最终都是利用中断号调用中断向量表中的函数。例如,int N 指令会触发中断号为 N 的软中断,操作系统会根据该中断号来找到对应的处理函数(范围从 0x00 到 0xFF(即从 0 到 255))

②系统调用表

系统调用也是通过中断完成的。OS系统调用都在系统调用表,集中管理,是由“下标”调用的。系统调用表特别底层且固定,用户无法查看这个表的存在。这个表中各个函数的下标叫系统调用号,在中断向量表中设置一个系统调用的入口函数,用系统调用号调用。

要调用系统调用,就要想办法软中断,int 0x080就是软中断到执行系统调用入口函数,进入固定例程。系统调用号会提前写到寄存器中,可以直接被获取。

系统调用的过程:先把要调用的系统调用号用寄存器存起来,再触发软中断陷入内核,OS根据中断向量表开始进入中断服务,根据寄存器里的系统调用号自动查系统调用表,执行对应方法。

OS提供的系统调用接口根本不是C函数,而是系统调用号 + 约定传递的参数,通过触发软中断、进行中断服务调用的方式实现系统调用功能的。系统调用的真正的底层实际上是汇编。

GNU、glibc给系统进行C语言封装,给我们提供C语言的系统调用。所以我们用的所有系统调用都是C的,是它对该平台的系统调用进行了统一的上层封装,这也是C具有跨平台性的本质。C语言的上层封装还保证了我们系统调用的安全性,不会导致对系统造成伤害。

③缺页中断

缺页异常意思就是有虚拟地址,但物理内存没有申请(分批加载)。当OS需要用到相应空间,但发现还没有物理内存时,就会触发中断,申请并加载物理内存

④异常、陷阱

OS就是躺在中断例程上的代码块,几乎所有操作都会转为中断。缺页中断、内存碎片处理、除零、野指针错误都是转为软中断,OS会设置中断号走中断例程。异常、陷阱都会转为软中断来处理。

如果发生了软中断且不是因为出错,就是单纯为了陷入中断(系统调用),这叫做陷阱

而像除零、野指针这种触发错误导致软中断的叫做异常。

OS会根据不同错误或者陷阱由中断号进行调用。

(5)用户态和内核态(重点)

①用户区、内核区

在32位操作系统下,进程的地址空间划分了4G,其中这块虚拟的地址空间包含了我们已知的堆栈段、代码段、数据段、以及命令行参数列表等。所有的这一切所在的内存区域都属于用户区。用户区从0G ~ 3G,共3G的大小。

3G ~ 4G这块叫做内核区,这块内核区也是虚拟的,它拥有共同的内核页表,同用户页表一样,都是映射到物理内存中的。不过需要注意的是,不同进程的内核区都会映射到同一块物理内存。

这里需要注意的是,用户页表每个进程一份,因为虚拟地址和物理地址都要根据不同进程实际情况映射。;而内核页表则是整个系统一份,无论是虚拟地址还是映射的物理地址都是同一份。对于任何进程来说,无论如何调度,它们都能找到同一个OS,访问同一张中断向量表和系统调用表。

无论调用任何函数(库、系统调用),都是在我们自己用户区进行调用的(代码段在用户区)。而被调用的系统调用方法的执行是在内核区进行的。

本质上说,内核区映射到同一块空间是因为OS只有一个,那么如果使得进程映射多块空间,就相当于启动了两个系统,这就是内核虚拟机的思路,当然还有用户虚拟机,这里提一句。

②用户态和内核态之间的切换

前面我们已经知道用户区和内核区了,这两个区域只能被具有对应的身份的人访问,标记身份的就是用户态和内核态。

如果我们要进入内核区,我们需要将我们的身份进行改变,即用户态 -> 内核态;同理,如果要访问用户区,我们也应该将内核态 -> 用户态。 

标记用户态和内核态的是CPU的CS段寄存器。它有两个bit位的标志(CPL):00表示内核,11表示用户。修改用户内核态的本质就是修改标志位。我们都知道MMU是集成在CPU内部,负责进行虚拟地址 -> 物理地址的,只要CPL不允许访问,MMU也自然不会给我们转换地址,我们也自然访问不了对应的空间。用户态和内核态的权限管理是硬件层面的。

当我们在用户区调用系统调用时,会使用int (中断号) 和 syscall (中断号) 触发软中断,进到中断向量表里面去执行系统调用入口函数,进而访问系统调用表,执行系统调用方法,最后回到调用处继续执行代码。在整个过程中,当触发软中断时就会第一次切换状态,将权限标志位CPL改为0,进入内核态。执行完后返回调用处又会切换一次,CPL改为3,进入用户态。

③函数跳转

在上述流程中,我们会有疑问:函数是如何跳转的,跳转回来时如何找到最开始的地址的?A函数调用B,A会把调用B的下一个地址先入栈,后面出栈后就能够找到下一句地址。由此以来就能正确地在内核区和用户区之间进行跳转。汇编层面所有操作都是基于寄存器、地址的。

(6)捕捉信号流程

有了前面知识的积累,我们能够更底层地理解信号捕捉的流程了。

①用户态和内核态的切换

如果来了一个信号,有可能当前进程正在做更重要的事,我们要把信号保存到pending表中。当进程从内核态切换回用户态的时候,进程就会进行信号检查do_signal(),检测当前的pending和block表决定是否处理信号。

当要处理信号时,会根据不同处理信号的方式决定走向。如果是DFL和IGN的情况,那么进程会不急着返回用户态,会在内核态把方法执行完成后再返回(DFL和IGN的代码都在内核区);如果我们自定义捕捉了信号,这些代码都在用户区保存,所以我们要切换回用户态执行处理代码处理之后,会回到内核态,执行sys_sigreturn()返回主程序,回到用户态

②对状态切换的理解

为什么执行自定义函数时要做权限切换?直接用内核态执行不行吗?自定义函数存在用户区,需要用户态,而内核态权限更高,切换回用户态是为了避免安全风险,防止内核态被利用。同时内核态进入用户态后执行自定义函数,意味着用户态的操作后果自负!

③对执行流概念的深化

我们执行完自定义函数后,想要回到主程序。为什么不直接回去,反而还要进入内核态呢?实际上自定义函数和主程序没有任何关系,在程序中不存在任何调用关系,信号处理和主程序是两个完全不同的执行流,因此只能先返回到内核。当触发硬件中断,OS会保护现场,存储各个寄存器的状态。其中pc寄存器就保存下一条指令的地址,在这里就是主执行流的下一条指令的地址。因此回到用户态,需要在内核态调用sys_sigreturn()函数,恢复上下文,才可以回到初始时的在主程序执行流。

到这里,我们也能彻底理解信号处理没有新开一个进程,主执行流和信号捕捉执行流,信号捕捉执行流没有在主执行流中被调用,而只是一个主执行流中的分支,进程调用过程中两个分支不会冲突。

下面是执行流切换的过程,仔细体会

(7)volatile

①可重入函数

若一个函数被两个以上执行流同时进入,就有可能遇到下面这种情况,这是单执行流的情况下遇不到的。


因此实例的insert函数是不可重入函数。相对的,还有可重入函数,也就是可以被被两个以上执行流同时进入同时不发生错误的函数。怎么判断呢?

只要使用了全局资源的,new了空间的,基本都是不可重入的,使用的都是局部变量的就可能是可重入的基本上STL都不可重入,大部分函数也都不可重入。但也有专门设计的系统接口带_r,表示可重入。

volatile、编译器优化

gcc有优化选项 -O0基础优化,-O1,-O2,-O3优化级别依次增加。

对于主程序执行流和信号处理执行流来说,由于它们从语法上没有任何联系,但实际上可以通过全局资源联系,编译时就有可能被优化出bug。

若在主执行流中对一个全局变量没有修改,而实际上这个全局变量在信号捕捉执行流中被处理。对于高优化的编译器来说,这个全局变量在信号捕捉执行流中的处理无效。我们要从底层来理解优化。

当编译器优化很大时,一些主执行流没有修改的变量会变成寄存器变量,这个变量永远无法被修改。当我们使用这个变量来进行判断时,逻辑判断会直接用寄存器里面的值进行判断而不会使用内存的数据这就导致信号捕捉执行流中修改变量不会生效,寄存器 + 优化屏蔽了内存的可见性。

如果担心被优化导致出现bug,我们可以使用volatile关键字修饰全局变量,volatitl int flag = 0;就不会被寄存器屏蔽了,这个关键字相当于告诉编译器不要对该变量进行任何优化,以保持内存可见性

4.信号拓展

(1)SIGCHLD

父进程一般关心子进程什么时候死,这样好回收,以免出现孤儿或者僵尸。事实上,子进程退出时都会向父进程发送SIGCHLD信号,告诉父进程自己结束了。不过默认情况下SIGCHLD的Action是Ign(忽略)。我们可以手动捕获这个信号,当父进程收到信号时再去wait,这样就能实现父子进程异步执行,而不是让父进程一直阻塞在原地。

但是有个问题,即信号处理过程中最多允许再接收一个信号,后续的没意义(pending已经为1了),所以每次需要waitpid + WNOHANG循环,用返回值来判断是没等完还是等完了。

仅Linux下,signal(SIGCHLD, SIG_IGN)处理后,这样fork出来的子进程在终止时会自动清理掉,没有僵尸,不过父进程也得不到status。这里可认为是Linux的特殊处理,仅仅用来处理僵尸的情况。

(2)SIGALRM

alarm(seconds)函数可以设置一个一次性闹钟信号(只会响一次,重复使用需要多次调用),闹钟时间到了之后会发送一个14号SIGALRM信号,默认就是Term终止。我们可以捕获它,利用alarm写一个统计服务器1s执行某种操作多少次的代码,可以用alarm来对比IO对效率的影响有多大等操作。

alarm(0)取消闹钟,其返回值是闹钟剩余时间。如果闹钟自己响了,那返回值就是0。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

opencv中的色彩空间及其转换

在 OpenCV 中,色彩空间(Color Space)指的是表示颜色的一种方式,或是用数学模型对颜色的表达。不同的色彩空间采用不同的方式来描述颜色的三要素(如亮度、饱和度、色调),因此可以在不同的应用场景…

OPPO 数据分析面试题及参考答案

如何设计共享单车数据库的各个字段? 对于共享单车的数据库设计,首先考虑用户相关的字段。用户表可以包含用户 ID,这是一个唯一标识符,用于区分不同用户;姓名,记录用户的真实姓名;联系方式,比如手机号码,方便在出现问题时联系用户;注册时间,记录用户何时开始使用共享…

在Ubuntu下运行QEMU仿真FreeBSD riscv64系统

在Ubuntu下运行QEMU仿真FreeBSD riscv64系统 突发奇想,尝试在Ubuntu下运行QEMU仿真FreeBSD riscv64系统, 参考这篇文档:手把手教你在QEMU上运行RISC-V Linux_qemu 运行 .bin-CSDN博客 并参考FreeBSD的Wiki:riscv - FreeBSD Wik…

大模型微调---Prompt-tuning微调

目录 一、前言二、Prompt-tuning实战2.1、下载模型到本地2.2、加载模型与数据集2.3、处理数据2.4、Prompt-tuning微调2.5、训练参数配置2.6、开始训练 三、模型评估四、完整训练代码 一、前言 Prompt-tuning通过修改输入文本的提示(Prompt)来引导模型生…

Visual Studio 、 MSBuild 、 Roslyn 、 .NET Runtime、SDK Tools之间的关系

1. Visual Studio Visual Studio 是一个集成开发环境(IDE),为开发者提供代码编写、调试、测试和发布等功能。它内置了 MSBuild、Roslyn 和 SDK Tools,并提供图形化界面来方便开发者进行项目管理和构建。与其他组件的关系&#xf…

Winnows基础(2)

Target 了解常见端口及服务,熟练cmd命令,编写简单的 .bat 病毒程序。 Trail 常见服务及端口 80 web 80-89 可能是web 443 ssl心脏滴血漏洞以及一些web漏洞测试 445 smb 1433 mssql 1521 oracle 2082/2083 cpanel主机管理系统登陆(国外用的…

Edge Scdn用起来怎么样?

Edge Scdn:提升网站安全与性能的最佳选择 在当今互联网高速发展的时代,各种网络攻击层出不穷,特别是针对网站的DDoS攻击威胁,几乎每个行业都可能成为目标。为了确保网站的安全性与稳定性,越来越多的企业开始关注Edge …

通信技术以及5G和AI保障电网安全与网络安全

摘 要:电网安全是电力的基础,随着智能电网的快速发展,越来越多的ICT信息通信技术被应用到电力网络。本文分析了历史上一些重大电网安全与网络安全事故,介绍了电网安全与网络安全、通信技术与电网安全的关系以及相应的电网安全标准…

梯度(Gradient)和 雅各比矩阵(Jacobian Matrix)的区别和联系:中英双语

雅各比矩阵与梯度:区别与联系 在数学与机器学习中,梯度(Gradient) 和 雅各比矩阵(Jacobian Matrix) 是两个核心概念。虽然它们都描述了函数的变化率,但应用场景和具体形式有所不同。本文将通过…

时间序列预测论文阅读和相关代码库

时间序列预测论文阅读和相关代码库列表 MLP-based的时间序列预测资料DLinearUnetTSFPDMLPLightTS 代码库以及论文库:Time-Series-LibraryUnetTSFLightTS MLP-based的时间序列预测资料 我会定期把我的所有时间序列预测论文有关的资料链接全部同步到这个文章中&#…

引言和相关工作的区别

引言和相关工作的区别 引言 目的与重点 引言主要是为了引出研究的主题,向读者介绍为什么这个研究问题是重要且值得关注的。它通常从更广泛的背景出发,阐述研究领域的现状、面临的问题或挑战,然后逐渐聚焦到论文要解决的具体问题上。例如,在这篇关于联邦学习数据交易方案的…

GitLab分支管理策略和最佳实践

分支管理是 Git 和 GitLab 中非常重要的部分,合理的分支管理可以帮助团队更高效地协作和开发。以下是一些细化的分支管理策略和最佳实践: 1. 分支命名规范 • 主分支:通常命名为 main 或 master,用于存放稳定版本的代码。 • …

批量提取zotero的论文构建知识库做问答的大模型(可选)——含转存PDF-分割统计PDF等

文章目录 提取zotero的PDF上传到AI平台保留文件名代码分成20个PDF视频讲解 提取zotero的PDF 右键查看目录 发现目录为 C:\Users\89735\Zotero\storage 写代码: 扫描路径‘C:\Users\89735\Zotero\storage’下面的所有PDF文件,全部复制一份汇总到"C:\Users\89735\Downl…

LabVIEW实现NB-IoT通信

目录 1、NB-IoT通信原理 2、硬件环境部署 3、程序架构 4、前面板设计 5、程序框图设计 6、测试验证 本专栏以LabVIEW为开发平台,讲解物联网通信组网原理与开发方法,覆盖RS232、TCP、MQTT、蓝牙、Wi-Fi、NB-IoT等协议。 结合实际案例,展示如何利用LabVIEW和常用模块实现物联网…

面试题整理9----谈谈对k8s的理解2

面试题整理9----谈谈对k8s的理解2 1. Service 资源1.1 ServiceClusterIPNodePortLoadBalancerIngressExternalName 1.2 Endpoints1.3 Ingress1.4 EndpointSlice1.5 IngressClass 2. 配置和存储资源2.1 ConfigMap2.2 Secret2.3 PersistentVolume2.4 PersistentVolumeClaim2.5 St…

精准采集整车信号:风丘混合动力汽车工况测试

一 背景 混合动力汽车是介于纯电动汽车与燃油汽车两者之间的一种新能源汽车。它既包含纯电动汽车无污染、启动快的优势,又拥有燃油车续航便捷、不受电池容量限制的特点。在当前环境下,混合动力汽车比纯电动汽车更符合目前的市场需求。 然而&#xff0c…

带标题和不带标题的内部表

什么是工作区? 什么是工作区?简单来说,工作区是单行数据。它们应具有与任何内部表相同的格式。它用于一次处理一行内部表中的数据。 内表和工作区的区别 ? 一图胜千言 内表的类型 有两种类型的内表: 带 Header 行…

【图像分类实用脚本】数据可视化以及高数量类别截断

图像分类时,如果某个类别或者某些类别的数量远大于其他类别的话,模型在计算的时候,更倾向于拟合数量更多的类别;因此,观察类别数量以及对数据量多的类别进行截断是很有必要的。 1.准备数据 数据的格式为图像分类数据集…

【Leetcode 每日一题】2545. 根据第 K 场考试的分数排序

问题背景 班里有 m m m 位学生,共计划组织 n n n 场考试。给你一个下标从 0 0 0 开始、大小为 m n m \times n mn 的整数矩阵 s c o r e score score,其中每一行对应一位学生,而 s c o r e [ i ] [ j ] score[i][j] score[i][j] 表示…

React系列(八)——React进阶知识点拓展

前言 在之前的学习中,我们已经知道了React组件的定义和使用,路由配置,组件通信等其他方法的React知识点,那么本篇文章将针对React的一些进阶知识点以及React16.8之后的一些新特性进行讲解。希望对各位有所帮助。 一、setState &am…