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

【Linux初阶】信号入门2 | 信号阻塞、捕捉、保存

文章目录

  • ☀️前言
  • ☀️一、信号阻塞
    • 🌻1.信号其他相关常见概念
    • 🌻2.信号在内核中的表示
  • ☀️二、信号捕捉(重点)
    • 🌻1.用户态 & 内核态
    • 🌻2.如何判断进程处于用户态或内核态
    • 🌻3.OS接口的访问方法
    • 🌻4.信号的捕捉过程
  • ☀️三、信号保存1
    • 🌻1.sigset_t
    • 🌻2. 信号集操作函数
    • 🌻3.sigprocmask
    • 🌻4.sigpending
    • 🌻5.代码示例
  • ☀️四、信号保存2
    • 🌻1.sigaction
    • 🌻2.代码示例 - sigaction
    • 🌻3.可重入函数
    • 🌻4.volatile关键字
  • ☀️五、信号总结
  • ☀️结语


☀️前言

通过我们上一篇文章的学习,我们知道信号的生命周期包括四个阶段:预备、信号产生、信号保存、信号处理。同时我们还接触到了信号的保存位置:信号被保存在进程的 task_struct中。知道了信号发送的本质就是修改进程 task_struct中的位图结构

在本片文章中,我将带领大家更加深入学习信号阻塞、捕捉、保存的知识。


☀️一、信号阻塞

🌻1.信号其他相关常见概念

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

对于信号的发送我们还要树立两点共识:1.信号发送是以操作系统为载体,向目标进程发送信号的。2.因为我们的信号不会被立即处理,因此信号产生和信号递达之间就会产生一个简单的时间窗口,在这个时间窗口中,信号已经收到了但是没有被立即处理,因此我们需要将信号保存起来。

🌻2.信号在内核中的表示

图示1:
在这里插入图片描述

  • task_struct中有两张位图和一个指针,它们分别是 pending位图block位图指向 hander函数指针数组的指针
  • pending位图默认为0,它可以表示为32个比特位,比特位的位置表示信号编号,比特位的内容(0 or 1)表示的是是否收到该信号
  • block位图默认也为0,它也可表示32个比特位,比特位的位置表示信号编号,比特位的内容表示 是否阻塞该信号
  • 指针指向 hander函数指针数组,我们可以把它简称为 hander表,数组的下标表示信号的编号,数组下标对应的函数内容表示 对应信号的处理方法
  • 图中右上角为信号的递达的伪代码,它告诉我们:如果一个信号阻塞了,信号就不会递达
  • 图的最上方有一个 signal函数,我们可以通过信号内核表示加以理解:signo代表信号编号,handler表示修改函数数组对应信号的处理方法。

总结:1.如果一个信号没有产生,并不妨碍它被阻塞。2.进程为什么能识别信号?因为每个信号都有自己对应的 pending位图、block位图 和 hander表。

图示2:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HKN04IS3-1690967125451)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230730232240040.png)]


☀️二、信号捕捉(重点)

通过上面的学习,我们知道:信号产生的时候,不会被立即处理,而是会在合适的时候被处理。

那么问题来了,究竟合适的时候是什么时候呢?答案是:从内核态返回用户态的时候,进行处理

🌻1.用户态 & 内核态

  • 进程在运行时,有两种状态(运行级别),它们分别是:用户态内核态
  • 用户态:运行我们在电脑上自己写的代码(包括数据结构等),都是在用户态下完成的。
  • 内核态:运行系统调用,就是在内核态完成的。
  • 用户为了访问某些资源(OS or 硬件),必须通过系统调用完成,因此在访问过程中需要 状态改变
  • 系统调用比较费时间,因此我们应尽量避免频繁调用系统调用。举一个简单的例子:当我们使用 vector进行扩容的时候,计算机往往会为我们多申请一些空间,这就是为了避免频繁调用系统调用。

🌻2.如何判断进程处于用户态或内核态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ev86QH5C-1690967125453)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230730231112099.png)]

  • CPU中有很多可见和不可见的寄存器,它们保存有当前运行进程的上下文数据。
  • CPU有专门的寄存器可以指向进程的 task_struct(PCB),和页表(用户级页表 & 内核级页表)的起始地址。
  • CPU内有一个名为 CR3的寄存器,它表征当前进程的运行级别:0-内核态,3-用户态

🌻3.OS接口的访问方法

在这里插入图片描述

  • 进程的 task_strcut有指向该进程的地址空间(mm_strcut)的指针,地址空间分为内核空间(1G)和用户空间(3G),因此页表也有两个:用户级页表内核级页表
  • 内核级页表指向 内核对应的虚拟地址空间。
  • 每个进程都有自己的地址空间,由于不同进程共用同一个内核,每个进程的地址空间中的内核空间都是同一个,因此内核级页表只要有一份就够了,它指向同一份虚拟地址空间和物理内存。
  • 进程要访问OS的接口,因为每个进程的地址空间中都带有同一个内核空间,因此只需要在地址空间中自行跳转到内核空间访问即可

总结:1.用户访问OS的过程:运行到特定代码 -> 系统调用(起始位置会更改CR3寄存器)-> 查看CR3寄存器(确认运行状态) -> 跳转到内核空间进行访问 -> 访问完成 -> 更改CR3寄存器 -> 返回并继续执行下一行代码。

🌻4.信号的捕捉过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l2m3h8QD-1690967125455)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230731231739555.png)]

  • 在有需要的时候陷入内核用户态 -> 内核态)。
  • 由于陷入内核会会产生一定的成本(系统调用比较费时间),因此内核处理完对应的工作或异常的时候,不会立即返回运行下一条代码,而是会以内核的身份处理一些只有内核才能完成的额外的工作。
  • 内核处理完对应的工作后会进行信号检测和递达处理
  • 信号处理分为3种:默认、忽略、自定义。大部分信号的处理方式为终止对应进程,忽略即不需要处理,自定义就是如同 signal函数一样执行我们定义的方法。
  • OS会检测是否收到某一信号、该信号是否阻塞、处理方法是哪个。
  • 如果确认收到某一信号,该信号没有阻塞,且为自定义处理方法,则:回到用户态,执行对应的自定义处理方法。(内核态 -> 用户态
  • 注意:我们不能用用户态执行内核的代码(权限不足),也不能用内核态执行用户态的代码(避免用户对内核的恶意访问)。
  • 执行完自定义处理方法之后,不能直接跳转回代码部分运行下一条代码。这是因为在我们使用系统调用时,我们的部分数据(代码运行位置)是由OS保存的,因此我们需要使用OS的身份进行恢复,再跳转回去运行下一条代码。
  • 执行完自定义处理方法之后,需要重新回到内核态,恢复数据用户态 -> 内核态)。
  • 再使用特定的系统调用,回到代码运行的地方内核态 -> 用户态)。
  • 至此,完成了信号捕捉的全过程,然后继续运行下一条代码。

信号捕捉巧记图:红色圆圈代表操作,绿色圆圈代表状态切换(4个操作 + 4次状态切换),如果信号的执行方法为默认或者忽略,则不会再沿图示路径进行下去。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YIEIQDXR-1690967125456)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230731235922188.png)]

通过学习信号的捕捉过程,我们就可以更加深入理解到本节开头时候的话:信号产生的时候,不会被立即处理,而是会在合适的时候被处理,即从内核态返回用户态的时候


☀️三、信号保存1

综合我们学习的知识,我们可以得出:信号产生之后不会立即递达,而是会在合适的时候递达,因此我们的信号在这个时间周期内需要被保存。信号被保存在进程的 task_struct中,信号发送(保存)的本质就是修改进程 task_struct中的位图结构

这里我们再复习一下信号递达和信号未决的知识点,方便后面的学习:

  • 实际执行信号的处理动作称为信号递达(Delivery)
  • 信号从产生到递达之间的状态,称为信号未决(Pending)

🌻1.sigset_t

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NQ8SwR98-1690967125456)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230730232240040.png)]

  • 从上图来看,每个信号只有一个bit的未决标志(判断是否收到该信号),非0即1,不记录该信号产生了多少次,阻塞标志(判断信号是否阻塞)也是这样表示的。
  • 因此,未决和阻塞标志可以用相同的数据类型 sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态
  • 下一节将详细介绍信号集的各种操作。 阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。

🌻2. 信号集操作函数

sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作 sigset_t变量,而不应该对它的内部数据做任何解释,比如用printf直接打印sigset_t变量是没有意义的。

#include <signal.h>int sigemptyset(sigset_t *set);int sigfillset(sigset_t *set);int sigaddset (sigset_t *set,int signo);int sigdelset(sigset_t *set, int signo);int sigismember(const sigset_t *set, int signo); 
  • 函数 sigemptyset初始化 set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号。
  • 函数 sigfifillset初始化 set所指向的信号集,使其中所有信号的对应bit置为1,表示该信号集的有效信号包括系统支持的所有信号。
  • 注意,在使用sigset_ t类型的变量之前,一定要调 用 sigemptysetsigfifillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用 sigaddsetsigdelset在该信号集中添加或删除某种有效信号。
  • 这四个函数都是成功返回0,出错返回-1。
  • sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种 信号,若包含则返回1,不包含则返回0,出错返回-1。

🌻3.sigprocmask

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

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 
返回值:若成功则为0,若出错则为-1
  • 如果 oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出
  • 如果 set是非空指针,则更改进程的信号屏蔽字
  • 参数 how指示如何更改
  • 如果oset和set都是非空指针,则先将原来的信号 屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。
  • 假设当前的信号屏蔽字为mask,下表说明了how参数的可选值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4UoEX5Jk-1690967125457)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230801105602021.png)]

  • 如果调用 sigprocmask解除了对当前若干个未决信号的阻塞,则在 sigprocmask返回前,至少将其中一个信号递达

🌻4.sigpending

#include <signal.h>
int sigpending(sigset_t *set);
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1

总结:sigprocmask - 修改block位图(阻塞信号集/信号屏蔽字),sigpending - 获取pending位图(未决信号集),signal - 修改信号处理方法。

🌻5.代码示例

  • 下面代码讲述的是如何调整信号屏蔽字
#include <iostream>
#include <vector>
#include <signal.h>
#include <unistd.h>// #define BLOCK_SIGNAL 2
#define MAX_SIGNUM 31using namespace std;// static vector<int> sigarr = {2,3};
static vector<int> sigarr = { 2 };static void show_pending(const sigset_t& pending)
{for (int signo = MAX_SIGNUM; signo >= 1; signo--){if (sigismember(&pending, signo)){cout << "1";}else cout << "0";}cout << "\n";
}static void myhandler(int signo)
{cout << signo << " 号信号已经被递达!!" << endl;
}int main()
{for (const auto& sig : sigarr) signal(sig, myhandler);// 1. 先尝试屏蔽指定的信号sigset_t block, oblock, pending;// 1.1 初始化sigemptyset(&block);sigemptyset(&oblock);sigemptyset(&pending);// 1.2 添加要屏蔽的信号for (const auto& sig : sigarr) sigaddset(&block, sig);// 1.3 开始屏蔽,设置进内核(进程)sigprocmask(SIG_SETMASK, &block, &oblock);// 2. 遍历打印pengding信号集int cnt = 10;while (true){// 2.1 初始化sigemptyset(&pending);// 2.2 获取它sigpending(&pending);// 2.3 打印它show_pending(pending);// 3. 慢一点sleep(1);if (cnt-- == 0){sigprocmask(SIG_SETMASK, &oblock, &block); // 一旦对特定信号进行解除屏蔽,一般OS要至少立马递达一个信号!cout << "恢复对信号的屏蔽,不屏蔽任何信号\n";}
  • 运行结果如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hxSWfAj0-1690967125459)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230801225551420.png)]

总结:我们可以通过信号集操作函数初始化信号集,并将需要屏蔽的信号加入屏蔽信号集中,然后用 sigprocmask函数将信号集内容射入内核,然后通过 sigpending函数查看 pending信号集。上面的示例显示,当我们屏蔽2号信号之后,我们输入 ctrl+C 后会将信号存储于 pending信号集中,而不会递达,即不会执行 signal函数中的 myhandler方法。


☀️四、信号保存2

🌻1.sigaction

#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);

知识点1:

  • sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1。
  • signo是指定信号的编号。
  • act指针非空,则根据act修改该信号的处理动作。
  • oact指针非 空,则通过oact传出该信号原来的处理动作。
  • act和oact指向sigaction结构体
  • sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。

知识点2:

  • 当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止
  • 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。
  • sa_flflags/sa_flags字段包含一些选项,本章的代码都把sa_flflags设为0,sa_sigaction是实时信号的处理函数,本章不详细解释这两个字段,有兴趣的同学可以在了解一下。

🌻2.代码示例 - sigaction

  • 下述代码用于验证:某个信号在递达时,该信号会被屏蔽。
 #include <iostream>#include <cstdio>#include <signal.h>#include <unistd.h>using namespace std;void Count(int cnt){while(cnt){printf("cnt: %2d\r", cnt);fflush(stdout);cnt--;sleep(1);}printf("\n");}void handler(int signo){cout << "get a signo: " << signo << "正在处理中..." << endl;Count(20); //调用计时程序}int main(){struct sigaction act, oact; act.sa_handler = handler;act.sa_flags = 0;sigemptyset(&act.sa_mask); // 当我们正在处理某一种信号的时候,我们也想顺便屏蔽其他信号,就可以添加到这个sa_mask中sigaddset(&act.sa_mask, 3); //对3号信号也添加屏蔽sigaction(SIGINT, &act, &oact); //SIGINT为2号信号while(true) sleep(1);return 0;}
  • 运行结果如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SstA1NV6-1690967793303)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230801233636876.png)]

总结:代码运行时,在第一个信号递达过程中(计数器开始计时),我们再向该进程发送2号信号则无法递达,第二次发送的2号信号将被保存在 pending位图中,等待第一次发送的信号递达完成之后才会执行对应方法,第3、4…次的信号发送均会失效/丢失。

🌻3.可重入函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IPuHK8xW-1690967125460)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230802001418612.png)]

  • main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步(如上图insert代码所示),刚做完第一步的 时候,因为硬件中断(该进程的时间片到了)使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换到sighandler函数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的两步都做完之后从sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续往下执行,先前做第一步之后被打断,现在继续做完第二步。结果是,main函数和sighandler先后 向链表中插入两个节点,而最后只有一个节点真正插入链表中了。
  • main 执行流 和 handler执行流(信号捕捉执行流)是两个不同的执行流,它们之间相互独立
  • 像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为 不可重入函数,反之,如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数

如果一个函数符合以下条件之一则是不可重入的:

  • 调用了malloc或free,因为malloc也是用全局链表来管理堆的。
  • 调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

🌻4.volatile关键字

  • 该关键字在C当中我们已经有所涉猎,今天我们站在信号的角度重新理解一下
[ldx@localhost code_test]$ cat sig.c
#include <stdio.h>
#include <signal.h>int flag = 0;void handler(int sig)
{printf("chage flag 0 to 1\n");flag = 1;
}int main()
{signal(2, handler);while (!flag);printf("process quit normal\n");return 0;
}[ldx@localhost code_test]$ cat Makefile
sig : sig.c
gcc -o sig sig.c #-O2      #使用#号屏蔽优化,02为优化级别
.PHONY : clean
clean :
rm - f sig[ldx@localhost code_test]$ ./sig
^ Cchage flag 0 to 1
process quit normal

标准情况下,键入 Ctrl-C ,2号信号被捕捉,执行自定义动作,修改 flag=1 , while 条件不满足,退出循环,进程退出

[ldx@localhost code_test]$ cat sig.c
#include <stdio.h>
#include <signal.h>int flag = 0;void handler(int sig)
{printf("chage flag 0 to 1\n");flag = 1;
}int main()
{signal(2, handler);while (!flag);printf("process quit normal\n");return 0;
}[ldx@localhost code_test]$ cat Makefile
sig : sig.c
gcc -o sig sig.c -O2       #放开屏蔽,设置优化级别02
.PHONY : clean
clean :
rm - f sig[ldx@localhost code_test]$ ./sig
^ Cchage flag 0 to 1
^ Cchage flag 0 to 1
^ Cchage flag 0 to 1

我们的代码在编译过程中,编译器会对其进行优化,优化有不同级别,优化情况下,键入 Ctrl-C ,2号信号被捕捉,执行自定义动作,修改 flag=1 ,但是 while 条件依旧满足,进程继续运行!但是很明显flag肯定已经被修改了,但是为何循环依旧执行?很明显while 循环检查的flag,并不是内存中最新的flag,这就存在了数据二异性的问题。 while检测的flag其实已经因为优化,被放在了CPU寄存器当中。如何解决呢?很明显需要 volatile

  • 优化过程中,编译器认为while循环中的flag不会被修改,因此它默认提前将flag的值加载到cup中去了,然后让出资源执行其他代码去了,即对于 flag只做了检测,没有做修改。
  • handler中修改的值是内存中的flag值,和已经 load到cpu中的 flag数据并不相同,只要cpu(寄存器)中的flag不变,那么循环就会一直进行下去。
[ldx@localhost code_test]$ cat sig.c#include <stdio.h>
#include <signal.h>volatile int flag = 0; //在全局变量前加volatile关键字void handler(int sig)
{printf("chage flag 0 to 1\n");flag = 1;
}int main()
{signal(2, handler);while (!flag);printf("process quit normal\n");return 0;
}[ldx@localhost code_test]$ cat Makefile
sig : sig.c
gcc - o sig sig.c - O2
.PHONY : clean
clean :
rm - f sig[ldx@localhost code_test]$ . / sig
^ Cchage flag 0 to 1
process quit normal

volatile 作用:保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量的任何操作,都必须在真实的内存中进行操作

我们需要根据实际应用场景(优化级别比较高且存在需要更新的判断变量),判断我们是否需要添加volatile 关键字。

☀️五、信号总结

  • 下面是我在前面的信号文章中给出的信号生命周期图

在这里插入图片描述

  • 下面是信号的知识点汇总,方便大家对应回顾

  • 信号的预备,信号的基本概念。

  • 信号的产生,信号的产生方法,发送本质。

  • 信号捕捉(用户态内核态 & OS接口的访问方法 & 捕捉过程)

  • 信号的保存,保存位置,保存方法,未决与递达的概念,信号阻塞,信号集及其操作,修改信号屏蔽字的方法,查看pending位图的方法,多次发送同一信号的现象。

  • 信号处理,信号递达。


☀️结语

🌹🌹 信号阻塞 & 信号捕捉 & 信号的保存 的知识大概就讲到这里啦,博主后续会继续更新更多C++ 和 Linux的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪

相关文章:

【Linux初阶】信号入门2 | 信号阻塞、捕捉、保存

文章目录 ☀️前言☀️一、信号阻塞&#x1f33b;1.信号其他相关常见概念&#x1f33b;2.信号在内核中的表示 ☀️二、信号捕捉&#xff08;重点&#xff09;&#x1f33b;1.用户态 & 内核态&#x1f33b;2.如何判断进程处于用户态或内核态&#x1f33b;3.OS接口的访问方法…...

【已解决】:该该虚拟机似乎正在使用中。如果该虚拟机未在使用,请按“获取所有权(T)“按钮获取它的所有权。否则,请按“取消(C)“按钮以防损坏。

现象 启动VMware虚拟机&#xff0c;无法正常打开并出现以下信息&#xff1a; 原因 这是因为当运行一个“虚拟系统”时&#xff0c;为防止该系统被另外一个VMware程序打开&#xff0c;导致数据被修改或损坏&#xff0c;VMware会自动在该“虚拟系统”所在的文件夹下&#xff0c…...

系统架构常用的工具

HBase HBase是一个分布式的、面向列的开源数据库&#xff0c;该技术来源于 Fay Chang 所撰写的Google论文“Bigtable&#xff1a;一个结构化数据的分布式存储系统”。就像Bigtable利用了Google文件系统&#xff08;File System&#xff09;所提供的分布式数据存储一样&#xff…...

腾讯云2核4G服务器5M带宽 218元一年 优惠价格明细表

腾讯云2核4G服务器5M带宽可以选择轻量应用服务器或云服务器ECS&#xff0c;轻量2核4G5M带宽服务器218元一年&#xff1a; 腾讯云2核4G服务器5M带宽收费 腾讯云2核4G服务器可以选择轻量应用服务器或者ECS云服务器&#xff0c;云服务器ECS是专业级云服务器&#xff0c;大多数使用…...

[C++ 网络协议] 多播与广播

目录 1. 多播 1.1 多播的使用情形 1.2 多播的原理 1.3 如何实现多播 1.4 多播的代码实现 2. 广播 2.1 广播与多播的区别 2.2 广播的分类 2.3 实现广播 1. 多播 1.1 多播的使用情形 考虑一种情形&#xff0c;你要向10000名用户发送数据&#xff0c;此时如果用TCP提供服…...

IOS17正式版今日发布

北京时间9月19日凌晨&#xff0c;苹果公司正式向全球用户推送了期待已久的iOS 17正式版。此次更新为iPhone带来了多项激动人心的功能&#xff0c;包括对“电话”、“信息”、FaceTime通话的重大更新&#xff0c;“待机显示”以及音乐、小组件、Safari浏览器的升级等。 据了解&…...

2560. 打家劫舍 IV

沿街有一排连续的房屋。每间房屋内都藏有一定的现金。现在有一位小偷计划从这些房屋中窃取现金。 由于相邻的房屋装有相互连通的防盗系统&#xff0c;所以小偷 不会窃取相邻的房屋 。 小偷的 窃取能力 定义为他在窃取过程中能从单间房屋中窃取的 最大金额 。 给你一个整数数…...

java web中部署log4j.xml

标题&#xff1a;Java Web中部署log4j.xml 目录&#xff1a; 1. 介绍 2. 配置log4j.xml文件 3. 配置web.xml文件 4. 配置Spring框架 5. 配置Spring Bean 6. 总结 ## 1. 介绍 在Java Web开发中&#xff0c;日志记录是非常重要的一部分。log4j是一个常用的Java日志记录框架&am…...

【张兔兔送书第一期:考研必备书单】

考研书单必备 《数据结构与算法分析》《计算机网络&#xff1a;自顶向下方法》《现代操作系统》《深入理解计算机系统》《概率论基础教程&#xff08;原书第10版》《线性代数&#xff08;原书第10版&#xff09;》《线性代数及其应用》赠书活动 八九月的朋友圈刮起了一股晒通知…...

基于Spring Boot+ Vue的健身房管理系统与实现

小熊学Java全能学面试指南&#xff1a;https://javaxiaobear.cn 摘要 随着健身行业的快速发展&#xff0c;健身房管理系统成为了提高管理效率和用户体验的重要工具。本论文旨在设计与实现一种基于前后端分离的健身房管理系统&#xff0c;通过前后端分离的架构模式&#xff0c;…...

ThreadLocal线程局部变量

1.原理 ThreadLocal是用来保存当前线程数据的&#xff0c;每一个线程的内部都有一个ThreadLocalMap&#xff0c;当前这个map中存储了以当前ThreadLocal作键&#xff0c;具体的数据作值的一个个Entry对象。 为什么非得以ThreadLocal对象作键呢&#xff1f;因为一个线程可能使用了…...

C++ Primer (第五版)第一章习题部分答案

在我自学C过程中&#xff0c;我选择了CPrimer这本书&#xff0c;并对部分代码习题进行了求解以及运行结果。接下来几个月我将为大家定时按章节更新习题答案与运行结果: 目录 1.9编写程序,使用while循环将50到100的整数相加 1.10 除了运算符将运算对象的值增加1之外,还有一个…...

Python与GUI集成:零基础也能开发国际象棋游戏

引言&#xff1a; 国际象棋&#xff0c;作为世界上最受欢迎的棋类游戏之一&#xff0c;拥有丰富的策略和深度。但是&#xff0c;你知道自己可以使用Python来创建一个简单的国际象棋游戏并为其添加图形用户界面&#xff08;GUI&#xff09;吗&#xff1f;在本教程中&#xff0c…...

SaaS软件能保证数据安全吗?

SaaS软件能保证数据安全吗&#xff1f; 本文将要尝试从各个方面尽可能客观的去阐述这个问题&#xff0c;而不是简单自嗨式的说简道云平台如何保障数据安全。 建议先收藏起来慢慢品&#xff01; 01 SaaS安全到底是什么&#xff1f;——定义解读 本文所用SaaS平台>>>…...

方案:基于AI烟火识别与视频技术的秸秆焚烧智能化监控预警方案

一、方案背景 为严控秸秆露天焚烧&#xff0c;改善环境空气质量&#xff0c;各省相继发布秸秆禁烧工作内容。以安徽省为例&#xff0c;大气污染防治联席会议下发了该省2020年秸秆禁烧工作部署通知。2020年起&#xff0c;气象局将对全省秸秆焚烧火点实施卫星全年全时段监测&…...

phantomjs插件---实现通过链接生成网页截图

Phantomjs | PhantomJS 配置要求 windows下&#xff0c;安装完成phantomJS 设置phantomjs环境变量【也可直接使用phantomjs目录下的执行文件】 直接通过访问php文件执行/通过cmd命令行执行【phantomjs phantom_script.js】 linux下,安装完成phantomJS 设置phantomjs环境变量 直…...

SpringBoot分页实现查询数据

1.原生查询 1.1创建分页查询实体类 package com.itheima.pojo;import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;import java.util.List;//分页查询结果封装类 Data NoArgsConstructor AllArgsConstructor public class PageBean {pr…...

Jetson Xavier NX 与飞控(Pixhawk 4 Mini)实现串口通信

一、飞控端配置 首先对 Pixhawk 4 Mini 烧录固件参考 Kakute H7 刷写 px4 固件_想要个小姑娘的博客-CSDN博客 烧录完成后打开 QGroundControl&#xff0c;进入参数设置并搜索 MAV&#xff0c;如下所示 然后修改 MAV_1_CONFIG&#xff0c;修改为自己想要连接机载电脑&#xf…...

为什么2022年秋招嵌入式开发岗位薪资大涨?

今天看到一个网友讨论的问题&#xff0c;其实这个问题也很简答。从嵌入式本身优势来说&#xff0c;首先是因为该行业人才人才需求大&#xff0c;据权威统计机构统计在所有软件开发类人才的需求中&#xff0c;对嵌入式工程师的需求达到全部需求量的60%~80%&#xff0c;并且每年以…...

在HTML里,attribute和property有什么区别?

在HTML中&#xff0c;attribute 和 property 之间的区别是一个常见但容易混淆的概念。它们都与HTML元素有关&#xff0c;但它们在功能、用途和行为上有所不同。以下是它们之间的主要区别&#xff1a; 定义和来源: Attribute: 它们是在HTML标记中定义的&#xff0c;通常用于提供…...

synchronized 学习

学习源&#xff1a; https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖&#xff0c;也要考虑性能问题&#xff08;场景&#xff09; 2.常见面试问题&#xff1a; sync出…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动

一、前言说明 在2011版本的gb28181协议中&#xff0c;拉取视频流只要求udp方式&#xff0c;从2016开始要求新增支持tcp被动和tcp主动两种方式&#xff0c;udp理论上会丢包的&#xff0c;所以实际使用过程可能会出现画面花屏的情况&#xff0c;而tcp肯定不丢包&#xff0c;起码…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器

——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的​​一体化测试平台​​&#xff0c;覆盖应用全生命周期测试需求&#xff0c;主要提供五大核心能力&#xff1a; ​​测试类型​​​​检测目标​​​​关键指标​​功能体验基…...

华为OD机试-食堂供餐-二分法

import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...

CMake 从 GitHub 下载第三方库并使用

有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版&#xff0c;柱状图PPT模版&#xff0c;线状图PPT模版&#xff0c;折线图PPT模版&#xff0c;饼状图PPT模版&#xff0c;雷达图PPT模版&#xff0c;树状图PPT模版 图表类系列各种样式PPT模版分享&#xff1a;图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”

目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...

HDFS分布式存储 zookeeper

hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架&#xff0c;允许使用简单的变成模型跨计算机对大型集群进行分布式处理&#xff08;1.海量的数据存储 2.海量数据的计算&#xff09;Hadoop核心组件 hdfs&#xff08;分布式文件存储系统&#xff09;&a…...

CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)

漏洞概览 漏洞名称&#xff1a;Apache Flink REST API 任意文件读取漏洞CVE编号&#xff1a;CVE-2020-17519CVSS评分&#xff1a;7.5影响版本&#xff1a;Apache Flink 1.11.0、1.11.1、1.11.2修复版本&#xff1a;≥ 1.11.3 或 ≥ 1.12.0漏洞类型&#xff1a;路径遍历&#x…...

【Linux手册】探秘系统世界:从用户交互到硬件底层的全链路工作之旅

目录 前言 操作系统与驱动程序 是什么&#xff0c;为什么 怎么做 system call 用户操作接口 总结 前言 日常生活中&#xff0c;我们在使用电子设备时&#xff0c;我们所输入执行的每一条指令最终大多都会作用到硬件上&#xff0c;比如下载一款软件最终会下载到硬盘上&am…...