当前位置: 首页 > news >正文

【Linux】信号的产生、保存、捕捉处理 (四种信号产生、核心存储、用户态与内核态、信号集及其操作函数)

文章目录

    • 1、什么是信号?
    • 2、信号的产生
      • 2.1 通过键盘产生信号
      • 2.2 通过系统调用产生信号
      • 2.3 硬件异常产生的信号
      • 2.4 由软件条件产生的信号
      • 2.5 进程的核心转储
    • 3、信号的保存
    • 4、信号的捕捉
      • 4.1 用户态和内核态
      • 4.2 用户态到内核态的切换
      • 4.3 信号捕捉过程
    • 5、信号集操作函数以及测试

1、什么是信号?

在生活上

比方说到了的外卖的提醒,这是一种信号。 我们对于这种信号有着自己的处理方式和意识,这就是处理信号。

值得注意的是,信号到来的时候,可能会因为有更加紧急的事,促使我们会先忽略这个信号,并且先保留这个信号。

在处理信号的时候,可能不同的个体会有着不同的处理动作。(比如红绿灯信号,一般人都是红停绿走,但是在一些可能存在的特殊群体下他们会有不一样的动作。)

在计算机上

首先,信号是给进程发的。(比如kill -9 pid)
进程如果需要识别信号,就一定要对信号有认识,并且有处理动作。

当进程收到信号可能有更重要的代码要执行,所以信号不一定会被处理。
那么进程本身就必须有对信号进行保存的能力

进程捕捉信号再对信号进行处理,一般有三种动作(默认动作,自定义动作,忽略动作)

通过 kill -l 可以看到所有信号,一个信号的数字对应其宏名。
其中 1–31号称为普通信号,34–64号称为实时信号。这里不关注实时信号。

在这里插入图片描述
通过man 7 signal 查看signal手册
再通过/Standard signals 可以查看所有信号对应的内容

在这里插入图片描述

如果一个信号发给进程,进程应该保存在哪呢?

可以推测:
信号将会保存在进程的task_struct{… unsigned int signal; …}
通过位图的结构,每一个bit位置代表一种信号,0和1代表是否受到信号,0无,1收到。
在这里插入图片描述

发送信号的本质就是修改PCB中signal的位图,
而对应PCB是由操作系统通过数据结构进行管理的,所以如果需要修改信号,就需要通过OS修改。
也就是说信号发送的方式,本质就是OS向目标进程发生信号。

修改OS数据只能通过操作系统接口来修改。
那么操作系统就应该提供发送信号的接口,而kill命令一定是调用了某种系统接口实现的。

下面就来证明这些推测。

2、信号的产生

进程可以通过以下四种方式收到信号。

2.1 通过键盘产生信号

系统提供的发送信号的接口 signal
在这里插入图片描述

这个接口用来捕捉信号,也就是当进程收到操作系统发送的sig信号后,将会由原来的默认动作转换成执行func函数中的自定义动作。
如果没有收到信号sig,将不会执行。

参数 sig : 对应信号名
参数 func 对应信号的三个处理动作:
SIG_DFL 默认的信号处理程序。
SIG_IGN 忽视信号。
或者传用户写的对应自定义动作的函数指针。

下面是一个捕捉2号信号和3号信号的例子:
2号信号的默认动作是在用户键盘输入ctrl+c时会退出前台进程。
3号信号的默认动作是在用户键盘输入ctrl+\时会退出前台进程。

#include <iostream>
#include <signal.h>
#include <unistd.h>using std::cout;
using std::endl;void handler(int signal)
{cout << signal << ":自定义处理\n" << endl;exit(0);
}int main()
{signal(2, handler); //2号信号SIGINT ctrl+c 触发 //signal(3, handler); //3号信号SIGQUIT ctrl+\ 触发 while(true){cout << "hello world" << endl;sleep(1);}return 0;
}

在这里插入图片描述
在这里插入图片描述

2.2 通过系统调用产生信号

操作系统是有能力向目标进程发送信号,但是得由用户来操作。

在这里插入图片描述

向一个pid进程发送sig信号。成功返回0,失败返回-1。
kill()可以向任意进程发送任意信号。

可以通过kill调用来模拟kill命令,只要向sig中发送9号信号就完成了kill命令。

//mysignal.cc
#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <cassert>
#include <sys/types.h>using std::cout;
using std::endl;static void Usage(const std::string& proc)
{cout << "\nUsage: " << proc << " signo pid\n" << endl;
}int main(int argc, char* argv[])
{if(argc != 3){Usage(argv[0]);exit(1);}int signo = atoi(argv[1]);pid_t pid = atoi(argv[2]);int n = kill(pid, signo);assert(n == 0);return 0;
}
/
//mytest.cc
#include <iostream>
#include <unistd.h>
#include <sys/types.h>int main()
{while(true){printf("我是一个正在运行的进程, pid: %d\n", getpid());sleep(2);}return 0;
}

在这里插入图片描述


raise调用
在这里插入图片描述

raise()函数发送一个任意信号给调用者
sig指明信号。

举例

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>int main()
{int cnt = 0;while(cnt++ < 10){printf("cnt : %d\n", cnt);if(cnt == 5) raise(9); //kill(getpid(), sig);}return 0;
}

在这里插入图片描述


abort调用
在这里插入图片描述

abort向当前进程发送 6) SIGABRT 信号,中止当前进程。

#include <iostream>
#include <stdlib.h>int main()
{int cnt = 0;while(cnt++ < 10){printf("cnt : %d\n", cnt);if(cnt == 5) abort(); //kill(getpid(), SIGABRT)}return 0;
}

在这里插入图片描述

关于信号的处理行为的理解:
很多情况,进程收到大部分的信号,都是为了中止进程。
因为信号的不同,代表不同的事件,但结果是可以相同的。

2.3 硬件异常产生的信号

CPU异常产生的信号

#include <iostream>
#include <unistd.h>
#include <signal.h>void catchSig(int signo)
{printf("捕获信号:%d\n", signo);sleep(2);
}int main()
{//捕获 8)SIGFPE 信号//Floating point exceptionsignal(8, catchSig);int n = 10/0; //return 0;
}

在这里插入图片描述

当代码执行后,由于除0的错误被操作系统捕获,向该进程发送了8号信号,通过signal捕获后,确认是8号信号,并且发现一个怪事。

为什么捕获信号后,一直打印?
这是因为处理器在处理10/0时,由于除0造成数据溢出,将一个状态寄存器的溢出标记位置为1。

CPU向OS发送运算异常的信息,根据寄存器上下文确定进程,OS就对进程发送了信号。这个信号默认是中止进程,被捕获后改成了打印。

由于进程收到信号后没有退出,因为进程切换,无数次寄存器当再次加载这个进程上下文,就让OS识别到了CPU内部状态寄存器溢出位是1。


MMU异常产生的信号

#include <iostream>
#include <unistd.h>
#include <signal.h>void catchSig(int signo)
{printf("捕获信号:%d\n", signo);exit(0);
}int main()
{signal(11, catchSig);int* p = nullptr;*p = 100; //野指针访问 OS发送 11) SIGSEGV信号return 0;
}

在这里插入图片描述

代码中p访问的是虚拟地址空间的地址,虚拟地址映射到页表,再通过MMU(内存管理单元,集成在CPU当中)读取页表地址放入到对应物理地址。

p解引用访问0号地址,MMU因为非法访问的原因发生异常,操作系统识别到异常将11号信号发生给进程。

所以以上就是由硬件自发的让OS给进程发送信号。
OS给进程发送信号虽然结果一样,但是由于种类不同,也能让进程知道发送了何种错误。

2.4 由软件条件产生的信号

在之前管道通信中,如果一个管道只写不读,就会浪费空间。
操作系统面对这种情况就会给进程发送一个SIGPIPE信号。
在这里插入图片描述

这种当fds[0]关闭,fds[1]存在的条件判断所造成的信号,就是软件产生的信号。


下面的alarm 函数调用也会产生一个14号信号。
在这里插入图片描述

闹钟功能,seconds秒后,向进程发送一次SIGALRM信号。
这个信号默认也是中止进程。

下面看两段代码

#include <iostream>
#include <unistd.h>
#include <signal.h>static int cnt = 0;void catchSig(int signo)
{printf("捕获信号:%d, 最终cnt:%d\n", signo, cnt);exit(0);
}int main()
{signal(14, catchSig);alarm(1); // 14) SIGALRMwhile(true){cnt++;printf("cnt:%d\n", cnt);}return 0;
}

在这里插入图片描述

#include <iostream>
#include <unistd.h>
#include <signal.h>static int cnt = 0;void catchSig(int signo)
{printf("捕获信号:%d, 最终cnt:%d\n", signo, cnt);alarm(1);
}int main()
{signal(14, catchSig);alarm(1); // 14) SIGALRMwhile(true){cnt++;}return 0;
}

在这里插入图片描述

首先第一段代码,展示了数据通过外设和网络打印是多快。
第二段代码,没有打印数据,单纯反应了cpu的计算力,并且通过两个alarm函数简单的实现了sleep()的功能。

那么为什么alarm函数能看作一个软件条件产生的信号呢?
因为alarm本质就是通过用户层上数据结构实现的,是一种软件。
任何进程都可以使用alarm系统调用在内核中设置闹钟,这么多的闹钟就一定需要被OS进行结构体描述后,通过相应数据结构管理。
(比如通过堆结构来管理闹钟,通过闹钟的里到点最近时间建立最小堆)

2.5 进程的核心转储

下面运行这个代码

很明显,它会因为非法内存引用报错。

#include <iostream>
#include <unistd.h>
#include <signal.h>int main()
{while(true){int arr[10];arr[10000] = 10;}return 0;
}

在这里插入图片描述

在之前看信号的标准内容的时候,发现每个信号有着自己的Action,而这其中有Term和Core两种类型。

这里是引用

这两个主要的区别是,core类型的信号在信号中止异常进程后,会在本地文件生成一个核心文件,这个文件在对应时刻存储着进程的有效数据。
Term类型的信号中止进程后就不会有这个步骤。


core file文件在云服务器默认关闭,通过ulimit -a可以看默认限制的资源,看到core file默认是关闭的。
通过ulimit -c 相应size 可以设置core文件。
在这里插入图片描述
在这里插入图片描述

再次运行程序:
与之前不同的是运行结果多了个(core dumped),意为核心转储
核心转储:当进程出现异常时,进程在对应时刻,将内存中的有效数据转储在磁盘中,也就是下面的core.14626(对应pid)文件中。

在这里插入图片描述

通过gdb 调试,输入core-file 对应核心文件名,就可以生成进程具体的异常原因。
看到由于11号信号,发送了段错误。
在这里插入图片描述

3、信号的保存

前面介绍了信号产生的四种情况,那么进程在收到信号后具体是怎么保存的呢?

首先了解一些概念

  1. 进程实际执行信号处理的动作称为信号递达(Delivery)
  2. 信号从产生到处理的中间的状态,称为信号未决(Pending)
  3. 进程可以选择阻塞某个信号。(可以在信号传递之前,选择阻塞这个信号,当信号到来时,阻塞信号)
  4. 当进程选择阻塞的信号到来时,信号处于未决状态。直到接触对这个信号的阻塞,才执行递达动作。
  5. 忽略和阻塞不同,阻塞是在执行递达动作前可以选择的,而忽略是在递达动作后可以选择的一种动作。


在进程的PCB中,其实有三种数据结构:
在这里插入图片描述

其中:
pending表(未决表): 其作为一个位图结构用来保存收到了哪些信号,通过bit位置表示具体信号,通过1/0来表示是否收到信号。(比如从右到左第一个bit位为1,代表收到了1号信号)
block表(阻塞表): 其作为一个位图结构用来保存阻塞了哪些信号,通过bit位置表示具体信号,通过1/0来表示是否阻塞信号。
handler表: 其作为一个函数指针数组,每个数组下标对应一种信号,通过下标可以调用对应信号的处理函数。(比如通过下标1,调用1号信号的处理函数)

如果一个信号对应pending表位置为1,block表位置为1,说明这个信号被阻塞,处于未决状态。
如果一个信号对应pending表位置为1,block表位置为0,说明这个信号抵达。
如果在信号抵达前,收到了多次信号,Linux下对普通信号只抵达一次,而如果是实时信号,将会放入一个队列中,这里不讨论实时信号。

综上,信号是由PCB保存的,发送信号修改PCB,因为PCB也是操作系统数据,而操作系统只能通过操作系统自身修改,也就是说信号是由操作系统发送的

操作系统拥有发送信号的能力,但是它无法自己使用这个能力,这就需要用户来运用它的能力,也就是说需要用户进程通过相应系统调用发送信号。

4、信号的捕捉

在之前,我们知道了信号是由进程通过系统调用修改PCB对应数据结构所产生,是由OS发送的,知道了信号如何产生,如何保存,那么信号是如何被进程接收处理的呢?

首先进程收到信号也在没有阻塞信号下,不会立马处理信号,而是在合适的情况下,由内核态到用户态再处理。
那么什么是内核态和用户态呢?

4.1 用户态和内核态

用户态和内核态是两种运行级别。
用户态是最低的级别,处于用户态的进程,会有一些命令的限制,不能访问内核资源和硬件。
内核态是最高的级别,处于内核态的进程,可以访问内核资源以及硬件。

值得注意的是,内核态是可以访问用户态程序的数据,也是在理论上可以访问用户态程序代码的,但是操作系统不允许这种情况发送,因为会出现安全性问题!

如果进程需要通过系统调用访问内核资源或者硬件,就一定需要从用户态切换到内核态。
(比如用户态的进程访问系统内核:waitpid、getpid,访问硬件:printf、fopen、write、read。再比如C++STL中vector的扩容需要访问内存,但是其扩容机制也一定避免了多次调用系统调用。)
系统调用也因此相比普通调用,耗时更多,因此尽量少使用系统调用。

用户态和内核态怎么表示的?

其实进程运行的时候,进程对应上下文被加载到CPU的寄存器中,CPU内有些寄存器指向进程有关的结构,比如PCB,页表等。

其中有一个CR3寄存器,表示当前CPU的运行级别,0代表内核态,3代表用户态。

这里是引用

4.2 用户态到内核态的切换

前面说了,进程如果需要系统调用访问内核资源或硬件,就需要从用户态切换到内核态。
这之中有个问题:
进程又是如何找到对应系统调用的呢?

进程通过用户级页表将虚拟地址空间数据映射到物理内存,每个进程都有自己的虚拟地址空间和自己的用户级页表,这样就能确保进程间的独立性,使得物理内存出现不一样的数据。

当操作系统加载到物理内存的时候,操作系统也为进程准备了一个内核级页表,内核级页表是为了维护在虚拟和物理之间的操作系统的代码而构成的内核级映射表。开机时会将操作系统加载到内存,因此操作系统在物理空间只存在一份,这就决定了内核级页表在内核中只有一份就够了。同时在CPU内也有一个寄存器一直指向这个内核级页表。内核级页表将物理内存中系统代码,在进程地址空间对应的3-4G空间进行映射,所以进程就可以通过内核级页表访问系统调用。

也就是说,在进程运行的时候,对应上下文会加载到寄存器中,这就使得一些寄存器能找到进程的PCB与页表,并且也能找到内核级页表,那么当需要调用系统接口的时候,与动态链接类似就可以直接从虚拟地址空间进行跳转,再通过内核级页表从物理内存找到对应代码,再返回到用户空间。

并且,每一个进程的虚拟地址3-4G空间都共享同一个内核级页表,所以无论进程怎么切换,进程对应3-4G空间是不会变的。因此,进程是可以随意访问系统调用的。
这里是引用

那么用户凭什么能访问内核呢?
其实进程以用户态调用系统接口一开始也是用户态的,Linux在系统调用接口初始位置有一个从用户态到内核态的转换(通过汇编指令int 80进行陷入内核),因此进程能以内核态的身份访问内核资源或硬件资源。

4.3 信号捕捉过程

接着前面 首先进程收到信号也在没有阻塞信号下,不会立马处理信号,而是在合适的情况下,由内核态到用户态再处理。 这也说明,此前是处于内核态的。那么什么时候会进入内核态呢?系统调用和进程切换。

从整个信号捕捉过程来看:
在这里插入图片描述

5、信号集操作函数以及测试

前面说的信号保存的数据结构都是内核中的,而在用户层可以通过一些手段访问它们。

比如:
sigset_t是一个信号集,它是一个位图结构,可以表示每个信号的有效和无效状态。在阻塞信号集中有效和无效对应阻塞和未阻塞,在未决信号集中有效和无效对应是否处于未决状态。阻塞信号集也称为当前进程的信号屏蔽字,这里的屏蔽是阻塞的意思而不是忽略。

在用户层有以下函数调用可以用来设置这个信号集:
详细可以通过man 3 手册查看

#include <signal.h>
int sigemptyset(sigset_t *set); //初始化信号集,全部清0
int sigfillset(sigset_t *set); // 填充信号集 全部置1
int sigaddset (sigset_t *set, int signo); // 往信号集里添加指定信号
int sigdelset(sigset_t *set, int signo); // 往信号集里删除指定信号
int sigismember(const sigset_t *set, int signo);// 判断信号集里是否有指定信号


除此以外还有以下函数调用可以修改内核相应信号结构:

sigprocmask
sigprocmask针对的是对PCB中block结构(阻塞位图)进行修改。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1

参数部分:
how有三种选择:
SIG_BLOCK 代表在原来的基础上添加set信号集中的信号。
SIG_UNBLOCK 代表在原来的基础上删除set信号集中的信号。
SIG_SETMASK 代表直接将block位图修改成set。

set代表需要传的信号集
oset 代表block位图修改前的信号集

sigpending
sigpending就是将PCB当前的pending位图传给set信号集。
set参数作为一个输出型参数。

#include <signal.h>
int sigpending(sigset_t *set);


知道了上面,就可以做一个小小实验。
大概内容如下:

创建一个阻塞的信号集,可以将2、3号信号添加进去,然后再添加到block位图中,再给进程发送对应信号,打印pending位图,看是否阻塞了该信号。
(并且添加解除阻塞功能,再捕获信号将对应默认处理改成自定义处理)

代码如下:

#include <iostream>
#include <vector>
#include <signal.h>
#include <unistd.h>
using std::cout;
using std::endl;#define MAX_SIGNO 31static std::vector<int> blocks = {2, 3};static void show_pending(const sigset_t& pending)
{for(int signo = MAX_SIGNO; signo > 0; --signo){if(sigismember(&pending, signo)){cout << "1";}else{cout << "0";}}cout << endl;
}static bool pendingIsEmpty(const sigset_t& pending)
{for(int signo = MAX_SIGNO; signo > 0; --signo){if(sigismember(&pending, signo)){return false;}}return true;
}static void myhandler(const int signo)
{cout << signo << " 号信号已经被捕捉" << endl;
}int main()
{//对信号做捕捉 自定义操作for(const int& signo : blocks) signal(signo, myhandler);//1 添加阻塞信号sigset_t block, oblock, pending;//1.1 初始化信号集sigemptyset(&block);sigemptyset(&oblock);sigemptyset(&pending);//1.2 添加阻塞信号到阻塞信号字for(const int& signo : blocks) sigaddset(&block, signo);//1.3 设置阻塞信号 (进入内核 直接将阻塞修改成block信号集)sigprocmask(SIG_SETMASK, &block, &oblock);//2 打印pending信号 显示收到信号但是没有做出处理动作,意味着被阻塞int cnt = 5;while(true){//2.1 接收pending信号集sigpending(&pending);//2.2 打印pending表show_pending(pending);sleep(1); //缓慢打印if(cnt-- == 0){if(pendingIsEmpty(pending)){cout << "未收到任何信号" << endl;break;}else{//2.3 解除信号的阻塞,完成自定义动作后,再次阻塞信号cout << "解除信号的阻塞" << endl;sigprocmask(SIG_SETMASK, &oblock, &block);//解除后信号由内核到用户态执行自定义处理方法sigprocmask(SIG_SETMASK, &block, &oblock);cnt = 5;}}}return 0;
}

实验结果:
在这里插入图片描述
本章完~

相关文章:

【Linux】信号的产生、保存、捕捉处理 (四种信号产生、核心存储、用户态与内核态、信号集及其操作函数)

文章目录1、什么是信号&#xff1f;2、信号的产生2.1 通过键盘产生信号2.2 通过系统调用产生信号2.3 硬件异常产生的信号2.4 由软件条件产生的信号2.5 进程的核心转储3、信号的保存4、信号的捕捉4.1 用户态和内核态4.2 用户态到内核态的切换4.3 信号捕捉过程5、信号集操作函数以…...

redis经典五种数据类型及底层实现

目录一、Redis源代码的核心部分1.redis源码在哪里2.src源码包下面该如何看&#xff1f;二、我们平时说redis是字典数据库KV键值对到底是什么1.6大类型说明(粗分)2.6大类型说明3.上帝视角4.Redis定义了redisObject结构体4.1 C语言struct结构体语法简介4.2 字典、KV是什么4.3 red…...

三十而立却被裁,打工人要如何应对职场危机?

又到金三银四就业季&#xff0c;对于部分职场人来说&#xff0c;年龄成为了他们找工作的最大限制。 因为绝大部分企业招聘中层干部以下岗位的时候&#xff0c;都会要求年龄不超过35周岁&#xff0c;再加上每年千万毕业生涌入社会&#xff0c;竞争程度相当激烈&#xff0c;这就导…...

java面试-java基础

char 变量能不能存贮一个中文汉字&#xff1f;为什么&#xff1f; char 变量可以存贮一个汉字&#xff0c;因为 Java 中使用的默认编码是 Unicode &#xff0c;一个 char 类型占 2 个字节&#xff08;16 bit&#xff09;&#xff0c;一个汉字是2个字节&#xff0c;所以放一个中…...

Kafka 消息不丢失

Kafka 消息不丢失生产者丢失消费者丢失不丢失配置Kafka 保证消息不丢失&#xff1a;只对已提交的消息 (committed message) 做有限度的持久化保证 已提交的消息&#xff1a;当 n 个 Broker 成功接收到该消息并写入到日志文件后&#xff0c;就告诉生产者该消息已成功提交有限度…...

ASEMI高压MOS管10N65参数,10N65规格,10N65封装

编辑-Z ASEMI高压MOS管10N65参数&#xff1a; 型号&#xff1a;10N65 漏极-源极电压&#xff08;VDS&#xff09;&#xff1a;650V 栅源电压&#xff08;VGS&#xff09;&#xff1a;30V 漏极电流&#xff08;ID&#xff09;&#xff1a;10A 功耗&#xff08;PD&#xff…...

LeetCode-416. 分割等和子集

目录题目分析回溯法动态规划动态规划(压缩)题目来源 416. 分割等和子集 题目分析 这道题目是要找是否可以将这个数组分割成两个子集&#xff0c;使得两个子集的元素和相等。 那么只要找到集合里能够出现 sum / 2 的子集总和&#xff0c;就算是可以分割成两个相同元素和子集了…...

2021年 第12届 蓝桥杯 Java B组 省赛真题详解及小结【第2场省赛 2021.05.09】

一、试题A&#xff1a;求余&#xff08;本题总分&#xff1a;5 分&#xff09; 得&#xff1a;5分 本题总分&#xff1a;5 分 【问题描述】 在 C/C/Java/Python 等语言中&#xff0c;使用 % 表示求余&#xff0c;请问 2021%20 的值是多少&#xff1f; 【答案提交】 这是一道结果…...

elasticSearch写入原理

elasticSearch写入原理 最近学习完了es相关的课程整理除了es的核心内容&#xff0c;学习这东西知其然知其所以然&#xff0c;自己按照自己的理解整理了es相关的面试题。先热个身&#xff0c;整理一下es的写入原理&#xff0c;有不对的地方请大家指正。 这些原理的东西我觉得还是…...

第十四届蓝桥杯模拟赛(第三期)Python

1 进制转换 问题描述   请找到一个大于 2022 的最小数&#xff0c;这个数转换成十六进制之后&#xff0c;所有的数位&#xff08;不含前导 0&#xff09;都为字母&#xff08;A 到 F&#xff09;。   请将这个数的十进制形式作为答案提交。 答案&#xff1a;2730 def ch…...

Pytorch模型参数的保存和加载

目录 一、前言 二、参数保存 三、参数的加载 四、保存和加载整个模型 五、总结 一、前言 在模型训练完成后&#xff0c;我们需要保存模型参数值用于后续的测试过程。由于保存整个模型将耗费大量的存储&#xff0c;故推荐的做法是只保存参数&#xff0c;使用时只需在建好模…...

面试热点题:回溯算法之组合 组合与组合总和 III

什么是回溯算法&#xff1f; 回溯算法也可以叫回溯搜索算法&#xff0c;回溯是递归的"副产品",回溯的本质是穷举&#xff0c;然后选出我们需要的数据&#xff0c;回溯本身不是特别高效的算法&#xff0c;但我们可以通过"剪枝"来优化它。 理解回溯算法 回溯…...

java面试-jvm

JVM JVM 是 java 虚拟机&#xff0c;简单来说就是能执行标准 java 字节码的虚拟计算机 JVM 是如何工作的 首先程序在执行之前先要把 Java 代码&#xff08;.java&#xff09;转换成字节码&#xff08;.class&#xff09;&#xff0c;JVM 通过类加载器&#xff08;ClassLoade…...

vscode下载与使用

1.vscode下载 官网下载地址&#xff1a;Download Visual Studio Code - Mac, Linux, Windows下载太慢&#xff0c;推荐文章&#xff1a;解决VsCode下载慢问题_vscode下载太慢_迷小圈的博客-CSDN博客下载太慢&#xff0c;推荐下载链接&#xff1a;https://vscode.cdn.azure.cn/s…...

人员摔倒识别预警算法 opencv

人员摔倒识别预警算法通过opencv网络模型技术&#xff0c;人员摔倒识别预警算法能够智能检测现场画面中人员有没有摔倒&#xff0c;无需人为干预可以立刻抓拍告警。OpenCV的全称是Open Source Computer Vision Library&#xff0c;是一个跨平台的计算机视觉处理开源软件库&…...

华为OD机试题 - 火星文计算(JavaScript)| 机考必刷

更多题库,搜索引擎搜 梦想橡皮擦华为OD 👑👑👑 更多华为OD题库,搜 梦想橡皮擦 华为OD 👑👑👑 更多华为机考题库,搜 梦想橡皮擦华为OD 👑👑👑 华为OD机试题 最近更新的博客使用说明本篇题解:火星文计算题目输入输出示例一输入输出说明Code解题思路版权说明…...

AI人工智能 - 初探

1.应用场景 主要用于了解和系统学习AI&#xff0c;从而可以在工作生活中利用AI做一些事。 2.学习/操作 1.文档阅读 下面的内容来自于与chatGPT的对话 2.整理输出 介绍AI 人工智能&#xff08;Artificial Intelligence&#xff0c;简称AI&#xff09;是计算机科学中的一个分支&…...

Spring-AOP工作流程

Spring-AOP工作流程 3&#xff0c;AOP工作流程 3.1 AOP工作流程 由于AOP是基于Spring容器管理的bean做的增强&#xff0c;所以整个工作过程需要从Spring加载bean说起: 流程1:Spring容器启动 容器启动就需要去加载bean,哪些类需要被加载呢?需要被增强的类&#xff0c;如:B…...

C51---串口发送指令,控制LED灯亮灭

1.Code: #include "reg52.h" #include "intrins.h" sfr AUXR 0x8E; sbit D5 P3^7; void UartInit(void) //9600bps11.0592MHz { //PCON & 0x7F; //波特率不倍速 AUXR 0x01; SCON 0x50; //8位数据,可变波…...

【Wiki】XWiki数据备份

XWiki为主题使用java开发的开源wiki&#xff0c;官网地址如下&#xff1a; https://www.xwiki.org/xwiki/bin/view/Main/ 目录1、 XWiki升级数据备份1.1、 获取XWiki配置的数据库与持久化目录信息1.2 备份数据库信息1.3 备份持久化目录2、XWiki数据迁移如果一个知识库不能确保数…...

ctk框架开发Qt插件应用示例工程

目录 前言 约定 插件工程pluginApp: 主启动工程StartApp: 效果演示 结语...

spring5源码篇(4)——beanFactoryPostProcessor执行/注解bean的装配

spring-framework 版本&#xff1a;v5.3.19 前面研究了beanDefinition的注册&#xff0c;但也仅仅是注册这一动作。那么在spring容器启动的过程中&#xff0c;是何时/如何装配的&#xff1f;以及装配的bean是如何注入的&#xff1f; &#xff08;考虑到xml方式基本不用了以及篇…...

masstransit的message几个高级用法

1&#xff09;问题&#xff0c;Class MessageA 基类&#xff0c;Class MessageB继承自MessageA&#xff1b; 用bus.Publish方法本想把有些消息只发给B队列&#xff0c;结果由于其继承关系A队列也获得了消息&#xff1b; 解决方法用send&#xff0c; Uri uri new Uri(RabbitM…...

漏洞分析丨cve-2012-0003

作者:黑蛋一、漏洞简介这次漏洞属于堆溢出漏洞&#xff0c;他是MIDI文件中存在的堆溢出漏洞。在IE6&#xff0c;IE7&#xff0c;IE8中都存在这个漏洞。而这个漏洞是Winmm.dll中产生的。二、漏洞环境虚拟机调试工具目标软件辅助工具XP-SP3、KaliOD、IDAIE6Windbg组件gflags.exe三…...

rm命令——删除文件或目录

rm命令是英文单词remove的缩写&#xff0c;主要功能是删除文件或目录。 因为删除文件是一个破坏性动作&#xff0c;因此&#xff0c;在使用时需要格外小心&#xff0c;在执行之前一定要再三确认删除的是哪个目录中的什么文件。 rm命令的语法格式如下&#xff1a; rm [选项] …...

【零基础入门学习Python---Python的基本语法使用】

一.Python基本语法使用 Python是一种易学且功能强大的编程语言,具有简洁的语法和广泛的应用领域。在本文中,我们将介绍Python的基本语法使用,以帮助初学者快速入门Python编程。 1.1 注释 Python 支持两种类型的注释:单行注释和多行注释。 单行注释:以 # 符号开头,从 # …...

数据仓库相关概念的解释

数据仓库相关概念的解释 文章目录数据仓库相关概念的解释1 ETL是什么&#xff1f;ETL体系结构2 数据流向何为数仓DW3 ODS 是什么&#xff1f;4 数据仓库层DWDWD 明细层DWD 轻度汇总层&#xff08;MID或DWB&#xff0c;data warehouse basis&#xff09;DWS 主题层&#xff08;D…...

1/4车、1/2车、整车悬架模糊PID控制仿真合集

目录 前言 1. 1/4悬架系统 1.1数学模型 1.2仿真分析 2. 1/2悬架系统 2.1数学模型 2.2仿真模型 2.3仿真分析 3. 整车悬架系统 3.1数学模型 3.2仿真分析 4.总结 前言 前面几篇文章介绍了LQR、SkyHook、H2/H∞、PID控制&#xff0c;接下来会继续介绍滑模、反步法、M…...

Linux性能补丁升级,避免不必要的跨核Wake-Up

导读一个由英特尔发起的、旨在改进Linux内核公平调度程序代码的补丁系列&#xff0c;也看到了来自AMD工程师和其他利益相关者的测试/反馈&#xff0c;并继续进行改进。这个补丁系列的重点是避免在不必要的情况下发生过多的跨核唤醒(Cross-CPU Wake-up)。这样一来&#xff0c;这…...

Spring Cloud Alibaba全家桶(六)——微服务组件Sentinel介绍与使用

前言 本文小新为大家带来 微服务组件Sentinel介绍与使用 相关知识&#xff0c;具体内容包括分布式系统存在的问题&#xff0c;分布式系统问题的解决方案&#xff0c;Sentinel介绍&#xff0c;Sentinel快速开始&#xff08;包括&#xff1a;API实现Sentinel资源保护&#xff0c;…...

拼多多2021笔试真题集 -- 3. 多多的求和计算

多多的求和计算 多多路上从左到右有N棵树&#xff08;编号1&#xff5e;N&#xff09;&#xff0c;其中第i个颗树有和谐值Ai。 多多鸡认为&#xff0c;如果一段连续的树&#xff0c;它们的和谐值之和可以被M整除&#xff0c;那么这个区间整体看起来就是和谐的。 现在多多鸡想请…...

DP算法:动态规划算法

步骤&#xff08;1&#xff09;确定初始状态&#xff08;2&#xff09;确定转移矩阵&#xff0c;得到每个阶段的状态&#xff0c;由上一阶段推到出来&#xff08;3&#xff09;确定边界条件。例题蓝桥杯——印章&#xff08;python实现&#xff09;使用dp记录状态&#xff0c;d…...

一三四——一六七

一三四、JavaScript——_DOM简介 MDNq前端参考文档&#xff1a;DOM 概述 - Web API 接口参考 | MDN (mozilla.org) 一三五、JavaScript——HelloWorld <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta h…...

day29_JS

今日内容 上课同步视频:CuteN饕餮的个人空间_哔哩哔哩_bilibili 同步笔记沐沐霸的博客_CSDN博客-Java2301 零、 复习昨日 一、事件 二、DOM操作 三、案例 零、 复习昨日 js 脚本语言,弱类型 引入方案: 3种 js的内容: 语法dombom 语法 变量 var 数据类型 引用类型 - 对象,J…...

【HTTP协议与Web服务器】

HTTP协议与Web服务器浏览器与服务器通信过程HTTP的请求报头HTTP请求报头结构HTTP的请求方法HTTP应答报头HTTP应答报头结构应答状态web服务器的c语言实现浏览器与服务器通信过程 浏览器与Web服务器再应用层通信使用的是HTTP协议&#xff0c;而HTTP协议在传输层使用的是TCP协议。…...

Idea+maven+spring-cloud项目搭建系列--12 整合grpc

前言&#xff1a; grpc 是geogle 开源的rpc 通信框架&#xff0c;通过定义proto生成通信存根&#xff0c;像本地调用服务一样&#xff0c;进行远程服务的调用&#xff1b; 1 消费端服务提供&#xff1a; 1.1 引入grpc 和 protobuf <!-- RPC --> <!-- RPC 服务调用 …...

Revit开洞问题:结构专业开洞口剖面显示及一键开洞

一、Revit中关于结构专业开洞口剖面显示问题 Revit作业的时候&#xff0c;我们不仅只为了一个最后的三维立体模型,我们需要的是一个符合国家以及本院制图标准的一个出图样式,这时候就会出现各种各样的显示问题&#xff0c;本期就一个结构专业开洞显示问题&#xff0c;跟大家一起…...

0107连通分量-无向图-数据结构和算法(Java)

文章目录1 API2 代码实现和分析测试后记1 API 深度优先搜索下一个直接应用就是找出一幅图中的连通分量,定义如下API。 public class CCCC(Graph g)预处理构造函数booleanconnected(int v, int w)v和w连通吗intcount()连通分量数intid(int v)v所在的连通分量标识符(0~count()-…...

[学习笔记]黑马程序员python教程

文章目录思维导图Python基础知识图谱面向对象SQL入门和实战Python高阶技巧第一阶段第九章&#xff1a;Python异常、模块与包1.9.1异常的捕获1.9.1.1 为什么要捕获异常1.9.1.2 捕获常规的异常1.9.1.3 捕获指定的异常1.9.1.4 捕获多个异常1.9.1.5 捕获全部异常1.9.1.6 异常的else…...

如何配置用于构建 FastReport Online Designer 的 API ?

FastReport Online Designer 是一个跨平台的报表设计器&#xff0c;允许通过任何平台的移动设备创建和编辑报表。今天我们就一起来看看在2023版中新增和改进的功能有哪些&#xff0c;点击下方可以获取最新版免费试用哦&#xff01; FastReport Onlin Designe最新版试用https:/…...

【嵌入式Linux内核驱动】02_字符设备驱动

字符设备驱动 〇、基本知识 设备驱动分类 &#xff08;按共性分类方便管理&#xff09; 1.字符设备驱动 字符设备指那些必须按字节流传输&#xff0c;以串行顺序依次进行访问的设备。它们是我们日常最常见的驱动了&#xff0c;像鼠标、键盘、打印机、触摸屏&#xff0c;还有…...

【零散整理】

1-1 git查看代码的项目总行数 git log --prettytformat: --numstat | awk ‘{ add $1; subs $2; loc $1 - $2 } END { printf “added lines: %s, removed lines: %s, total lines: %s\n”, add, subs, loc }’ - 1-2 cookie const cookies document.cookie.split(; )for…...

RocketMQ重复消费的症状以及解决方案

RocketMQ重复消费的症状以及解决方案 生产消息时重复 症状 当一条消息已被成功发送到 消费者 并完成持久化&#xff0c;此时出现了网络闪断或者客户端宕机&#xff0c;导致服务端对客户端应答失败。 如果此时 生产者 意识到消息发送失败并尝试再次发送消息&#xff0c;消费者…...

数字化时代,企业的商业模式建设

随着新一代信息化、数字化技术的应用&#xff0c;众多领域通过科技革命和产业革命实现了深度化的数字改造&#xff0c;进入到以数据为核心驱动力的&#xff0c;全新的数据处理时代&#xff0c;并通过业务系统、商业智能BI等数字化技术和应用实现了数据价值&#xff0c;从数字经…...

项目实战典型案例23——-注册上nacos上的部分服务总是出现频繁掉线的情况

注册上nacos上的部分服务总是出现频繁掉线的情况一&#xff1a;背景介绍二&#xff1a;思路&方案解决问题过程涉及到的知识nacos服务注册和服务发现一&#xff1a;背景介绍 spring cloud项目通过nacos作为服务中心和配置中心&#xff0c;出现的问题是其中几个服务总是出现…...

玩转金山文档 3分钟让你的文档智能化

在上个月底&#xff0c;我们给大家推荐了金山轻维表的几个使用场景&#xff0c;社群中不少用户反响很好&#xff0c;对其中一些场景的解决方案十分感兴趣。但也有一些人表示&#xff0c;有些场景不知道如何实现&#xff0c;希望我们能提供模版/教程。这次我们将做一期热门模板盘…...

安装了nodejs怎么安装nvm

第一步&#xff0c;从控制面板卸载已经安装的node 第二步&#xff0c;删除C盘program开头文件夹下的node文件 第三步&#xff0c;去C/user/用户名 文件夹下&#xff0c;删除.npmrc文件 第四步&#xff0c;打开隐藏文件&#xff0c;第三步文件夹下有一个Appdata文件&#xff…...

java安全编码规范考试

java安全编码规范考试 整理不易&#xff0c;收点币&#xff01;&#xff01; 安全编码规范考试.md 下面对zip文件的安全解压缩描述&#xff0c;错误的是 A.zip文件解压时&#xff0c;可以使用entry.getSize(&#xff09;对解压缩文件进行文件大小判断 B.zip文件解压时&…...

表格检测识别技术的发展历程

近年来&#xff0c;随着计算机技术的飞速发展&#xff0c;越来越多的研究者开始关注表格检测识别技术。表格检测识别技术是一种利用计算机自动处理表格的技术&#xff0c;它可以实现从文本中检测出表格&#xff0c;并进行识别和提取。这种技术有助于提高文本处理的效率&#xf…...

设计UI - Adobe xd对象介绍

矩形工具 新建矩形 操作步骤&#xff1a;选择矩形工具&#xff0c;快捷键R&#xff0c;鼠标在画板上拖出矩形即可。 拖动定界框周围圆形手柄&#xff0c;可快速调整矩形大小&#xff0c;也可以输入宽和高的参数对矩形大小进行改变。 移动矩形 操作步骤&#xff1a;选择选择工具…...