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

系统级基础信号知识【Linux】

目录

一,什么是信号 

进程面对信号常见的三种反应概述

二,产生信号

1.终端按键产生信号

signal

2. 进程异常产生信号

核心转储

3. 系统调用函数发送信号

kill

raise

abort

小结:

4. 由软件条件产生

alarm

5. 硬件异常产生信号

三,信号其他概念

1. 进程中储存信号的内核结构

2. sigset_t类型——信号集类型

3. sigpending接口

4. sigprocmask接口

5. 重新理解进程在计算机中的运行

四,捕捉信号

1. 捕捉信号流程

​编辑

2. sigaction

关键字——volatile

SIGCHLD信号


嘿!收到一张超美的风景图,希望你每天都能开心顺心! 

一,什么是信号 

操作系统中的信号是一种在进程间传递信息和通知的机制。它可以用来通知进程发生了某种事件,比如用户按下了某个键盘按键、进程收到了某个信号或者发生了某个错误等。

生活中的例子:

  1. 手机收到新短信或来电时会发出提示音,这就是一种信号,通知用户有新的事件发生。
  2. 交通信号灯会发出红、黄、绿三种不同的信号,指示车辆和行人何时可以通行。
  3. 火灾报警器发出警报声,通知人们有火灾发生,需要立即疏散。
  4. 门铃响起,通知主人有人来访。
  5. 警报器在发现入侵者时会发出警报声,通知屋主有危险。

进程面对信号常见的三种反应概述

可选的处理动作有以下三种 :
1. 忽略此信号。
2. 执行该信号的默认处理动作。
3. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉 (Catch)一个信号。

这里我们以:kill  信号为例

我们平时手动结束一个进程: kill -9  PID, 本质上是让操作系统向目标进程发送 9信号,从而结束该进程。下面是kill 相关的信号表:

以上普通信号,我们认识七八个即可

指令:man  7  signal

通过man 7 signal, 我们可以查看信号的详细信息

试问:如何理解键盘中,组合键如何实现其功能? 

答: 首先我们得知道,键盘的工作方式:通过中断的方式产生信息。同时,操作系统中也一定有识别该组合键产生信息的记录表。

一个进程,在接受信号后,必然会对信号进程储存。进程则是通过PCB(task_struct)中位图unsigned  int来记录,信号是否存在。

而PCB又是内核数据结构,能修改PCB的也就只有OS自身。

即:信号发送的本质是,OS对目标进程的PCB中信号位图的修改

回到组合键的问题: 

二,产生信号

1.终端按键产生信号

signal

signum:  捕获该进程信号

handler :  信号处理方法(函数指针)

比如这样:

void signalmain(int signal)
{cout << "信号处理中...  : " << signal << "  gitpid :" << getpid() << endl;
}int main()
{signal(SIGINT, signalmain);while ( 1){cout << "接受信号中...... " << endl; sleep(1);}return 0;
}

运行过程中,我们不断通过,ctrl + c的组合键进行操作。运行结果如下; 

从上面我们可以得出2个点:1.键盘的组合键确实是系统向当前进程发送信号。 2. 可以通过signal注册信号处理函数。

(signal使用须知:signal接口,并不是调用就会触发信号处理方法,它只是提前注册了信号处理函数;只有捕捉到特定信号时才会调用特定方法;  signal接口一般出现在main函数开始。)

2. 进程异常产生信号

核心转储

这个在进程控制,waitpid函数,status参数中提到过:

大概就是这样:在进程发生异常退出时,能进行核心转储,形成一个二进制的特殊文件

解释一下,为什么云服务器核心转储默认是关闭?:因为服务器的管理,是有另外一层服务负责,他们的任务是将异常挂掉的服务进程自动重启如果因为进程老是异常挂掉,这会导致磁盘中存在大量的转储文件,会导致资源浪费。 

我们在终端,再次打开 man  7   signal, 文档中core的意思就是核心转储

3. 系统调用函数发送信号

kill

我们在终端输入kill -9  PID等等信号,在底层是调用了kill系统函数。kill也很简单。

kill : 进程 PID

sig:  信号编号

raise

功能很简单,就是让OS向自身进程发送信号

abort

功能: 终止自身进程(相当于向自身进程发送:kill -6)

小结:

上面这些接口,本质上都是利用系统接口调用,执行OS对应的系统调用代码,OS在PCB中设置或者是修改特定的值,进程对信号进行处理。

4. 由软件条件产生

例子: 

我们以曾经的管道为例,如果读端不仅没读,而且将读端关闭;写端一直在写。作为单向通信,写已经没有意义了,OS会终止写进程,发送SIGPIPE(13),这构成不了软件级的条件。 

alarm

 

 功能:就是过 seconds 后,OS将自动向该进程发送SIGALRM(14)信号。

注意:当进程开始,当alarm被触发后向进程发送信号,但我们要注意,alarm只是发送SIGALRM信号,并不会阻断进程正常进行

5. 硬件异常产生信号

硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。
例如:当前进程执行了除以0的指令, CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。
void func(int st)
{cout << "接受到信号: " << st << endl;
}int mian()
{signal(SIGFPF, func);int count = 150;z = count / 0;while(1) {sleep(1);}
}

 现象:会一直打印 “接受到信号: 8”

1. 首先我们得思考,这个信号是谁发出的呢?
CPU运算单位异常详细解释:CPU内部有寄存器,其中一个状态寄存器(位图存储信息),有对应的状态标记位,溢出标记位。OS会自动进行计算检测(先检测再计算),如果状态标记位是1(可以理解为是否异常),OS会立即调取进程PID,向目标进程发送信号,然后就是进程会选择合适的时机处理信号。因此, 一些运算,并不都是软件层产生的信号,一些则是硬件层产生的信号。

2,硬件出现异常,进程就一定会退出吗?  

     不一定,如果我们没有捕获信号,那么进程默认退出;而捕获后,我们的就可以控制进程是否退出。

3. 为什么上面代码,会进入死循环??

     在捕获信号后,信号处理中并未退出进程,该进程还在CPU运行队列中,将会被再次调度,当再次调度时OS还会继续检测,会继续发送信号,然后被捕获处理,接着继续在CPU运行队列中。

再比如:当前进程访问了非法内存地址,MMU(硬件)会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。

注: 野指针的异常,常常会有段错误: Segmentation fault

void func(int st)
{cout << "接受到信号: " << st << endl;
}int mian()
{signal(SIGSEGV, func);int *count = nullptr;*count  = 100;while(1) {sleep(1);}
}

现象还是:死循环打印 “接受到信号: 11"

1. 如何理解地址访问??

     首先我们访问一个数据目标,我们一定得访问其物理地址。那么中间会有一段虚拟地址转换为物理地址的过程,由页表(并不是软件结构,而是一种硬件) + MMU(Memory Manager  Unit, 是一种硬件) , 当错误的地址被MMU(硬件寄存器)读取后,一定会报错,OS将MMU的报错转换为信号发送给进程,让其退出。

2. 死循环原因??

     因为状态寄存器储存进程的状态,在信号发送完一次后,再次被调度时,检测到进程状态寄存器中的异常,则又会发送信号,然后被切换保存进程上下文,就这样一直继续下去。

三,信号其他概念

实际执行信号的处理动作,称为 信号递达(Delivery)
信号从产生到递达之间的状态,称为 信号未决(Pending)
进程可以选择 阻塞 (Block )某个信号( 一般情况下,进程不会阻塞任何一种信号)。
被阻塞的信号产生时将 保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
注意:阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

1. 进程中储存信号的内核结构

我们可以回想,之前是使用的signal系统接口:

结合上图,可以这么理解:sig就是pending中,对应信号的位置;第二参数位,我们暂时叫func, func就是对handler[sig]中设置处理函数的地址。

上面是对信号的自定义处理

而执行默认处理

signal(SIGSEGV, SIG_DFL); // 对应的下标是0

忽略处理

signal(SIGSEGV, SIG_IGN); // 下标为1

这有一点需要注意的是:当OS检测到进程的一个信号,下标值为signalsum,并不是直接handler[signalsum]直接访问,而是先比较是否是SIG_DEL(0)或者SIG_IGN(1),再比较自定义处理

2. sigset_t类型——信号集类型

从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储, sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有 效”和“无效”的含义是该信号是否处于未决状态。下一节将详细介绍信号集的各种操作。 阻塞信号集也叫做当前进程的 信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。( 总结:block, pending都可以用sigset_t类型表示
sigset_t类型的理解:
sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从 使用者的角度是不必关心的,使用者只能 调用函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释,比如用 printf直接打印sigset_t变量是没有意义的

信号集处理,相关函数:

#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); //   判断一个信号集的有效信号中是否包含某种 信号, 若包含则返回 1, 不包含则返回 0, 出错返回 -1

3. sigpending接口

功能: 读取当前进程的未决信号集 , 通过 set参数传出 。调用成功则返回 0, 出错则返回 -1 。 

4. sigprocmask接口

功能:调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)

set: 是我们自定义的一个信号集。

how:  传入的set,对该进程的阻塞信号集进行怎样的操作,比如:添加屏蔽字,删除屏蔽字,覆盖屏蔽字。

oset:  传入一个新set,  储存修改前旧的阻塞信号集

how可选值:

实践:

void cmpshow(const sigset_t& set)
{for (int i = 1; i <= 31; i++){if (sigismember(&set, i)){cout<< "1";}else cout << "0";}cout << endl;
}int main()
{sigset_t set, oset;sigemptyset(&set);sigemptyset(&oset);sigaddset(&set, 2); // 目标阻塞信号 2sigpending(&oset);int n = sigprocmask(SIG_BLOCK, &set, &oset);assert(n == 0);  // n 必然==0(void)n;  // 目的是调用一次n,避免在release版本中,n未被调用的警告while (1){sigset_t tmp;sigemptyset(&tmp);sigpending(&tmp);cmpshow(tmp);sleep(1);} return 0;
}

 问:为什么没有设置pending信号集的接口?? 答:没必要,像kill, raise,  abort指令接口都可以修改pending。

小结:我们的进程中的信号,都有各自接口负责管理,处理函数——signal; 信号未决表——sigpending;  阻塞信号集——sigprocmask。

代码知识加餐:

  int n = sigprocmask(SIG_BLOCK, &set, &oset);assert(n == 0);    // n 必然==0(void)n;           // 目的是调用一次n,避免在release版本中,n未被调用的警告

问题1,既然进程可以自己捕捉信号,那我们让进程能捕获任何信号,并且全部阻塞信号,那这样就可以制作一个无法被动退出的进程了吗??

回答:OS的设计者已经考虑到这种情况了,所以解决方法是:kill -9  PID 这个信号是管理者信号无法被阻塞,也无法修改其处理方法(指结束进程)

5. 重新理解进程在计算机中的运行

四,捕捉信号

1. 捕捉信号流程

如果信号的处理动作是用户自定义函数 , 在信号递达时就调用这个函数 , 这称为捕捉信号。由于信号处理函数的代码是在用户空间的, 处理过程比较复杂 , 举例如下 : 用户程序注册了 SIGQUIT 信号的处理函数 sighandler 。 当前正在执行main函数 , 这时发生中断或异常切换到内核态。 在中断处理完毕后要返回用户态的 main 函数之前检查到有信号SIGQUIT递达。 内核决定返回用户态后不是恢复 main函数的上下文继续执行,而是执行sighandler函数,sighandler和main函数使用不同的堆栈空间 , 它们之间不存在调用和被调用的关系, 两个独立的控制流程 sighandler 函数返回后自动执行特殊的系统调用sigreturn 再次进入内核态。 如果没有新的信号要递达 , 这次再返回用户态就是恢复main函数的上下文继续执行了

疑问:为什么在内核层山时,不直接调用信号处理函数呢?

答:如果信号处理函数中存在非法操作,那么贸然让计算机内核进行访问数据,计算机数据安全无法得到保证。

2. sigaction

功能:  检查或者修改信号处理方法。平时用的最多的是signal 用法简单易上手。

sig :  目标信号

struct  sigaction * :一种放多种信息的结构体,其中就包括自定义函数处理方法sa_handler

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>void sigint_handler(int signo) {printf("Caught SIGINT, exiting...\n");exit(1);
}int main() {struct sigaction sa;// 对结构体内数据初始化sa.sa_handler = sigint_handler;sigemptyset(&sa.sa_mask);sa.sa_flags = 0;    if (sigaction(SIGINT, &sa, NULL) == -1) {perror("sigaction");exit(1);}printf("Press Ctrl+C to send SIGINT...\n");while (1) {// Do some work}return 0;
}
当某个信号的处理函数被调用时 , 内核自动将当前信号加入进程的信号屏蔽字 , 当信号处理函数返回时自动恢复原来的信号屏蔽字, 这样就保证了在处理某个信号时 , 如果这种信号再次产生 , 那么 它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时, 除了当前信号被自动屏蔽之外 , 还希望自动屏蔽另外一些信号 , 则用 sa_mask 字段说明这些需要额外屏蔽的信号, 当信号处理函数返回时自动恢复原来的信号屏蔽字。

关键字——volatile

该关键字在C当中我们已经有所涉猎,今天我们站在信号的角度重新理解一下。
示例代码:
int tmp = 0;void func(int sig)
{cout << "tmp: " << tmp << "-->";tmp = 1;cout << tmp << endl;
}int main()
{signal(2, func);while (!tmp);return 0;
}// 编译
signal : signal.ccg++  -std=c++11 -o $@ $^ -O3 -g# -O3 ————编译器对代码进行三级优化./PHONY: clean
clean:	rm -rf signal

现象:进程开始运行后,进行ctrl +  c,进程结束,一切正常。但未来我们的代码会跑在各种各样的编译器上,其中一些优化就会影响这个过程。这里就直接说了,在编译时,添加 -o3  进行三级优化,由于tmp没有进行写入操作,寄存器直接用0代替了tmp,这就会导致我们使用 ctrl  +  c,无法终止循环。而  volatile(易变的)  就是提醒计算机,请不要优化该数据

因此,用  volatile 修饰即可tmp即可。 

SIGCHLD信号

进程一章讲过用wait waitpid函数清理僵尸进程,父进程可以阻塞等待子进程结束 也可以非阻塞地查询是否有子进程结束等待清理( 也就是轮询的方式——查看子进程是否发来信号 )
采用第一种方式, 父进程阻塞了就不能处理自己的工作了;
采用第二种方式, 父进程在处理自己的工作的同时还要记得时不时地轮询一 下, 程序实现复杂。
其实, 子进程在终止时会给父进程发 SIGCHLD 信号, 该信号的默认处理动作是忽略, 父进程可以自定义SIGCHLD 信号的处理函数 这样父进程只需专心处理自己的工作, 不必关心子进程了, 子进程终止时会通知父进程, 父进程在信号处理函数中调用wait 清理子进程即可。

下期:多线程

结语

   本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力

相关文章:

系统级基础信号知识【Linux】

目录 一&#xff0c;什么是信号 进程面对信号常见的三种反应概述 二&#xff0c;产生信号 1.终端按键产生信号 signal 2. 进程异常产生信号 核心转储 3. 系统调用函数发送信号 kill raise abort 小结&#xff1a; 4. 由软件条件产生 alarm 5. 硬件异常产生信号…...

Excel单元格隐藏如何取消?

Excel工作表中的有些单元格隐藏了数据&#xff0c;如何取消隐藏行列呢&#xff1f;今天分享几个方法给大家 方法一&#xff1a; 选中隐藏的区域&#xff0c;点击右键&#xff0c;选择【取消隐藏】就可以了 方法二&#xff1a; 如果工作表中有多个地方有隐藏的话&#xff0c;…...

Visual Studio(VS)常用快捷键(最详细)

Visual Studio常用快捷键 一、生成&#xff1a;常用快捷键二、调式&#xff1a;常用快捷键三、编辑&#xff1a;常用快捷键四、文件&#xff1a;常用快捷键五、项目&#xff1a;常用快捷键六、重构&#xff1a;常用快捷键七、工具&#xff1a;常用快捷键八、视图&#xff1a;常…...

UDP特性之组播(多播)

UDP特性之组播 1. 组播的特点2. 设置主播属性2.1 发送端2.2 接收端 3. 组播通信流程3.1 发送端3.2 接收端 4. 通信代码 原文链接 在公司测试广播和多播有一点问题。。。 1. 组播的特点 组播也可以称之为多播这也是UDP的特性之一。组播是主机间一对多的通讯模式&#xff0c;是…...

ElasticSearch之cat shards API

命令样例如下&#xff1a; curl -X GET "https://localhost:9200/_cat/shards?vtrue&pretty" --cacert $ES_HOME/config/certs/http_ca.crt -u "elastic:ohCxPHQBEs5*lo7F9"执行结果输出如下&#xff1a; index shard prirep state docs s…...

Thread-Per-Message设计模式

Thread-Per-Message是为每一个消息的处理开辟一个线程&#xff0c;以并发方式处理&#xff0c;提高系统整体的吞吐量。这种模式再日常开发中非常常见&#xff0c;为了避免线程的频繁创建和销毁&#xff0c;可以使用线程池来代替。 示例代码如下&#xff1a; public class Requ…...

运筹学经典问题(一):指派问题

问题描述 有 N N N个任务&#xff0c;需要 N N N个人去完成&#xff0c;每个人完成不同工作的效率不同&#xff08;或者资源、收益等等&#xff09;&#xff0c;需要怎么分配使得整体的效率最高&#xff08;成本最低等等&#xff09;呢&#xff1f;这就是经典的指派问题啦&…...

产品经理之如何编写竞品分析(医疗HIS系统管理详细案例模板)

目录 一.项目周期 二.竞品分析的目的 三.竞品分析包含的维度 四.如何选择竞品 五.竞品画布 六.案例模板 一.项目周期 在整个项目的周期&#xff0c;产品经理所做的事情主要在项目前期做市场分析、需求调研等&#xff0c;下面一张图概况了整个项目周期产品经理、开发工程师…...

虚拟内存管理

虚拟内存管理 页面置换算法 功能和目标&#xff1a; 功能&#xff1a;当缺页中断发生&#xff0c;需要调入新的页面而内存已经满时&#xff0c;选择内存当中哪个物理页面被置换。目标&#xff1a;尽可能的减少页面的换进换出次数&#xff08;即缺页中断的次数&#xff09;。具…...

ssh时怎么同时指定其端口号,以及scp文件到远程的指定端口

如果想要通过 SSH 连接到指定端口的远程服务器&#xff0c;可以在 SSH 命令中使用 -p 或 --port 参数来指定端口号。以下是相应的用法&#xff1a; $ ssh -p <port> userhost其中&#xff0c; 是要连接的端口号&#xff0c;user 是远程服务器上的用户名&#xff0c;host…...

Redis过期淘汰策略

一. Redis过期淘汰策略 当Redis已用内存超过maxmemory限定时&#xff0c;触发主动清理策略。 主动清理策略在Redis 4.0之前一共实现了 6 种内存淘汰策略&#xff0c;在 4.0 之后&#xff0c;又增加了 2 种 策略&#xff0c;总共8种&#xff1a; 针对设置了过期时间的key做处理…...

微信小程序---自定义组件

目录 1.局部引用组件 2.全局引用组件 3.组件和页面的区别 4.自定义组件样式 5.properties属性 6.data和properties的区别 7.数据监听器 8.纯数据字段 9.自定义组件-组件的生命周期 lifetimes节点 10.组件所在的页面的生命周期 pageLifetimes节点 11.插槽 &#x…...

CGAL的最优传输曲线重构

1、介绍 此程序包实现了一种重建和简化二维点集的方法。输入是一组具有质量属性的二维点&#xff0c;可能受到噪声和离群值的干扰。输出是一组线段和孤立点&#xff0c;它们近似于输入点&#xff0c;如下图所示。质量属性与每个点的近似重要性有关。 左&#xff1a;输入点集受到…...

使用Docker本地安装部署Draw.io绘图工具并实现远程访问协作办公

前言 提到流程图&#xff0c;大家第一时间可能会想到Visio&#xff0c;不可否认&#xff0c;VIsio确实是功能强大&#xff0c;但是软件为收费&#xff0c;并且因为其功能强大&#xff0c;导致安装需要很多的系统内存&#xff0c;并且是不可跨平台使用。所以&#xff0c;今天给…...

流程图、泳道图的介绍和示例分享,以及自定义元件库的介绍

目录 一. 流程图介绍 二. Processon使用 新建一个流程图 图形的使用 三. 流程图示例 登录界面 门诊业务流程图 住院业务流程图 药房业务流程图 会议OA流程图 四. 泳道图介绍 五. 自定义元件库 5.1 新建一个元件库 5.2 创建元件 5.3 使用自定义元件库 一. 流程图介…...

RabbitMq的详细使用

消息队列RabbitMQ详细使用 文章目录 消息队列RabbitMQ详细使用MQ 的相关概念什么是MQ为什么要用MQMQ 的分类MQ 的选择 RabbitMQRabbitMQ 的概念四大核心概念各个名词介绍安装RabbitMQWeb管理界面及授权操作Docker 安装Hello world简单示例 Work Queues轮训分发消息消息应答自动…...

软件设计师——软件工程(二)

&#x1f4d1;前言 本文主要是【软件工程】——软件设计师——软件工程的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304…...

阿里云RDS MySQL 数据如何快速同步到 ClickHouse

云数据库 RDS MySQL 和 云数据库 ClickHouse 是阿里云推出的两个备受欢迎的数据库解决方案&#xff0c;它们为用户提供了可靠的数据存储方案、分析数仓方案&#xff0c;本文介绍如何快速将 RDS MySQL 的数据同步到云数据库 ClickHouse。 如何快速将RDSMySQL的数据同步到云数据库…...

HINet技术要点

《HINet: Half Instance Normalization Network for Image Restoration》发表于CVPR2021&#xff0c;是旷视科技&复旦大学&北大在图像复原方面的的最新进展&#xff0c;所提方案取得了NTIRE2021图像去模糊Track2赛道冠军。 下面谈谈该文章的主要技术点。 1. HIN&#…...

IntelliJ IDEA2023学习教程

详细介绍idea开发工具及使用技巧 1. 2023版安装1.1删除老版本1.2 下载及安装 3.快捷技巧4. 创建各种model 1. 2023版安装 1.1删除老版本 如果以前装有idea需要先删除&#xff0c;以避免冲突&#xff0c;在idea安装目录/bin/Uninstall.exe双击1.2 下载及安装 最新版本 https:/…...

MATLAB基础应用精讲-【数模应用】神经网络(补充篇)

目录 前言 几个相关概念 反向传播 梯度下降 损失函数 优化函数...

洛谷题单【算法1-7】搜索

P1135 奇怪的电梯 一开始以为深搜肯定没问题&#xff0c;从a点出发&#xff0c;衍生出一个二叉树&#xff0c;遍历所有情况就好了&#xff0c;但是会重复&#xff0c;所以加了一个vis防止重复&#xff0c;但是只拿了64pts&#xff0c;因为有可能某个点并不是最短被到达的&…...

WordPress主题Lolimeow v8.0.1二次元风格支持erphpdown付费下载

WordPress国人原创动漫主题lolimeow免费下载 lolimeow是一款WordPress国人原创主题&#xff0c;风格属于二次元、动漫、可爱萝莉风&#xff0c;带有后台设置&#xff0c;支持会员中心。该主题为免费主题。 1.侧栏/无侧栏切换&#xff01; 2.会员中心&#xff08;配套Erphpdown…...

WTN6xxx系列OTP语音芯片:智能语音解决方案的可靠之选

在智能语音交互领域&#xff0c;唯创知音的WTN6xxx系列OTP语音芯片以其独特的特性成为声音播放提示IC的可靠之选。本文将深入探讨WTN6xxx系列OTP语音芯片的应用优势&#xff0c;展示其在各个方面的卓越性能。 一、低成本、高性能 1.经济实惠&#xff1a; WTN6xxx系列OTP语音芯…...

腾讯云Elasticsearch Service产品体验

基本介绍 产品概述 腾讯云 Elasticsearch Service&#xff08;ES&#xff09;是云端全托管海量数据检索分析服务&#xff0c;拥有高性能自研内核&#xff0c;集成X-Pack。ES 支持通过自治索引、存算分离、集群巡检等特性轻松管理集群&#xff0c;也支持免运维、自动弹性、按需…...

SQLE 3.0 部署实践

来自 1024 活动的投稿系列 第一篇《SQLE 3.0 部署实践》 . 作者&#xff1a;张昇&#xff0c;河北东软软件有限公司高级软件工程师&#xff0c;腾讯云社区作者。 爱可生开源社区出品&#xff0c;原创内容未经授权不得随意使用&#xff0c;转载请联系小编并注明来源。 本文共 32…...

爬虫的分类

爬虫的分类 网络爬虫按照系统结构和实现技术&#xff0c;大致可分为4类&#xff0c;即通用网络爬虫、聚焦网络爬虫、增量网络爬虫和深层次网络爬虫。 1.通用网络爬虫&#xff1a;搜索引擎的爬虫 比如用户在百度搜索引擎上检索对应关键词时&#xff0c;百度将对关键词进行分析…...

简说vue-router原理

vue-router原理 hash模式 实现原理 改变描点监听描点变化 history模式 实现原理 改变url监听url变化 abstracthash 和 history 模式有什么区别&#xff1f; url 不一样原理不同 其他总结扩展 history 出现404错误 vue-router原理 vue-router是vue项目的重要组成部分&#x…...

什么是 Spring 框架?

Spring 框架是一个开源的、轻量级的企业级应用框架&#xff0c;用于构建 Java 应用程序。它提供了全面的基础设施支持&#xff0c;以简化企业级应用的开发。Spring 的核心目标是通过促进良好的设计原则和编程习惯来提高 Java 开发人员的效率和系统的可维护性。 Spring 框架的主…...

Vue2.x源码:new Vue()做了啥

例子1new Vue做了啥?new Vue做了啥,源码解析 initMixin函数 初始化 – 初始化Vue实例的配置initLifecycle函数 – 初始化生命周期钩子函数initEvents – 初始化事件系统初始化渲染 initRender初始化inject选项 例子1 <div id"app"><div class"home&…...