【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:
☀️二、信号捕捉(重点)
通过上面的学习,我们知道:信号产生的时候,不会被立即处理,而是会在合适的时候被处理。
那么问题来了,究竟合适的时候是什么时候呢?答案是:从内核态返回用户态的时候,进行处理。
🌻1.用户态 & 内核态
- 进程在运行时,有两种状态(运行级别),它们分别是:
用户态
和内核态
。 - 用户态:运行我们在电脑上自己写的代码(包括数据结构等),都是在用户态下完成的。
- 内核态:运行系统调用,就是在内核态完成的。
- 用户为了访问某些资源(OS or 硬件),必须通过系统调用完成,因此在访问过程中需要 状态改变。
- 系统调用比较费时间,因此我们应尽量避免频繁调用系统调用。举一个简单的例子:当我们使用 vector进行扩容的时候,计算机往往会为我们多申请一些空间,这就是为了避免频繁调用系统调用。
🌻2.如何判断进程处于用户态或内核态
- CPU中有很多可见和不可见的寄存器,它们保存有当前运行进程的上下文数据。
- CPU有专门的寄存器可以指向进程的 task_struct(PCB),和页表(用户级页表 & 内核级页表)的起始地址。
- CPU内有一个名为
CR3
的寄存器,它表征当前进程的运行级别:0-内核态,3-用户态。
🌻3.OS接口的访问方法
- 进程的 task_strcut有指向该进程的地址空间(mm_strcut)的指针,地址空间分为内核空间(1G)和用户空间(3G),因此页表也有两个:
用户级页表
和内核级页表
。 - 内核级页表指向 内核对应的虚拟地址空间。
- 每个进程都有自己的地址空间,由于不同进程共用同一个内核,每个进程的地址空间中的内核空间都是同一个,因此内核级页表只要有一份就够了,它指向同一份虚拟地址空间和物理内存。
- 进程要访问OS的接口,因为每个进程的地址空间中都带有同一个内核空间,因此只需要在地址空间中自行跳转到内核空间访问即可。
总结:1.用户访问OS的过程:运行到特定代码 -> 系统调用(起始位置会更改CR3寄存器)-> 查看CR3寄存器(确认运行状态) -> 跳转到内核空间进行访问 -> 访问完成 -> 更改CR3寄存器 -> 返回并继续执行下一行代码。
🌻4.信号的捕捉过程
- 在有需要的时候陷入内核(
用户态 -> 内核态
)。 - 由于陷入内核会会产生一定的成本(系统调用比较费时间),因此内核处理完对应的工作或异常的时候,不会立即返回运行下一条代码,而是会以内核的身份处理一些只有内核才能完成的额外的工作。
- 内核处理完对应的工作后会进行信号检测和递达处理。
- 信号处理分为3种:默认、忽略、自定义。大部分信号的处理方式为终止对应进程,忽略即不需要处理,自定义就是如同 signal函数一样执行我们定义的方法。
- OS会检测是否收到某一信号、该信号是否阻塞、处理方法是哪个。
- 如果确认收到某一信号,该信号没有阻塞,且为自定义处理方法,则:回到用户态,执行对应的自定义处理方法。(
内核态 -> 用户态
) - 注意:我们不能用用户态执行内核的代码(权限不足),也不能用内核态执行用户态的代码(避免用户对内核的恶意访问)。
- 执行完自定义处理方法之后,不能直接跳转回代码部分运行下一条代码。这是因为在我们使用系统调用时,我们的部分数据(代码运行位置)是由OS保存的,因此我们需要使用OS的身份进行恢复,再跳转回去运行下一条代码。
- 执行完自定义处理方法之后,需要重新回到内核态,恢复数据(
用户态 -> 内核态
)。 - 再使用特定的系统调用,回到代码运行的地方(
内核态 -> 用户态
)。 - 至此,完成了信号捕捉的全过程,然后继续运行下一条代码。
信号捕捉巧记图:红色圆圈代表操作,绿色圆圈代表状态切换(4个操作 + 4次状态切换),如果信号的执行方法为默认或者忽略,则不会再沿图示路径进行下去。
通过学习信号的捕捉过程,我们就可以更加深入理解到本节开头时候的话:信号产生的时候,不会被立即处理,而是会在合适的时候被处理,即从内核态返回用户态的时候。
☀️三、信号保存1
综合我们学习的知识,我们可以得出:信号产生之后不会立即递达,而是会在合适的时候递达,因此我们的信号在这个时间周期内需要被保存。信号被保存在进程的 task_struct
中,信号发送(保存)的本质就是修改进程 task_struct中的位图结构
。
这里我们再复习一下信号递达和信号未决的知识点,方便后面的学习:
- 实际执行信号的处理动作称为信号递达(Delivery)。
- 信号从产生到递达之间的状态,称为信号未决(Pending)。
🌻1.sigset_t
- 从上图来看,每个信号只有一个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类型的变量之前,一定要调 用
sigemptyset
或sigfifillset
做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset
和sigdelset
在该信号集中添加或删除某种有效信号。 - 这四个函数都是成功返回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参数的可选值。
- 如果调用 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";}
- 运行结果如下
总结:我们可以通过信号集操作函数初始化信号集,并将需要屏蔽的信号加入屏蔽信号集中,然后用 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;}
- 运行结果如下
总结:代码运行时,在第一个信号递达过程中(计数器开始计时),我们再向该进程发送2号信号则无法递达,第二次发送的2号信号将被保存在 pending位图中,等待第一次发送的信号递达完成之后才会执行对应方法,第3、4…次的信号发送均会失效/丢失。
🌻3.可重入函数
- 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 | 信号阻塞、捕捉、保存
文章目录 ☀️前言☀️一、信号阻塞🌻1.信号其他相关常见概念🌻2.信号在内核中的表示 ☀️二、信号捕捉(重点)🌻1.用户态 & 内核态🌻2.如何判断进程处于用户态或内核态🌻3.OS接口的访问方法…...
【已解决】:该该虚拟机似乎正在使用中。如果该虚拟机未在使用,请按“获取所有权(T)“按钮获取它的所有权。否则,请按“取消(C)“按钮以防损坏。
现象 启动VMware虚拟机,无法正常打开并出现以下信息: 原因 这是因为当运行一个“虚拟系统”时,为防止该系统被另外一个VMware程序打开,导致数据被修改或损坏,VMware会自动在该“虚拟系统”所在的文件夹下,…...
系统架构常用的工具
HBase HBase是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的Google论文“Bigtable:一个结构化数据的分布式存储系统”。就像Bigtable利用了Google文件系统(File System)所提供的分布式数据存储一样ÿ…...
腾讯云2核4G服务器5M带宽 218元一年 优惠价格明细表
腾讯云2核4G服务器5M带宽可以选择轻量应用服务器或云服务器ECS,轻量2核4G5M带宽服务器218元一年: 腾讯云2核4G服务器5M带宽收费 腾讯云2核4G服务器可以选择轻量应用服务器或者ECS云服务器,云服务器ECS是专业级云服务器,大多数使用…...
[C++ 网络协议] 多播与广播
目录 1. 多播 1.1 多播的使用情形 1.2 多播的原理 1.3 如何实现多播 1.4 多播的代码实现 2. 广播 2.1 广播与多播的区别 2.2 广播的分类 2.3 实现广播 1. 多播 1.1 多播的使用情形 考虑一种情形,你要向10000名用户发送数据,此时如果用TCP提供服…...
IOS17正式版今日发布
北京时间9月19日凌晨,苹果公司正式向全球用户推送了期待已久的iOS 17正式版。此次更新为iPhone带来了多项激动人心的功能,包括对“电话”、“信息”、FaceTime通话的重大更新,“待机显示”以及音乐、小组件、Safari浏览器的升级等。 据了解&…...
2560. 打家劫舍 IV
沿街有一排连续的房屋。每间房屋内都藏有一定的现金。现在有一位小偷计划从这些房屋中窃取现金。 由于相邻的房屋装有相互连通的防盗系统,所以小偷 不会窃取相邻的房屋 。 小偷的 窃取能力 定义为他在窃取过程中能从单间房屋中窃取的 最大金额 。 给你一个整数数…...
java web中部署log4j.xml
标题:Java Web中部署log4j.xml 目录: 1. 介绍 2. 配置log4j.xml文件 3. 配置web.xml文件 4. 配置Spring框架 5. 配置Spring Bean 6. 总结 ## 1. 介绍 在Java Web开发中,日志记录是非常重要的一部分。log4j是一个常用的Java日志记录框架&am…...
【张兔兔送书第一期:考研必备书单】
考研书单必备 《数据结构与算法分析》《计算机网络:自顶向下方法》《现代操作系统》《深入理解计算机系统》《概率论基础教程(原书第10版》《线性代数(原书第10版)》《线性代数及其应用》赠书活动 八九月的朋友圈刮起了一股晒通知…...
基于Spring Boot+ Vue的健身房管理系统与实现
小熊学Java全能学面试指南:https://javaxiaobear.cn 摘要 随着健身行业的快速发展,健身房管理系统成为了提高管理效率和用户体验的重要工具。本论文旨在设计与实现一种基于前后端分离的健身房管理系统,通过前后端分离的架构模式,…...
ThreadLocal线程局部变量
1.原理 ThreadLocal是用来保存当前线程数据的,每一个线程的内部都有一个ThreadLocalMap,当前这个map中存储了以当前ThreadLocal作键,具体的数据作值的一个个Entry对象。 为什么非得以ThreadLocal对象作键呢?因为一个线程可能使用了…...
C++ Primer (第五版)第一章习题部分答案
在我自学C过程中,我选择了CPrimer这本书,并对部分代码习题进行了求解以及运行结果。接下来几个月我将为大家定时按章节更新习题答案与运行结果: 目录 1.9编写程序,使用while循环将50到100的整数相加 1.10 除了运算符将运算对象的值增加1之外,还有一个…...
Python与GUI集成:零基础也能开发国际象棋游戏
引言: 国际象棋,作为世界上最受欢迎的棋类游戏之一,拥有丰富的策略和深度。但是,你知道自己可以使用Python来创建一个简单的国际象棋游戏并为其添加图形用户界面(GUI)吗?在本教程中,…...
SaaS软件能保证数据安全吗?
SaaS软件能保证数据安全吗? 本文将要尝试从各个方面尽可能客观的去阐述这个问题,而不是简单自嗨式的说简道云平台如何保障数据安全。 建议先收藏起来慢慢品! 01 SaaS安全到底是什么?——定义解读 本文所用SaaS平台>>>…...
方案:基于AI烟火识别与视频技术的秸秆焚烧智能化监控预警方案
一、方案背景 为严控秸秆露天焚烧,改善环境空气质量,各省相继发布秸秆禁烧工作内容。以安徽省为例,大气污染防治联席会议下发了该省2020年秸秆禁烧工作部署通知。2020年起,气象局将对全省秸秆焚烧火点实施卫星全年全时段监测&…...
phantomjs插件---实现通过链接生成网页截图
Phantomjs | PhantomJS 配置要求 windows下,安装完成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,进入参数设置并搜索 MAV,如下所示 然后修改 MAV_1_CONFIG,修改为自己想要连接机载电脑…...
为什么2022年秋招嵌入式开发岗位薪资大涨?
今天看到一个网友讨论的问题,其实这个问题也很简答。从嵌入式本身优势来说,首先是因为该行业人才人才需求大,据权威统计机构统计在所有软件开发类人才的需求中,对嵌入式工程师的需求达到全部需求量的60%~80%,并且每年以…...
在HTML里,attribute和property有什么区别?
在HTML中,attribute 和 property 之间的区别是一个常见但容易混淆的概念。它们都与HTML元素有关,但它们在功能、用途和行为上有所不同。以下是它们之间的主要区别: 定义和来源: Attribute: 它们是在HTML标记中定义的,通常用于提供…...
机器学习入门与实践:从原理到代码
💂 个人网站:【工具大全】【游戏大全】【神级源码资源网】🤟 前端学习课程:👉【28个案例趣学前端】【400个JS面试题】💅 寻找学习交流、摸鱼划水的小伙伴,请点击【摸鱼学习交流群】 在本文中,我…...
SpringCloud在idea中一键启动项目
1、如下图文件中加上: <component name"RunDashboard"><option name"configurationTypes"><set><option value"SpringBootApplicationConfigurationType" /></set></option></component>…...
VB过程的递归调用,辗转相除法求最大公约数
VB过程的递归调用,辗转相除法求最大公约数 过程的递归调用,辗转相除法求最大公约数 Private Function gys(ByVal m%, ByVal n%) As IntegerDim r%r m Mod n m大或者n大都无所谓,这个不影响计算,由于辗转相除法的算法,…...
OpenCV(三十九):积分图像
1.积分图像介绍 积分图像中的每个像素表示了原始图像中对应位置及其左上方矩形区域内像素值的总和。如图,p0表示原始图像蓝色区域内像素值的总和。 倾斜求和(Skewed Sum)是积分图像的一种扩展形式,用于计算图像区域内的像素和&…...
【Electron 拦截请求实现自定义网络处理】
文章目录 Electron 拦截请求实现自定义网络处理1. 获取默认会话2. 拦截请求3. 完整示例代码总结 Electron 拦截请求实现自定义网络处理 在 Electron 中,我们可以使用 session 模块来拦截和处理网络请求。通过拦截请求,我们可以对请求进行修改、添加请求…...
Pytest系列-内置标签skip和skipif 跳过测试用例的详细使用(5)
简介 skip和skipif,见名知意就是跳过测试,主要用于不想执行的代码,标记后,标记的代码不执行。希望满足某些条件才执行某些测试用例,否则pytest会跳过运行该测试用例实际常见场景:根据平台不同执行测试、跳…...
华为云云耀云服务器L实例评测|docker 常用操作命令
文章目录 写在前面云耀云服务器L实例与ECS的购买和配置区别 1、管理命令2、帮助命令3、镜像命令4、容器命令4.1 查看容器4.2 创建容器 实例 写在前面 前面讲到了docker环境的安装,这是我们可以直接打开远程连接华为云云耀云服务器L实例,直接连接公网…...
RJ45网络信号浪涌保护器解决方案
RJ45网络信号浪涌保护器是一种用于保护网络设备免受雷击或其他高压电流干扰的装置,它可以有效地吸收和释放信号线路上的过电压,从而避免设备损坏或数据丢失。 RJ45信号浪涌保护器的应用领域和施工方案如下: 地凯科技RJ45网络信号浪涌保护器…...
SoC性能指标ARM内核运算能力
自动驾驶芯片常用的性能评价指标:TOPS,DMIPS,GFLOPS分别说的是啥? TOPS Tera Operation Per Second,表示每秒钟可以进行的操作数量,用于衡量自动驾驶的算力。 众所周知,汽车上最常用的传感器是摄像头,而与之对应的计…...
注册小鲸鱼88888专用网站
点击注册充值即可 高效不限速,不限设备 注意这里的地址并没有错,只是你需要想办法正确能进入就行,懂的大佬一定知道用一定的方法访问的。...
在线python编程网页/seo咨询推广
什么是 Visual Paradigm IT项目管理框架? 框架是一组工具,可以帮助您更快,更好地完成工作。IT项目管理框架是一个概念,方法和工具的集合,帮助项目团队实现以下目标。 管理项目生命周期:从项目识别到项目启动…...
做西点网站/社群运营的经典案例
1 制定目的按照项目组所有成员的贡献度,为“项目阶段奖金”的合理分配提供依据。2 奖金池金额及其运作方法奖金总额按月度发放,月奖金总额为:;根据项目运作的进度及客户反馈进行评估,奖罚并施;细则如下。2.…...
百色高端网站建设/优化大师的三大功能
目录知识点总结: Note: 1.创建一个/server/scripts目录,用于存放脚本(命令:mkdir -p /server/scripts) 2.安装软件时,安装路径统一为/usr/local/软件名-版本号 3.安装完软件后,需做软链接&#…...
东营做网站优化的公司/网络营销课程
欢迎来到我的《从源码中学Vue》专题系列文章,更多精彩内容持续更新中,欢迎关注 :)上一章节我们通过源码分析了Vue中的methods对象下的方法是如何挂载到vm下,以及各方法内部的this为何是指向了vm对象。其实在Vue中,还有…...
成都网站建设:思乐科技/优化网站搜索
摘要摘要在机器翻译和作文自动评分领域已经有比较成熟的研究成果,但是在人工翻译评分领域的研究还不够深入。在同为主观题的作文评分中,采用多元线性回归方法建立文本特征和分数之间的方程。但是人工翻译评分选取的文本特征之间关系更为复杂,…...
好网站/得物app的网络营销分析论文
作者:satuen在单板计算机的世界里,毫无疑问,树莓派必有一席之地。从性能、大小、性价比及生态发展各方面综合来说,树莓派可以称得上是王者。这样说是有理由的。在林林总总的SBC产品中,有些产品鼓吹性能、有些产品宣扬易…...