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

【Linux】:进程信号(信号概念 信号处理 信号产生)

✨                              眼里有诗,自向远方                        🌏 

 📃个人主页:island1314

🔥个人专栏:Linux—登神长阶

⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏  💞 💞 💞


温馨提示:信号和信号量 二者之间没有任何关系

1, 信号概念 🚀 

🚀 信号是 Linux 系统提供的一种向指定进程发送特定事件的方式,进程会对信号进行识别和处理。

信号的产生是异步的

  • 即一个进程不知道自己何时会收到信号,在收到信号之前进程只能一直在处理自己的任务

使用 kill -l 指令查看信号()

  • 每个信号都有⼀个编号和⼀个宏定义名称,这些宏定义可以在 signal.h 中找到
  • 其中:1-30号信号为普通信号,31-64号信号为实时信号

🍉 具体的信号采取的动作和详细信息可查看:man 7 signal

分析:

  • Action列即为信号的默认处理方式
  • Core、Term即为进程终止,Stop为进程暂停……
  • (Core终止进程同时还会形成一个debug文件,Term仅终止进程)

基本特点:

  • 信号:Linux系统提供的一种,向指定进程发送特定事件的方式。
  • 信号的产生和进程是异步的。即进程不知道什么时候会收到信号。
  • 信号可以随时产生
  • 如果进程做着别的事,可以暂不处理信号,等到合适的时候再处理

2, 信号处理 ❓

  ( sigaction 函数后面博客来详细介绍),现在先说可选的以下三种处理动作

  1. 默认处理(通常为终止、暂停、忽略等)

  2. 忽略处理

  3. 自定义处理(信号捕捉)

在看相关内容之前,先插播一个小知识 signal

#include <signal.h>typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);

参数:

  • signum:指定信号的编号

  • func:函数指针,该函数将在接收到sig信号时被调用。这个函数必须接受一个 int 参数(信号编号),并且返回类型为 void。

返回值:返回值为一个函数指针,指向之前的信号处理器;如果之前没有信号处理器,则返回 SIG_ERR

2.1 执行该信号的默认处理动作

如果signal函数的 func 参数为 SIG_DFL,则系统将使用默认的信号处理动作。

#include<iostream>
#include<signal.h>
#include<unistd.h>int main()
{::signal(2, SIG_DFL); // defalut 默认while(true){std::cout << "I am a process, I am waiting signal!" << std::endl;sleep(1);}
}

2.2 忽略该信号

如果signal函数的 func 参数为 SIG_IGN,则系统将忽略该信号

  • 此时 将pending表中被忽略的信号置为 0 (pending 表下面会讲地)

注意看源码:

#define SIG_DFL ((__sighandler_t) 0) /* Default action. */
#define SIG_IGN ((__sighandler_t) 1) /* Ignore signal. */
/* Type of a signal handler. */
typedef void (*__sighandler_t) (int);
// 其实SIG_DFL和SIG_IGN就是把0,1强转为函数指针类型

2.3 自定义处理(信号捕捉)

信号自定义处理,其实是对信号进行捕捉,然后让信号执行自定义的方法

  • 注意:信号的捕捉,一次捕捉就会使其一直有效
#include<iostream>
#include<signal.h>
#include<unistd.h>void hander(int signo)
{std::cout << "get a new signal: " << signo << std::endl;
}int main()
{::signal(2, hander);while(true){std::cout << "I am waiting signal!, pid: " << getpid() << std::endl;sleep(1);}
}

运行如下:

这里 signal(2, handler)

  • signal是用来进行信号捕捉的。
  • 参数1是信号的编号,参数2是函数指针。如果进程收到参数1对应的信号,就会执行参数2对应的方法

注意: ^\Quit 表示 kill -3,相当于从键盘输入了 Ctrl + \

同时我们也可以对多个信号进行捕捉

信号的保存和发送理解:

  1. 进程pcb中,是用位图来保存信号的。收到什么信号,就把对应比特位上的数字变为1
  2. 发送信号:修改指定进程 pcb 中的信号的指定位图的比特位

3, 信号产生 🔥

  1. 键盘可以产生信号。ctrl+c(SIGINT)、ctrl+\(SIGQUIT)
  2. 通过 kill 命令,向指定进程发送指定信号
  3. 系统调用
  4. 软件条件
  5. 异常

3.1 通过终端按键产生信号 ⚽

  • Ctrl+C(SIGINT):已经验证过,这⾥不再重复?
  • Ctrl+\(SIGQUIT) :可以发送终⽌信号并生成 core dump 文件,用于事后调试(后⾯详谈)

补充个知识:(前后台进程)--> 理解信号异步

  • Ctrl-C 产生的信号只能发给前台进。一个命令后面加个 & 可以放到后台运行,这样 Shell 不必等待进程结束就可以接受新的命令, 启动新的进程。
  • Shell 可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像 Ctrl-C 这种控制键产生的信号。
  • 前台进程在运行过程中用户随时可能按下 Ctrl-C 而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到 SIGINT 信号而终止,所以信号相对于进程的控制流程来说是异步(Asynchronous)

理解: OS如何得知键盘有数据?

初步理解 【信号起源】
• 信号其实是从纯软件⻆度,模拟硬件中断的行为
• 只不过硬件中断是发给CPU,而信号是发给进程?
• 两者有相似性,但是层级不同,这点我们后⾯的感觉会更加明显

3.2 通过系统命令向进程发信号 ⚾

#include <iostream>
#include <unistd.h>
#include <signal.h>
int main()
{while(true){sleep(1);}
}$ g++ sig.cc -o sig // step 1
$ ./sig & // step 2
$ ps ajx |head -1 && ps ajx | grep sig // step 3
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
211805 213784 213784 211805 pts/0 213792 S 1002 0:00 ./sig
  • 首先在后台执行死循环程序,然后用kill 命令 给它发SIGSEGV信号
$ kill -SIGSEGV 213784
$ // 多按⼀次回⻋
[1]+ Segmentation fault ./sig
  • 213784 是 sig 进程的pid。之所以要再次回车才显示 Segmentation fault ,是因为在 213784 进程终止掉之前已经回到了 Shell 提示符等待用户输入下⼀条命令, Shell 不希望 Segmentation fault   信息和用户的输入交错在⼀起,所以等用户输入命令之后才显示。
  • 指定发送某种信号的 kill 命令可以有多种写法,上面的命令还可以写成 kill -11 213784,11 是信号 SIGSEGV 的编号。以往遇到的段错误都是由非法内存访问产生的,而这个程序本身没错,给它发 SIGSEGV 也能产生段错误

3.3 使用函数产生信号 🥎

3.3.1 kill 函数
  • kill 命令是调用 kill 函数实现的。 kill 函数可以给⼀个指定的进程发送指定的信号
NAMEkill - send signal to a processSYNOPSIS#include <sys/types.h>#include <signal.h>int kill(pid_t pid, int sig);参数分析:
pid:指定进程pid,如果 pid 是负数,信号将被发送到与 pid 的绝对值相同的进程组中的所有进程
sig:指定的信号编号RETURN VALUEOn success (at least one signal was sent), zero is returned. On error, -1 is returned, and errno is set appropriately.

实现自己的 kill 命令

#include<iostream>
#include<signal.h>
#include<unistd.h>// 形成 自己的 kill 命令
void Usage(std::string proc){std::cout << "Usage: " << proc << " signumber processid " << std::endl;
}int main(int argc, char *argv[]){if(argc != 3){Usage(argv[0]);exit(1);}int signumber = std::stoi(argv[1]);pid_t id = std::stoi(argv[2]);int n = ::kill(id, signumber);if(n < 0){perror("kill");exit(2);}exit(0);
}

运行结果如下: 

3.3.2 raise 函数
  • raise 函数可以给当前进程发送指定的信号(自己给自己发信号)。
#include <signal.h>int raise(int sig);

样例:

#include<iostream>
#include<signal.h>
#include<unistd.h>void hander(int sig){std::cout << "get a sig: " << sig << std::endl;
}int main() {int cnt = 3;::signal(2, hander);while(true){raise(2);cnt--;if(cnt<=0) raise(9);sleep(1);}
}

过 3 s 后进程被杀死

3.3.3 abort 函数
  • abort 函数使当前进程接收到信号而异常终止
#include <stdlib.h>void abort(void);
#include<iostream>
#include<signal.h>
#include<unistd.h>int main(){int cnt = 3;while(true){std::cout << "IsLand 1314" << std::endl;cnt--;if(cnt<=0) abort();sleep(1);}
}

注意事项:

  1. 6号信号 SIGABRT 可以被自定义捕捉处理,但是捕捉后仍然会立即退出进程,比较特殊

  2. 9号信号 SIGKILL 无法被捕捉,否则如果所有的信号都被捕捉,那么进程将无法退出 

3.4 由软件条件产生信号 🪩

🔥 使用管道通信时,当读端关闭,但是写端一直写,操作系统就会给写端进程发送13号信号SIGPIPE,终止进程。SIGPIPE 就是一种由软件条件产生的信号,SIGPIPE 是⼀种由软件条件产生的信号。

  • 主要介绍 alarm 函数和 SIGALRM 信号
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
  • alarm 函数用于设置一个定时器,在指定时间后向进程发送14号信号SIGALRM,终止进程

  • seconds:指定定时器的时间,单位为秒。如果这个值是 0,则会取消之前设置的闹钟

  • 返回值:alarm 函数返回自上次调用的 alarm 闹钟剩余的秒数。如果之前没有设置定时器,或者定时器已经触发,返回 0

3.4.1 基本alarm验证 - 体会IO效率问题
  • 程序的作用:是1秒钟之内不停地数数,1秒钟到了就被SIGALRM信号终止,必要的时候,对SIGALRM信号进行捕捉?

结论:

  • 闹钟会响⼀次,默认终止进程
  • 有IO效率低
3.4.2 设置重复闹钟
#include<iostream>
#include<signal.h>
#include<unistd.h>
#include <functional>
#include <vector>
#include <string>
#include <signal.h>
#include <sys/wait.h>// 定时器功能
using func_t = std::function<void()>;int gcount = 0;
std::vector<func_t> gfuncs;// 1. handler 是一个信号处理函数,当接收到 SIGALRM 信号时被调用
// 2. 在处理函数内部,通过遍历 gfuncs 中的所有函数并调用它们,执行所有注册的任务
// 3. 然后输出 gcount 的值,并重新设置一个定时器(alarm(1)),使得下一个 SIGALRM 信号在 1 秒后再次发送// 如果我们把下面操作,信号 更换成 硬件中断,那么就是 OS 的操作原理void handler(int signo)
{for(auto &f: gfuncs){f();}std::cout << "gcount : "<< gcount << std::endl;int n = alarm(1); // 重设闹钟,会返回上⼀次闹钟的剩余时间std::cout << "剩余时间 : " << n << std::endl;
}int main()
{gfuncs.push_back([](){std::cout << "我是一个内核刷新操作" << std::endl;});gfuncs.push_back([](){std::cout << "我是一个检测进程时间片的操作,如果时间片到了,我会切换进程" << std::endl;});gfuncs.push_back([](){std::cout << "我是一个内存管理操作,定期清理操作系统内部的内存碎片" << std::endl;});alarm(1); // 一次性的闹钟,一旦超时 alarm 自动取消signal(SIGALRM, handler);while(true){pause();std::cout << "我醒来了..." <<std::endl;gcount++;}
}

运行结果如下:

结论:

  • 闹钟设置一次,起效一次
  • 重复设置的方法
  • alarm(0)

3.4.3 如何理解软件条件

🔥 在操作系统中,信号的软件条件指的是由 软件内部状态特定软件操作触发 的信号产生机制。

  • 这些条件包括但不限于定时器超时(如alarm函数设定的时间到达)、软件异常(如向已关闭的管道写数据产生的SIGPIPE信号)等。
  • 当这些软件条件满足时,操作系统会向相关进程发送相应的信号,以通知进程进行相应的处理。
  • 简而言之,软件条件是因操作系统内部或外部软件操作而触发的信号产生

3.4.4 简单理解系统闹钟

🐅 系统闹钟,其实本质是OS必须自身具有定时功能,并能让用户设置这种定时功能,才可能实现闹钟这样的技术,现代Linux是提供了定时功能的,定时器也要被管理:先描述,在组织。

内核中的定时器数据结构是:

struct timer_list {struct list_head entry;unsigned long expires;void (*function)(unsigned long);unsigned long data;struct tvec_t_base_s* base;
};

我们不在这部分进行深究,为了理解它,我们可以看到:定时器超时时间 expires 和 处理方法function

  • 操作系统管理定时器:采用的是时间轮的做法,但是我们为了简单理解,可以把它在组织成为"堆结构"。

3.5 硬件异常产生信号 🏀

硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号

  • 例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE 信号 发送给进程。
  • 再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为 SIGSEGV信号 发送给进程。
 3.5.1 除 0 问题

关于进程中的计算问题,一般都是交由 cpu 来完成的,在计算的过程中,难免会出现错误的计算,比如说除0,那么 cpu 又是如何知道的呢? 如下:

🐇 这就要提到 cpu 中的寄存器了,cpu 中是有很多的寄存器的,其中有一个寄存器:EFLAGS 寄存器(状态寄存器)

  • 该寄存器中有很多状态标志:这些标志表示了算术和逻辑操作的结果,如溢出(OF)、符号(SF)、零(ZF)、进位(CF)、辅助进位(AF)和奇偶校验(PF)。
  • 除 0 操作就会触发溢出,就会标定出来运算在 cpu 内部出错了。OS 是软硬件资源的管理者!OS 就会处理这种硬件问题,向目标进程发送信号,默认终止进程。

我们要知道 cup 内部是只有一套寄存器的,寄存器中的数据是属于每一个进程的,是需要对进程上下文进行保存和恢复的。

  • 如果进程因为除0操作而被操作系统标记为异常状态,但没有被终止,那么它可能会被挂起,等待操作系统的进一步处理。
  • 当操作系统决定重新调度这个进程时,会进行上下文切换,即将当前进程的上下文保存到其PCB(进程控制块)中,并加载异常进程的上下文到CPU寄存器中。

上下文切换是一个相对耗时的过程,包括保存和恢复寄存器、堆栈等信息

  • 当切换回这个进程的时候,溢出标志位的错误信息同样会被恢复,会频繁的导致除0异常而触发上下文切换,会大大增加系统的开销。
     
3.5.2 空指针问题

代码演示如下:

这个问题就与页表,MMU及CR2,CR3寄存器有关联了

🐍 MMU 和 页表 是操作系统实现虚拟内存管理和内存保护的关键机制,它们通过虚拟地址到物理地址的转换来确保程序的正确运行和内存安全。

  • CR2 和 CR3 寄存器在内存管理和错误处理中扮演着重要角色
  • CR3 寄存器用于切换不同进程的页表
  • CR2 寄存器则用于存储引起页错误的虚拟地址,帮助操作系统定位和处理错误

🐍 CR2 寄存器用于存储引起页错误的线性地址(即虚拟地址)。

  • 当 MMU 无法找到一个虚拟地址对应的物理地址时(例如,解引用空指针或野指针),会触发一个页错误(page fault)。
  • 此时,CPU会将引起页错误的虚拟地址保存到 CR2 寄存器中,并产生一个异常,此时就会向进程发送11号信号。
  • 由此可以确认:我们在C/C++当中除零,内存越界等异常,在系统层⾯上,是被当成信号处理

4, Core Dump 理解 🖊

先来看看 Core 的意思

  • Core:这个动作表示在终止进程的同时,还会生成一个 core dump 文件。这个文件包含了进程在内存中的信息,通常用于调试。例如,SIGQUIT(编号3)和 SIGSEGV(编号11)等信号的默认动作就是终止进程并生成 core dump

  • 但当进程因某个信号而 core(终止并 核心转储,这个动作在云服务器下是被默认关掉的)时,会生成一个 core dump 文件。这个文件包含了进程在内存中的状态信息,对于程序员来说是非常有用的调试工具。
  • core 动作则更常用于在进程崩溃时生成调试信息,帮助程序员找出崩溃的原因。(以gbd为例,先使用gdb打开目标文件,然后将core文件加载进来,就直接可以定位到错误在哪一行)

信号示例:

  • SIGTERM(编号15):默认动作为term,即请求进程正常退出。
  • SIGQUIT(编号3)和SIGSEGV(编号11):默认动作为core,即终止进程并生成core dump。
    当进程退出时,如果core dump为0就表示没有异常退出,如果是1就表示异常退出了

  • SIGINT的默认处理动作是终止进程,SIGOUIT的默认处理动作是终止进程并且Core Dump,现在我们来验证一下
  • 首先解释什么是Core Dump。当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump。
  • 进程异常终止通常是因为有Bug,比如非法内存访问导致段错误,事后可以用调试器检查core文件以查清错误原因,这叫做 Post-mortem Debug(事后调试)
  • 一个进程允许产生多大的 core 文件取决于进程的 Resource Limit(这个信息保存 在PCB中)。默认是不允许产生 core 文件的,因为 core 文件中可能包含用户密码等敏感信息,不安全
  • 在开发调试阶段可以用 ulimit 命令改变这个限制,允许产生 core 文件。首先用 ulimit 命令改变 Shell 进程的 Resource Limit,如允许 core 文件最大为 1024K: $ ulimit -c 1024

eg:关于core dump的演示

  • 如果你是云服务器,那么就需要手动的将core dump功能打开

  • ulimit -c 10240 打开

注意:如果 ulimit -c 10240 失败,如下:

已经ulimit打开了core,但是运行的时候没有生成core文件,图片是我的运行情况,然后右边是我把core关了,但是用代码去记录core的时候,打印的确还是1(正确来说,应该是0才对)

echo "./core.%e.%p"  |  sudo tee /proc/sys/kernel/core_pattern

./core.%e.%p  后面的 %e %p 代表对应后缀
执行这一句,普通用户的话,就在前面加sudo

然后这个我们就要切换成主用户 root 来【su -root】操作


那么关于进程信号的处理与产生我们就讲到这里啦,后面将会更新关于信号保存和处理的知识,敬请期待吧

【*★,°*:.☆( ̄▽ ̄)/$:*.°★* 】那么本篇到此就结束啦,如果我的这篇博客可以给你提供有益的参考和启示,可以三连支持一下 !💞💖!

 

相关文章:

【Linux】:进程信号(信号概念 信号处理 信号产生)

✨ 眼里有诗&#xff0c;自向远方 &#x1f30f; &#x1f4c3;个人主页&#xff1a;island1314 &#x1f525;个人专栏&#xff1a;Linux—登神长阶 ⛺️ 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&#…...

Flink运行时架构以及核心概念

1.运行构架 1.提交作业后启动一个客户端进程&#xff0c;客户端解析参数&#xff08;-d -t 等等&#xff09;&#xff0c;后进行封装由Actor通信系统提交&#xff0c;取消&#xff0c;更新任务给JobManager。 2.JobManager&#xff08;进程&#xff09;通信系统一个组件叫分发…...

用 Python 从零开始创建神经网络(五):损失函数(Loss Functions)计算网络误差

用损失函数&#xff08;Loss Functions&#xff09;计算网络误差 引言1. 分类交叉熵损失&#xff08;Categorical Cross-Entropy Loss&#xff09;2. 分类交叉熵损失类&#xff08;The Categorical Cross-Entropy Loss Class&#xff09;展示到目前为止的所有代码3. 准确率计算…...

[CKS] K8S RuntimeClass SetUp

最近准备花一周的时间准备CKS考试&#xff0c;在准备考试中发现有一个题目关于RuntimeClass创建和挂载的题目。 ​ 专栏其他文章: [CKS] Create/Read/Mount a Secret in K8S-CSDN博客[CKS] Audit Log Policy-CSDN博客 -[CKS] 利用falco进行容器日志捕捉和安全监控-CSDN博客[CKS…...

【Python爬虫实战】轻量级爬虫利器:DrissionPage之SessionPage与WebPage模块详解

&#x1f308;个人主页&#xff1a;易辰君-CSDN博客 &#x1f525; 系列专栏&#xff1a;https://blog.csdn.net/2401_86688088/category_12797772.html ​ 目录 前言 一、SessionPage &#xff08;一&#xff09;SessionPage 模块的基本功能 &#xff08;二&#xff09;基本使…...

计算机网络-2.1物理层

文章目录 通信的基础概念信源、信宿、信号、信道码元、速率、波特带宽&#xff08;Hz&#xff09; 奈奎斯特采样定律和香农采样定律编码&解码&#xff0c;调制&解调常用的编码方法常用的调制方法 传输介质1. 导向型传输介质2. 非导向型传输介质物理层接口的特性 物理层…...

纯血鸿蒙系统 HarmonyOS NEXT自动化测试实践

1、测试框架选择 hdc&#xff1a;类似 android 系统的 adb 命令&#xff0c;提供设备信息查询&#xff0c;包管理&#xff0c;调试相关的命令ohos.UiTest&#xff1a;鸿蒙 sdk 的一部分&#xff0c;类似 android sdk 里的uiautomator&#xff0c;基于 Accessibility 服务&…...

C 语言标准库 - <errno.h>

目录 1.errno 变量 2.宏 1.errno 变量 errno.h 声明了一个 int 类型的 errno 变量&#xff0c;用来存储错误码&#xff08;正整数&#xff09;。 如果这个变量有非零值&#xff0c;表示已经执行的程序发生了错误。 #include <errno.h> #include <stdio.h> #in…...

Golang自带的测试库testing的使用

testing是golang自带的测试库。 testting规则&#xff1a; 在待测试功能所在文件的同级目录中创建一个以_test.go结尾的文件。 测试函数名必须是TestXxxx这个形式&#xff0c;而且Xxxx必须以大写字母开头&#xff0c;另外函数带有一个*testing.T类型的参数。 // 单元测试&am…...

29.电影院售票系统(基于springboot和vue的Java项目)

目录 1.系统的受众说明 2 论文背景 2.1 国内研究现状&#xff1a; 2.2 国外研究现状&#xff1a; ​​​​​​​2.3 所用技术 3 系统需求分析 ​​​​​​​3.1 需求分析 ​​​​​​​3.2 可行性分析 3.2.1技术可行性分析 3.2.2市场可行性分析 3.2.3经济可…...

大学生就业平台微信小程序

随着计算机技术的成熟&#xff0c;互联网的建立&#xff0c;如今&#xff0c;PC平台上有许多关于大学生就业方面的程序&#xff0c;但由于使用时间和地点上的限制&#xff0c;用户在使用上存在着种种不方便&#xff0c;而开发一款大学生就业平台微信小程序&#xff0c;能够有效…...

Redis 缓存击穿

目录 缓存击穿 什么是缓存击穿&#xff1f; 有哪些解决办法&#xff1f; 缓存穿透和缓存击穿有什么区别&#xff1f; 缓存雪崩 什么是缓存雪崩&#xff1f; 有哪些解决办法&#xff1f; 缓存预热如何实现&#xff1f; 缓存雪崩和缓存击穿有什么区别&#xff1f; 如何保…...

初探鸿蒙:从概念到实践

一、鸿蒙开发的环境准备 开发工具&#xff1a;使用 DevEco Studio&#xff0c;支持 ArkTS 语法。 系统要求&#xff1a;确保计算机符合 DevEco Studio 的最低系统需求。安装步骤&#xff1a;下载 DevEco Studio&#xff0c;安装合适的 SDK 和模拟器 二、鸿蒙应用可以…...

PHP API的路由设计思路

PHP API的路由设计是构建高效、可维护API的关键环节。以下是一套完整的PHP API路由设计思路&#xff1a; 一、明确设计原则 使用统一资源标识符&#xff08;URI&#xff09;&#xff1a;通过URI来标识资源&#xff0c;确保每个资源都有一个唯一的地址。使用HTTP方法&#xff…...

工程师 - 如何访问Github

Github无法访问&#xff0c;涉及到IP地址、Host文件、DNS等配置。 1&#xff0c;查找github地址 打开https://www.ipaddress.com/网站&#xff0c;这个网站首页是查询自己IP的。 在上方搜索栏输入github.com&#xff0c;查找github的地址。 https://www.ipaddress.com/websit…...

222. 完全二叉树的节点个数 迭代

222. 完全二叉树的节点个数 已解答 简单 相关标签 相关企业 给你一棵 完全二叉树 的根节点 root &#xff0c;求出该树的节点个数。 完全二叉树 的定义如下&#xff1a;在完全二叉树中&#xff0c;除了最底层节点可能没填满外&#xff0c;其余每层节点数都达到最大值&#xff0…...

中心极限定理的三种形式

独立同分布的中心极限定理&#xff1a; 设 X 1 , X 2 , … , X n X_1, X_2, \ldots, X_n X1​,X2​,…,Xn​是独立同分布的随机变量序列&#xff0c;且 E ( X i ) μ E(X_i) \mu E(Xi​)μ&#xff0c; D ( X i ) σ 2 > 0 D(X_i) \sigma^2 > 0 D(Xi​)σ2>0存在…...

React Native 全栈开发实战班 - 导航栈定制

在 React Native 应用中&#xff0c;导航栈管理是实现页面跳转和状态维护的核心机制。React Navigation 提供了强大的导航栈管理功能&#xff0c;允许开发者灵活地控制页面堆栈、传递参数、处理返回逻辑等。本章节将深入探讨导航栈的管理与定制&#xff0c;包括如何控制导航栈、…...

扬州BGP高防服务器可以给企业带来哪些好处?

扬州BGP服务器是目前江苏较为出名的高防机房&#xff0c;随着网络安全逐渐被企业所重视&#xff0c;扬州机房的也被大家进行选择&#xff0c;但是扬州BGP高防服务器除了可以帮助企业抵御网络攻击&#xff0c;还有着其他的帮助&#xff0c;下面就让我们来了解一下吧&#xff01;…...

题目讲解15 合并两个排序的链表

原题链接&#xff1a; 合并两个排序的链表_牛客题霸_牛客网 思路分析&#xff1a; 第一步&#xff1a;写一个链表尾插数据的方法。 typedef struct ListNode ListNode;//申请结点 ListNode* BuyNode(int x) {ListNode* node (ListNode*)malloc(sizeof(ListNode));node->…...

技巧小结:根据寄存器手册写常用外设的驱动程序

需求&#xff1a;根据STM32F103寄存器手册写DMA模块的驱动程序 一、分析标准库函数的写法&#xff1a; 各个外设的寄存器地址定义在stm32f10x.h文件中&#xff1a;此文件由芯片厂家提供;内核的有关定义则定义在core_cm3.h文件中&#xff1a;ARM提供; 1、查看外设区域多级划分…...

手机端抓包大麦网抢票协议:实现自动抢票与支付

&#x1f680; 手机端抓包大麦网抢票协议&#xff1a;实现自动抢票与支付 &#x1f680; &#x1f525; 你是否还在为抢不到热门演出票而烦恼&#xff1f;本文将教你如何通过抓包技术获取大麦网抢票协议&#xff0c;并编写脚本实现自动化抢票与支付&#xff01;&#x1f525; …...

毕设 基于机器视觉的驾驶疲劳检测系统(源码+论文)

文章目录 0 前言1 项目运行效果2 课题背景3 Dlib人脸检测与特征提取3.1 简介3.2 Dlib优点 4 疲劳检测算法4.1 眼睛检测算法4.2 打哈欠检测算法4.3 点头检测算法 5 PyQt55.1 简介5.2相关界面代码 6 最后 0 前言 &#x1f525;这两年开始毕业设计和毕业答辩的要求和难度不断提升…...

测试(面经 八股)

目录 前言 一&#xff0c;软件测试&#xff08;定义&#xff09; 1&#xff0c;定义 2&#xff0c;目的 3&#xff0c;价值 4&#xff0c;实践 二&#xff0c;软件测试&#xff08;目的&#xff09; 1&#xff0c;找 bug 2&#xff0c;验证达标 3&#xff0c;质量评价…...

Windows 系统安装 Redis 详细教程

Windows 系统安装 Redis 详细教程 一、Redis 简介 Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的、基于内存的高性能键值存储系统&#xff0c;常被用作数据库、缓存和消息中间件。相比传统数据库&#xff0c;Redis 具有以下优势&#xff1a; 超高性能…...

React 中 HTML 插入的全场景实践与安全指南

在 React 开发过程中&#xff0c;我们常常会遇到需要插入 HTML 内容的场景。比如将服务端返回的富文本渲染到页面&#xff0c;还有处理复杂的 UI 结构&#xff0c;正确的 HTML 插入方式不仅影响页面展示效果&#xff0c;更关乎应用的安全性。 本文将详细探讨 React 中插入 HTM…...

基于STM32的DHT11温湿度远程监测LCD1602显示Proteus仿真+程序+设计报告+讲解视频

DHT11温湿度远程监测proteus仿真 1. 主要功能2.仿真3. 程序4. 设计报告5. 资料清单&下载链接 基于STM32的DHT11温湿度远程监测LCD1602显示Proteus仿真设计(仿真程序设计报告讲解视频&#xff09; 仿真图proteus 8.9 程序编译器&#xff1a;keil 5 编程语言&#xff1a;C…...

Spring AI(10)——STUDIO传输的MCP服务端

Spring AI MCP&#xff08;模型上下文协议&#xff09;服务器Starters提供了在 Spring Boot 应用程序中设置 MCP 服务器的自动配置。它支持将 MCP 服务器功能与 Spring Boot 的自动配置系统无缝集成。 本文主要演示支持STDIO传输的MCP服务器 仅支持STDIO传输的MCP服务器 导入j…...

电脑同时连接内网和外网的方法,附外网连接局域网的操作设置

对于工作一般都设置在内网网段中&#xff0c;而同时由于需求需要连接外网&#xff0c;一般只能通过内网和外网的不断切换进行设置&#xff0c;如果可以同时连接内网和外网会更加便利&#xff0c;同时连接内网和外网方法具体如下。 一、电脑怎么弄可以同时连接内网和外网&#…...

获取 OpenAI API Key

你可以按照以下步骤来获取 openai.api_key&#xff0c;用于调用 OpenAI 的 GPT-4、DALLE、Whisper 等 API 服务&#xff1a; &#x1f9ed; 获取 OpenAI API Key 的步骤&#xff1a; ✅ 1. 注册或登录 OpenAI 账号 打开 https://platform.openai.com/ 使用你的邮箱或 Google/…...