【Linux】进程信号全攻略(一)

🌈 个人主页:Zfox_
🔥 系列专栏:Linux

目录
- 一:🔥 信号的概念
- 二:🔥 信号产生的方式
- 🦋 使用键盘
- 🦋 系统调用函数
- 🦋 软件条件
- 🦋 进程异常
- 三:🔥 关于term和core
- 四:🔥 信号处理
- 🦋 信号处理的三种方式
- 五:🔥 信号保存
- 🦋 信号其他相关常见概念
- 🦋 在内核中的表示
- 六:🔥 信号处理
- 🦋 信号集sigset_t
- 🦋 信号集操作函数
- 🦋 sigprocmask(操作block表的函数)
- 🦋 sigpending
- 🦋 代码样例
- 七:🔥 共勉
一:🔥 信号的概念
🦁 信号是 Linux 系统提供的一种向指定进程发送特定事件的方式,进程会对信号进行识别和处理。
信号的产生是异步的,即一个进程不知道自己何时会收到信号,在收到信号之前进程只能一直在处理自己的任务
- 使用
kill -l
指令查看信号(1-30号信号为普通信号,31-64号信号为实时信号)
root@hcss-ecs-a9ee:~/code/linux/112# kill -l1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
🍉 具体的信号采取的动作和详细信息可查看:
man 7 signal
进程内核数据结构中有位图,例如 uint32_t signalbits。一共32位,除去0号位,刚好对应1-32号普通信号
0000 0000 0000 0000 0000 0000 0000 0000
例如给进程发送2号信号,则修改位图为:0000 0000 0000 0000 0000 0000 0000 0100
所以给进程发送信号,本质是修改进程内核数据结构中的位图数据
二:🔥 信号产生的方式
🦋 使用键盘
例如 ctrl+c 产生是2号信号(终止进程)、ctrl+\ 产生的是3号信号(终止进程)
注意:
- Ctrl-C 产生的信号只能发给前台进程。一个命令后面加个 & 可以放到后台运行, 这样 Shell 不必等待进程结束就可以接受新的命令, 启动新的进程。
- Shell 可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像 Ctrl-C 这种控制键产生的信号。
- 前台进程在运行过程中用户随时可能按下 Ctrl-C 而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到 SIGINT 信号而终止,所以信号相对于进程的控制流程来说是异步(Asynchronous) 的
🦋 系统调用函数
(1) kill 函数,用于向指定进程发送信号
#include <sys/types.h>
#include <signal.h>int kill(pid_t pid, int sig);
pid
:指定进程pid,如果 pid 是负数,信号将被发送到与 pid 的绝对值相同的进程组中的所有进程。sig
:指定的信号编号
返回值:成功返回 0,失败返回 -1 并设置 errno
#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <signal.h>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);return 0;
}
(2) raise 函数,向自己发送信号
#include <signal.h>int raise(int sig);
sig
:发送给当前进程的信号编号
返回值:成功返回 0,失败返回 -1并设置 errno
(3) abort 函数,立即终止当前进程(本质发送的是6号信号SIGABRT)
#include <stdlib.h>void abort(void);
6号信号SIGABRT可以被自定义捕捉处理,但是捕捉后仍然会立即退出进程,比较特殊
9号信号 SIGKILL 无法被捕捉,否则如果所有的信号都被捕捉,那么进程将无法退出
🦋 软件条件
\qquad 🦁 使用管道通信时,当读端关闭,但是写端一直写,操作系统就会给写端进程发送13号信号SIGPIPE,终止进程。SIGPIPE 就是一种由软件条件产生的信号。
现在要介绍的是 alarm 函数
#include <unistd.h>unsigned int alarm(unsigned int seconds);
alarm
函数用于设置一个定时器,在指定时间后向进程发送14号信号SIGALRM,终止进程seconds
:指定定时器的时间,单位为秒。如果这个值是 0,则会取消之前设置的闹钟- 返回值:alarm 函数返回自上次调用的 alarm 闹钟剩余的秒数。如果之前没有设置定时器,或者定时器已经触发,返回 0。
#include <iostream>
#include <signal.h>
#include <unistd.h>int main()
{alarm(1);//1秒后终止进程int cnt = 0;while(true){std::cout << cnt << std::endl;++cnt;}return 0;
}
🦋 进程异常
当进程进行非法操作、访问等就会崩溃,操作系统就给崩溃的进程发送相应的信号终止进程
- 例如进程将0作为除数时,进程崩溃,操作系统会给进程发送8号信号 SIGFPE,终止进程
root@hcss-ecs-a9ee:~/code/linux/112/lesson24/3.signal/work# ./process
Floating point exception
- 例如进程中进行野指针访问时,进程崩溃,操作系统会给进程发送11号信号 SIGSEGV,终止进程
root@hcss-ecs-a9ee:~/code/linux/112/lesson24/3.signal/work# ./process
Segmentation fault
收到这些信号,进程必须退出吗?不是,可以捕捉以上的异常信号,但是我们推荐终止进程,为什么呢?
- 除0问题
关于进程中的计算问题,一般都是交由 cpu 完成的,在计算的过程中,难免会出现错误的计算,比如说除0,那么 cpu 又是如何知道的呢?
🐮 这就要提到 cpu 中的寄存器了,cpu 中是有很多的寄存器的,其中有一个寄存器:EFLAGS 寄存器(状态寄存器)。该寄存器中有很多状态标志:这些标志表示了算术和逻辑操作的结果,如溢出(OF)、符号(SF)、零(ZF)、进位(CF)、辅助进位(AF)和奇偶校验(PF)。
除 0 操作就会触发溢出,就会标定出来运算在 cpu 内部出错了。OS 是软硬件资源的管理者!OS 就会处理这种硬件问题,向目标进程发送信号,默认终止进程。
我们要知道 cup 内部是只有一套寄存器的,
寄存器中的数据是属于每一个进程的,是需要对进程上下文进行保存和恢复的。
如果进程因为除0操作而被操作系统标记为异常状态,但没有被终止,那么它可能会被挂起,等待操作系统的进一步处理。
当操作系统决定重新调度这个进程时,会进行上下文切换,即将当前进程的上下文保存到其PCB(进程控制块)中,并加载异常进程的上下文到CPU寄存器中。
上下文切换是一个相对耗时的过程,包括保存和恢复寄存器、堆栈等信息。当切换回这个进程的时候,溢出标志位的错误信息同样会被恢复,会频繁的导致除0异常而触发上下文切换,会大大增加系统的开销。
为什么推荐呢?因为要释放进程上下文的数据,包括溢出标志数据或其他的异常数据。
- 空指针解引用(野指针)问题
这个问题就与页表,MMU及CR2,CR3寄存器有关联了。
🐮 MMU 和 页表 是操作系统实现虚拟内存管理和内存保护的关键机制,它们通过虚拟地址到物理地址的转换来确保程序的正确运行和内存安全。CR2 和 CR3 寄存器在内存管理和错误处理中扮演着重要角色。CR3 寄存器用于切换不同进程的页表,而 CR2 寄存器则用于存储引起页错误的虚拟地址,帮助操作系统定位和处理错误。
CR2 寄存器用于存储引起页错误的线性地址(即虚拟地址)。当 MMU 无法找到一个虚拟地址对应的物理地址时(例如,解引用空指针或野指针),会触发一个页错误(page fault)。此时,CPU会将引起页错误的虚拟地址保存到 CR2 寄存器中,并产生一个异常,此时就会向进程发送11号信号。
三:🔥 关于term和core
term 和 core 是某些信号默认动作的一种表示。它们之间的区别如下:
默认动作:
-
term
:这是“terminate”的缩写,表示信号的默认动作是终止进程。例如,-- SIGTERM(编号15)信号的默认操作就是请求进程正常退出。这给了进程一个机会来清理并正常终止。 -
core
:这个动作表示在终止进程的同时,还会生成一个core dump
文件。这个文件包含了进程在内存中的信息,通常用于调试。例如,SIGQUIT(编号3)和 SIGSEGV(编号11)等信号的默认动作就是终止进程并生成 core dump。
文件生成:
- 当一个进程因某个信号而 term(终止)时,通常不会生成额外的文件。
- 但当进程因某个信号而 core(终止并
核心转储
,这个动作在云服务器下是被默认关掉的)时,会生成一个 core dump 文件。这个文件包含了进程在内存中的状态信息,对于程序员来说是非常有用的调试工具。
使用场景:
- term 动作通常用于请求进程正常退出,比如当你想要优雅地关闭一个服务时。
- core 动作则更常用于在进程崩溃时生成调试信息,帮助程序员找出崩溃的原因。(以gbd为例,先使用gdb打开目标文件,然后将core文件加载进来,就直接可以定位到错误在哪一行)
信号示例:
- SIGTERM(编号15):默认动作为term,即请求进程正常退出。
- SIGQUIT(编号3)和SIGSEGV(编号11):默认动作为core,即终止进程并生成core dump。
当进程退出时,如果core dump为0就表示没有异常退出,如果是1就表示异常退出了。
eg:关于core dump的演示:
如果你是云服务器,那么就需要手动的将core dump功能打开
注意:如果是ubuntu系统的话需要在配置文件/etc/sysctl.conf 后加入如下两行指令
然后在执行一下这条指令 让其生效
sudo sysctl -p
此时就形成了 core_process 文件
四:🔥 信号处理
🦋 信号处理的三种方式
信号处理有三种方式:
-
默认处理(通常为终止、暂停、忽略等)
-
忽略处理
-
自定义处理(信号捕捉)
先介绍信号处理函数:signal
#include <signal.h>typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);
-
signum
:指定信号的编号 -
func
:函数指针,该函数将在接收到sig信号时被调用。这个函数必须接受一个 int 参数(信号编号),并且返回类型为 void。 -
返回值:返回值为一个函数指针,指向之前的信号处理器;如果之前没有信号处理器,则返回 SIG_ERR
(1) 默认处理
如果signal函数的 func 参数为 SIG_DFL,则系统将使用默认的信号处理动作。
指令man 7 signal查看信号的默认处理方式,Action列即为信号的默认处理方式
Core、Term即为进程终止,Stop为进程暂停……
(Core终止进程同时还会形成一个debug文件,Term仅终止进程)
(2) 忽略处理
如果signal函数的 func 参数为 SIG_IGN,则系统将忽略该信号。
将pending表中被忽略的信号置为0
(3) 自定义处理(信号捕捉)
信号自定义处理,其实是对信号进行捕捉,然后让信号执行自定义的方法
信号的捕捉,一次捕捉,一直有效
#include <iostream>
#include <signal.h>
#include <unistd.h>void hander(int sig)
{std::cout << "get a sig: " << sig << std::endl;
}
int main()
{//信号捕捉::signal(2, hander); //当进程收到2号1信号时,执行hander函数while(true){std::cout << "my pid is: " << getpid() << std::endl;::sleep(1);}return 0;a
}
五:🔥 信号保存
🦋 信号其他相关常见概念
- 实际执行信号的处理动作称为
信号递达
(Delivery) - 信号从产生到递达之间的状态,称为
信号未决
(Pending)。 - 进程可以选择阻塞 (Block )某个信号。
- 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
- 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作
🦋 在内核中的表示
信号在内核中的表示示意图
在这个阶段有以下几种情况:
-
信号未决:信号产生后,在未被处理之前,处于未决状态。这意味着信号已经被发送,但目标进程尚未对其作出响应。操作系统会检查目标进程的Pending表,确定哪些信号处于未决状态。(每个进程都有一个Pending位图,用于记录哪些信号处于未决状态。这个位图由32个比特位组成,分别代表32个不同的信号,如果对应的比特位为1,表示该信号已经产生但尚未处理。)
-
信号阻塞:如果目标进程阻塞了某些信号,那么这些信号会保持在未决状态,直到进程解除对这些信号的阻塞。(与Pending位图类似,Block位图用于记录哪些信号被进程阻塞。当信号被阻塞时,对应的比特位会被设置为1。)
-
handler表:是一个函数指针数组,每个下标都是一个信号的执行方式(有31个普通信号,信号的编号就是数组的下标,可以采用信号编号,索引信号处理方法!)如signal函数在进行信号捕捉的时候,其第二个参数就是,提供给handler的
-
如果进程选择阻塞某个信号,操作系统会在block表中设置对应信号的比特位为1。此时,即使信号已经产生(pending表中对应比特位为1),进程也不会立即处理该信号。
-
被阻塞的信号将保持在pending表中,直到进程解除对该信号的阻塞(即block表中对应比特位被重置为0)。
-
注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
信号阻塞和未决的区别
- 信号阻塞(Blocking):是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。它使得系统暂时保留信号留待以后发送。阻塞只是暂时的,通常用于防止信号打断敏感的操作。
- 信号未决(Pending):是一种状态,指的是从信号的产生到信号被处理前的这一段时间。信号产生后,如果未被处理且没有被阻塞,则处于未决状态,等待被处理。
六:🔥 信号处理
🦋 信号集sigset_t
前面我们了解到,每个信号只有一个 bit 的未决标志,非 0 即 1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型
sigset_t
来存储,sigset_t
称为信号集, 这个类型可以表示每个信号的 “有效” 或 “无效” 状态,在阻塞信号集中 “有效” 和 “无效” 的含义是该信号是否被阻塞, 而在未决信号集中 “有效” 和 “无效” 的含义是该信号是否处于未决状态。阻塞信号集也叫做当前进程的信号屏蔽字 (Signal Mask)
,这里的 “屏蔽” 应该理解为阻塞而不是忽略。(该类型只在 Linux 系统上有效,是 Linux 给用户提供的一个用户级的数据类型)
🦋 信号集操作函数
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 清零,表示该信号集不包含任何有效信号。 - 函数
sigfillset
初始化 set 所指向的信号集,使其中所有信号的对应 bit 置位,表示 该信号集的有效信号包括系统支持的所有信号。
注意 : 在使用 sigset_ t 类型的变量之前,一定要调 用 sigemptyset 或 sigfillset 做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用 sigaddset 和 sigdelset 在该信号集中添加或删除某种有效信号。
这四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种 信号,若包含则返回1,不包含则返回0,出错返回-1。
🦋 sigprocmask(操作block表的函数)
调用函数 sigprocmask
可以读取或更改进程的信号屏蔽字(阻塞信号集)
#include <signal.h>int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1
how
:指定对信号屏蔽集的操作方式,有以下几种方式:
SIG_BLOCK
:将 set 所指向的信号集中包含的信号添加到当前的信号屏蔽集中,即信号屏蔽集和set信号集进行逻辑或操作。SIG_UNBLOCK
:将 set 所指向的信号集中包含的信号从当前的信号屏蔽集中删除,即信号屏蔽集和set信号集的补集进行逻辑与操作。SIG_SETMASK
:将 set 的值设定为新的进程信号屏蔽集,即 set 直接对信号屏蔽集进行了赋值操作。set
:指向一个 sigset_t 类型的指针,表示需要修改的信号集合。如果只想读取当前的屏蔽值而不进行修改,可以将其置为 NULL。oldset
:指向一个 sigset_t 类型的指针,用于存储修改前的内核阻塞信号集。如果不关心旧的信号屏蔽集,可以传递 NULL。
🦁 如果 oset 是非空指针, 则读取进程的当前信号屏蔽字通过oset参数传出。如果 set 是非空指针, 则更改进程的信号屏蔽字, 参数 how 指示如何更改。如果 oset 和 set 都是非空指针, 则先将原来的信号屏蔽字备份到 oset里, 然后根据 set 和 how 参数更改信号屏蔽字。假设当前的信号屏蔽字为 mask,下表说明了 how 参数的可选值。
🦋 sigpending
(检查pending信号集,获取当前进程pending位图)
#include <signal.h> int sigpending(sigset_t *set);
- 参数:set 是一个指向 sigset_t 类型的指针,用于存储当前进程的未决信号集合。
- 返回值:函数调用成功时返回 0,失败时返回 -1,并设置 errno 以指示错误原因。
🦋 代码样例
基于上面的操作方法我们来做一个实验:我们把2号信号block对应的位图置为1,那么2号信号就会被屏蔽掉了,此时我们给当前进程发送2号信号,但2号信号已经被屏蔽了,2号信号永远不会递达,发完之后我们再不断的获取当前进程的pending表,我们就能肉眼看见2号信号被pending的效果:验证
1.第一步实现对2号信号的屏蔽
int main()
{sigset_t block_set,old_set;sigemptyset(&block_set);sigemptyset(&old_set);sigaddset(&block_set,SIGINT);//向block_set信号集中添加SIGINT信号(编号为2)。//1.屏蔽2号信号// 1.1 设置进入进程的Block表中sigprocmask(SIG_BLOCK, &block_set, &old_set); // 真正的修改当前进行的内核block表,完成了对2号信号的屏蔽!while(true) sleep(1);
}
当我们运行程序的时候,对进程发送2号信号是没有作用的,因为2号信号此时已经被屏蔽了。
- 下一步我们打印pending表,之后我们给该进程发送2号信号
void PrintPending(sigset_t &pending)
{std::cout << "curr process[" << getpid() << "]pending: ";for (int signo = 31; signo >= 1; signo--){if (sigismember(&pending, signo))//如果存在就返回1{std::cout << 1;}else{std::cout << 0;}}std::cout << "\n";
}
int main()
{sigset_t block_set,old_set;sigemptyset(&block_set);sigemptyset(&old_set);sigaddset(&block_set,SIGINT);//向block_set信号集中添加SIGINT信号(编号为2)。//1.屏蔽2号信号// 1.1 设置进入进程的Block表中sigprocmask(SIG_BLOCK, &block_set, &old_set); // 真正的修改当前进行的内核block表,完成了对2号信号的屏蔽!while(true){//2.获取当前进程的pending信号集sigset_t pending;sigpending(&pending);//3.打印pending信号集PrintfPending(pending);sleep(1);}
}
对该进程发送2号信号,pending表对应位置被置为1
- 解除对2号信号的屏蔽,并且捕捉2号信号,我们来看一下现象:
#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <sys/types.h>
#include <sys/wait.h>void PrintPending(sigset_t &pending)
{std::cout << "curr process[" << getpid() << "]pending: ";for (int signo = 31; signo >= 1; signo--){if (sigismember(&pending, signo))//如果存在就返回1{std::cout << 1;}else{std::cout << 0;}}std::cout << "\n";
}
void handler(int signo)
{std::cout << signo << " 号信号被递达!!!" << std::endl;std::cout << "-------------------------------" << std::endl;sigset_t pending;sigpending(&pending);PrintPending(pending);std::cout << "-------------------------------" << std::endl;
}
int main()
{// 0. 捕捉2号信号signal(2, handler); // 自定义捕捉sigset_t block_set,old_set;sigemptyset(&block_set);sigemptyset(&old_set);sigaddset(&block_set,SIGINT);//向block_set信号集中添加SIGINT信号(编号为2)。//1.屏蔽2号信号// 1.1 设置进入进程的Block表中sigprocmask(SIG_BLOCK, &block_set, &old_set); // 真正的修改当前进行的内核block表,完成了对2号信号的屏蔽!int cnt =10;while(true){//2.获取当前进程的pending信号集sigset_t pending;sigpending(&pending);//3.打印pending信号集PrintPending(pending);//4.解除对2号信号的屏蔽cnt--;if(cnt==0){std::cout << "解除对2号信号的屏蔽!!!" << std::endl;//使用直接重置的方法//我们之前是保存了old_set,老的屏蔽字,直接使用就行了sigprocmask(SIG_SETMASK, &old_set, &block_set);}sleep(1);}
}
我们不难发现,解除屏蔽后,信号会立即递达,pending对应位置由1置为0(这个过程,是在执行handler方法之前完成的,也就是在信号递达之前,位图就由1转为0了)。
七:🔥 共勉
以上就是我对 【Linux】进程信号(一)
的理解,觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~😉
相关文章:

【Linux】进程信号全攻略(一)
🌈 个人主页:Zfox_ 🔥 系列专栏:Linux 目录 一:🔥 信号的概念 二:🔥 信号产生的方式 🦋 使用键盘🦋 系统调用函数🦋 软件条件🦋 进程异…...

linux文件重命名
Linux文件重命名 文件名显示异常问题出在哪里批量改名扩展 文件名显示异常 跑测CTS,linux环境看跑测结果log file显示没问题,倘若windows下看log file名却显示异常,不太方便操作。 问题出在哪里 linux环境下文件名可以显示正常࿰…...

如何选择适合的AWS EC2实例类型
在云计算的世界中,Amazon Web Services(AWS)提供了丰富的服务,其中Elastic Compute Cloud(EC2)是最受欢迎的服务之一。选择合适的EC2实例类型对于确保应用程序的性能和成本效益至关重要。我们九河云通过本文…...

【Uniapp】Uniapp Android原生插件开发指北
前言 在uniapp开发中当HBuilderX中提供的能力无法满足App功能需求,需要通过使用Andorid/iOS原生开发实现时,或者是第三方公司提供的是Android的库,这时候可使用App离线SDK开发原生插件来扩展原生能力。 插件类型有两种,Module模…...

【随手笔记】FLASH-W25Q16(三)
#include "bsp_w25q16.h"/*内部函数声明区*/ static HAL_StatusTypeDef bsp_w25q_Transmit(uint8_t * T_pData, uint16_t T_Size); static HAL_StatusTypeDef bsp_w25q_Receive(uint8_t * R_pData, uint16_t R_Size);/*内部函数定义区*//* 函数参数:1、T_…...

2024软件测试面试热点问题
🍅 点击文末小卡片 ,免费获取软件测试全套资料,资料在手,涨薪更快 大厂面试热点问题 1、测试人员需要何时参加需求分析? 如果条件循序 原则上来说 是越早介入需求分析越好 因为测试人员对需求理解越深刻 对测试工…...

【JAVA】java 企业微信信息推送
前言 JAVA中 将信息 推送到企业微信 // 企微消息推送messageprivate String getMessage(String name, String problemType, String pushResults, Long orderId,java.util.Date submitTime, java.util.Date payTime) {String message "对接方:<font color\…...

介绍一下数组(c基础)(smart 版)
c初期,记住规则,用规则。 我只是介绍规则。(有详细版,这适合smart人看) 数组(同类型) int arr[n] {} ; int 是 元素类型。 int arr[n] {} ; arr为标识符。 {} 集合,元素有次…...

Java项目实战II基于Spring Boot的个人云盘管理系统设计与实现(开发文档+数据库+源码)
目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发,CSDN平台Java领域新星创作者,专注于大学生项目实战开发、讲解和毕业答疑辅导。 一、前言 基于Spring Boot的个人云盘管理系统设计…...

探索数据科学与大数据技术专业本科生的广阔就业前景
随着信息技术的不断发展,数据科学与大数据技术已经成为各大行业的关键推动力。在这样一个数据驱动的时代,越来越多的企业依赖数据来驱动决策、优化运营和创造价值。因此,数据科学与大数据技术专业的本科生在就业市场上具有广阔的前景和多样的…...

微服务架构面试内容整理-Zuul
Zuul 是由 Netflix 开发的一个边缘服务(API 网关),用于动态路由、监控、认证、以及对微服务架构中的请求进行过滤。它在微服务架构中扮演着重要的角色,提供了一种集中管理和控制服务访问的方式。以下是 Zuul 的主要特点、工作原理和使用场景: 主要特点 1. 动态路由: Zuu…...

解决Knife4j 接口界面UI中文乱码问题
1、查看乱码情况 2、修改 编码设置 3、删除 target 文件 项目重新启动 被坑死了...

微服务架构面试内容整理-Sleuth
Spring Cloud Sleuth 是一个分布式追踪工具,用于监控微服务系统中请求的传播情况。它通过在微服务之间传递追踪信息,帮助开发者理解系统的行为,快速定位性能瓶颈和问题。以下是 Sleuth 的主要特点、工作原理和使用场景: 主要特点 …...

Go语言的接口示例
Go语言的接口(interface)是一种轻量级的多态性实现方式,是构建高扩展性、高复用性代码的利器。Go语言的接口非常灵活,不要求显式的实现声明,只要一个类型实现了接口规定的方法,它就可以被视为该接口的实现者。在本篇博客中,我们将通过多个实际示例,探讨Go语言接口的使用…...

【Apache ECharts】<农作物病害发生防治面积>
在vs Code里打开, 实现 1. 首先引入 echarts.min.js 资源 2. 在body部分设一个 div,设置 id 为 main 3. 设置 script 3.1 基于准备好的dom,初始化echarts实例 var myChart echarts.init(document.getElementById(main)); 3.2 指定图表的…...

基于vue3实现的聊天机器人前端(附代码)
<template><div class"container"><!-- 页面头部 --><header><h1>跟它说说话吧!</h1><p>一个活泼的伙伴,为你提供情感支持!</p></header><!-- 聊天容器 --><div c…...

DICOM标准:深入详解DICOM医学影像中的传输语法
引言 DICOM(数字成像和通信医学)标准在医学影像数据交换中扮演着至关重要的角色。其中,*传输语法(Transfer Syntax)是DICOM标准中定义数据编码和传输方式的核心部分。理解传输语法对于确保不同设备和系统之间的互操作性…...

sql server 文件备份恢复
数据库介绍文件组 PRIMARY 文件 lys D:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA\lys.mdf lys_02 D:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA\lys_02.ndf文件组 sec 有2个表(sec_1,sec_2) 文件 …...

Gradle命令编译Android Studio工程项目并签名
文章目录 gradlew命令gradlew编译debug apkgradlew编译release apkapksigner签名apkgradlew注意事项 gradlew命令 gradlew 是一个脚本文件,它允许你在没有全局安装 Gradle 的情况下运行 Gradle 构建。这个脚本在多平台上可用,对于 Windows 系统来说是 g…...

lua入门教程:垃圾回收
Lua的垃圾回收机制是一种自动内存管理方式,用于回收不再被程序访问的对象,从而避免内存泄漏。以下是一个关于Lua垃圾回收机制的详细教程: 一、Lua垃圾回收机制概述 Lua使用自动内存管理,这意味着程序员不需要手动释放内存。Lua的…...

基于前后端分离架构,SaaS云平台与私有云部署的智慧校园源码,java电子班牌源码
基于前后端分离架构,SaaS云平台与私有云部署的智慧校园源码,java电子班牌源码,自主研发,自主版权,上百个学校应用案例,支持二次开发。 在信息技术飞速发展的今天,教育领域也迎来了一场革命性的变…...

知识总结五
一、C深浅拷贝 浅拷贝:只复制对象的成员变量的值,如果成员变量包含指针,则只复制指针值,不复制指针所指向的数据。深拷贝:复制对象的成员变量的值,并且如果成员变量包含指针,则还复制指针所指向…...

一、初识C语言(1)
1.C语言识别的是二进制语言 C语言是一门计算机语言,计算机是硬件,硬件分通电(1)和 未通电(0)两种情况,所以C语言识别的都是0 / 1信号,也就是二进制语言。 2.C语言文件类型以及基本框…...

petty 状态管理库文档
自研 Petty 状态管理库产生背景 petty 是一款适用于 vue2.5以下版本(目前已兼容vue2.5x 以上版本)的状态管理库,能够在 vue 2这种配置项的代码中,去实现类似于 vue3 里的 pinia、React 里的hook的调用形式,用函数式的…...

SpringMVC学习记录(三)之响应数据
SpringMVC学习记录(三)之响应数据 一、页面跳转控制1、快速返回模板视图2、转发和重定向 二、返回JSON数据1、前置准备2、ResponseBody 三、返回静态资源1、静态资源概念2、访问静态资源 /*** TODO: 一个controller的方法是控制层的一个处理器,我们称为h…...

ENSP GVRP动态学习VLAN
手工配置的VLAN称为静态VLAN,通过GVRP协议创建的VLAN称为动态VLAN。 GVRP有三种注册模式,不同的模式对静态VLAN和动态VLAN的处理方式也不同。 GVRP的三种注册模式分别定义如下: Normal模式:允许动态VLAN在端口上进行注册…...

怎么给llama3.2-vision:90b模型进行量化剪枝蒸馏
对 LLaMA 3.2 Vision: 90B 模型进行量化、剪枝和蒸馏,涉及到模型的压缩和优化技术,以减少其计算量和内存占用。以下是实现这些步骤的一般流程: 1. 量化 (Quantization) 量化的目的是减少模型的精度(如从FP32到INT8)&…...

flutter 专题四 Flutter渲染流程
一、 Widget - Element - RenderObject关系 二、 Widget 、Element 、RenderObject 分别表示什么 2.1 Widget Widget描述和配置子树的样子 Widget就是一个个描述文件,这些描述文件在我们进行状态改变时会不断的build。但是对于渲染对象来说,只会使用最…...

刘艳兵-DBA028-您可以在 ORCL1 和 ORCL2 数据库都运行其实例的主机上安装“独立服务器的 Oracle 网格基础结构“。哪两个陈述是正确的?
您可以在 ORCL1 和 ORCL2 数据库都运行其实例的主机上安装"独立服务器的 Oracle 网格基础结构"。哪两个陈述是正确的?(选择两个) A 在完成“用于独立服务器的Oracle Grid Infrastructure”安装后,必须使用crsctl sta…...

前端三件套-css
一、元素选择器 元素选择器:利用标签名称。p,h1-h6...... 行内样式(内联样式):例如<p style"color:red;font-size:50px"> id选择器:针对某一个特定的标签来使用。以#定义。 class(类&a…...