【Linux】信号的产生、保存、捕捉处理 (四种信号产生、核心存储、用户态与内核态、信号集及其操作函数)
文章目录
- 1、什么是信号?
- 2、信号的产生
- 2.1 通过键盘产生信号
- 2.2 通过系统调用产生信号
- 2.3 硬件异常产生的信号
- 2.4 由软件条件产生的信号
- 2.5 进程的核心转储
- 3、信号的保存
- 4、信号的捕捉
- 4.1 用户态和内核态
- 4.2 用户态到内核态的切换
- 4.3 信号捕捉过程
- 5、信号集操作函数以及测试
1、什么是信号?
在生活上
比方说到了的外卖的提醒,这是一种信号。 我们对于这种信号有着自己的处理方式和意识,这就是处理信号。
值得注意的是,信号到来的时候,可能会因为有更加紧急的事,促使我们会先忽略这个信号,并且先保留这个信号。
在处理信号的时候,可能不同的个体会有着不同的处理动作。(比如红绿灯信号,一般人都是红停绿走,但是在一些可能存在的特殊群体下他们会有不一样的动作。)
在计算机上
首先,信号是给进程发的。(比如kill -9 pid)
进程如果需要识别信号,就一定要对信号有认识,并且有处理动作。
当进程收到信号可能有更重要的代码要执行,所以信号不一定会被处理。
那么进程本身就必须有对信号进行保存的能力。
进程捕捉信号再对信号进行处理,一般有三种动作(默认动作,自定义动作,忽略动作)
通过 kill -l 可以看到所有信号,一个信号的数字对应其宏名。
其中 1–31号称为普通信号,34–64号称为实时信号。这里不关注实时信号。
通过man 7 signal 查看signal手册
再通过/Standard signals 可以查看所有信号对应的内容
如果一个信号发给进程,进程应该保存在哪呢?
可以推测:
信号将会保存在进程的task_struct{… unsigned int signal; …}。
通过位图的结构,每一个bit位置代表一种信号,0和1代表是否受到信号,0无,1收到。
发送信号的本质就是修改PCB中signal的位图,
而对应PCB是由操作系统通过数据结构进行管理的,所以如果需要修改信号,就需要通过OS修改。
也就是说信号发送的方式,本质就是OS向目标进程发生信号。
修改OS数据只能通过操作系统接口来修改。
那么操作系统就应该提供发送信号的接口,而kill命令一定是调用了某种系统接口实现的。
下面就来证明这些推测。
2、信号的产生
进程可以通过以下四种方式收到信号。
2.1 通过键盘产生信号
系统提供的发送信号的接口 signal
这个接口用来捕捉信号,也就是当进程收到操作系统发送的sig信号后,将会由原来的默认动作转换成执行func函数中的自定义动作。
如果没有收到信号sig,将不会执行。
参数 sig : 对应信号名
参数 func 对应信号的三个处理动作:
SIG_DFL 默认的信号处理程序。
SIG_IGN 忽视信号。
或者传用户写的对应自定义动作的函数指针。
下面是一个捕捉2号信号和3号信号的例子:
2号信号的默认动作是在用户键盘输入ctrl+c时会退出前台进程。
3号信号的默认动作是在用户键盘输入ctrl+\时会退出前台进程。
#include <iostream>
#include <signal.h>
#include <unistd.h>using std::cout;
using std::endl;void handler(int signal)
{cout << signal << ":自定义处理\n" << endl;exit(0);
}int main()
{signal(2, handler); //2号信号SIGINT ctrl+c 触发 //signal(3, handler); //3号信号SIGQUIT ctrl+\ 触发 while(true){cout << "hello world" << endl;sleep(1);}return 0;
}
2.2 通过系统调用产生信号
操作系统是有能力向目标进程发送信号,但是得由用户来操作。
向一个pid进程发送sig信号。成功返回0,失败返回-1。
kill()可以向任意进程发送任意信号。
可以通过kill调用来模拟kill命令,只要向sig中发送9号信号就完成了kill命令。
//mysignal.cc
#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <cassert>
#include <sys/types.h>using std::cout;
using std::endl;static void Usage(const std::string& proc)
{cout << "\nUsage: " << proc << " signo pid\n" << endl;
}int main(int argc, char* argv[])
{if(argc != 3){Usage(argv[0]);exit(1);}int signo = atoi(argv[1]);pid_t pid = atoi(argv[2]);int n = kill(pid, signo);assert(n == 0);return 0;
}
/
//mytest.cc
#include <iostream>
#include <unistd.h>
#include <sys/types.h>int main()
{while(true){printf("我是一个正在运行的进程, pid: %d\n", getpid());sleep(2);}return 0;
}
raise调用
raise()函数发送一个任意信号给调用者
sig指明信号。
举例
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>int main()
{int cnt = 0;while(cnt++ < 10){printf("cnt : %d\n", cnt);if(cnt == 5) raise(9); //kill(getpid(), sig);}return 0;
}
abort调用
abort向当前进程发送 6) SIGABRT 信号,中止当前进程。
#include <iostream>
#include <stdlib.h>int main()
{int cnt = 0;while(cnt++ < 10){printf("cnt : %d\n", cnt);if(cnt == 5) abort(); //kill(getpid(), SIGABRT)}return 0;
}
关于信号的处理行为的理解:
很多情况,进程收到大部分的信号,都是为了中止进程。
因为信号的不同,代表不同的事件,但结果是可以相同的。
2.3 硬件异常产生的信号
CPU异常产生的信号
#include <iostream>
#include <unistd.h>
#include <signal.h>void catchSig(int signo)
{printf("捕获信号:%d\n", signo);sleep(2);
}int main()
{//捕获 8)SIGFPE 信号//Floating point exceptionsignal(8, catchSig);int n = 10/0; //return 0;
}
当代码执行后,由于除0的错误被操作系统捕获,向该进程发送了8号信号,通过signal捕获后,确认是8号信号,并且发现一个怪事。
为什么捕获信号后,一直打印?
这是因为处理器在处理10/0时,由于除0造成数据溢出,将一个状态寄存器的溢出标记位置为1。
CPU向OS发送运算异常的信息,根据寄存器上下文确定进程,OS就对进程发送了信号。这个信号默认是中止进程,被捕获后改成了打印。
由于进程收到信号后没有退出,因为进程切换,无数次寄存器当再次加载这个进程上下文,就让OS识别到了CPU内部状态寄存器溢出位是1。
MMU异常产生的信号
#include <iostream>
#include <unistd.h>
#include <signal.h>void catchSig(int signo)
{printf("捕获信号:%d\n", signo);exit(0);
}int main()
{signal(11, catchSig);int* p = nullptr;*p = 100; //野指针访问 OS发送 11) SIGSEGV信号return 0;
}
代码中p访问的是虚拟地址空间的地址,虚拟地址映射到页表,再通过MMU(内存管理单元,集成在CPU当中)读取页表地址放入到对应物理地址。
p解引用访问0号地址,MMU因为非法访问的原因发生异常,操作系统识别到异常将11号信号发生给进程。
所以以上就是由硬件自发的让OS给进程发送信号。
OS给进程发送信号虽然结果一样,但是由于种类不同,也能让进程知道发送了何种错误。
2.4 由软件条件产生的信号
在之前管道通信中,如果一个管道只写不读,就会浪费空间。
操作系统面对这种情况就会给进程发送一个SIGPIPE信号。
这种当fds[0]关闭,fds[1]存在的条件判断所造成的信号,就是软件产生的信号。
下面的alarm 函数调用也会产生一个14号信号。
闹钟功能,seconds秒后,向进程发送一次SIGALRM信号。
这个信号默认也是中止进程。
下面看两段代码
#include <iostream>
#include <unistd.h>
#include <signal.h>static int cnt = 0;void catchSig(int signo)
{printf("捕获信号:%d, 最终cnt:%d\n", signo, cnt);exit(0);
}int main()
{signal(14, catchSig);alarm(1); // 14) SIGALRMwhile(true){cnt++;printf("cnt:%d\n", cnt);}return 0;
}
#include <iostream>
#include <unistd.h>
#include <signal.h>static int cnt = 0;void catchSig(int signo)
{printf("捕获信号:%d, 最终cnt:%d\n", signo, cnt);alarm(1);
}int main()
{signal(14, catchSig);alarm(1); // 14) SIGALRMwhile(true){cnt++;}return 0;
}
首先第一段代码,展示了数据通过外设和网络打印是多快。
第二段代码,没有打印数据,单纯反应了cpu的计算力,并且通过两个alarm函数简单的实现了sleep()的功能。
那么为什么alarm函数能看作一个软件条件产生的信号呢?
因为alarm本质就是通过用户层上数据结构实现的,是一种软件。
任何进程都可以使用alarm系统调用在内核中设置闹钟,这么多的闹钟就一定需要被OS进行结构体描述后,通过相应数据结构管理。
(比如通过堆结构来管理闹钟,通过闹钟的里到点最近时间建立最小堆)
2.5 进程的核心转储
下面运行这个代码
很明显,它会因为非法内存引用报错。
#include <iostream>
#include <unistd.h>
#include <signal.h>int main()
{while(true){int arr[10];arr[10000] = 10;}return 0;
}
在之前看信号的标准内容的时候,发现每个信号有着自己的Action,而这其中有Term和Core两种类型。
这两个主要的区别是,core类型的信号在信号中止异常进程后,会在本地文件生成一个核心文件,这个文件在对应时刻存储着进程的有效数据。
Term类型的信号中止进程后就不会有这个步骤。
core file文件在云服务器默认关闭,通过ulimit -a可以看默认限制的资源,看到core file默认是关闭的。
通过ulimit -c 相应size 可以设置core文件。
再次运行程序:
与之前不同的是运行结果多了个(core dumped),意为核心转储
核心转储:当进程出现异常时,进程在对应时刻,将内存中的有效数据转储在磁盘中,也就是下面的core.14626(对应pid)文件中。
通过gdb 调试,输入core-file 对应核心文件名,就可以生成进程具体的异常原因。
看到由于11号信号,发送了段错误。
3、信号的保存
前面介绍了信号产生的四种情况,那么进程在收到信号后具体是怎么保存的呢?
首先了解一些概念
- 进程实际执行信号处理的动作称为信号递达(Delivery)。
- 信号从产生到处理的中间的状态,称为信号未决(Pending)
- 进程可以选择阻塞某个信号。(可以在信号传递之前,选择阻塞这个信号,当信号到来时,阻塞信号)
- 当进程选择阻塞的信号到来时,信号处于未决状态。直到接触对这个信号的阻塞,才执行递达动作。
- 忽略和阻塞不同,阻塞是在执行递达动作前可以选择的,而忽略是在递达动作后可以选择的一种动作。
在进程的PCB中,其实有三种数据结构:
其中:
pending表(未决表): 其作为一个位图结构用来保存收到了哪些信号,通过bit位置表示具体信号,通过1/0来表示是否收到信号。(比如从右到左第一个bit位为1,代表收到了1号信号)
block表(阻塞表): 其作为一个位图结构用来保存阻塞了哪些信号,通过bit位置表示具体信号,通过1/0来表示是否阻塞信号。
handler表: 其作为一个函数指针数组,每个数组下标对应一种信号,通过下标可以调用对应信号的处理函数。(比如通过下标1,调用1号信号的处理函数)
如果一个信号对应pending表位置为1,block表位置为1,说明这个信号被阻塞,处于未决状态。
如果一个信号对应pending表位置为1,block表位置为0,说明这个信号抵达。
如果在信号抵达前,收到了多次信号,Linux下对普通信号只抵达一次,而如果是实时信号,将会放入一个队列中,这里不讨论实时信号。
综上,信号是由PCB保存的,发送信号修改PCB,因为PCB也是操作系统数据,而操作系统只能通过操作系统自身修改,也就是说信号是由操作系统发送的。
操作系统拥有发送信号的能力,但是它无法自己使用这个能力,这就需要用户来运用它的能力,也就是说需要用户进程通过相应系统调用发送信号。
4、信号的捕捉
在之前,我们知道了信号是由进程通过系统调用修改PCB对应数据结构所产生,是由OS发送的,知道了信号如何产生,如何保存,那么信号是如何被进程接收处理的呢?
首先进程收到信号也在没有阻塞信号下,不会立马处理信号,而是在合适的情况下,由内核态到用户态再处理。
那么什么是内核态和用户态呢?
4.1 用户态和内核态
用户态和内核态是两种运行级别。
用户态是最低的级别,处于用户态的进程,会有一些命令的限制,不能访问内核资源和硬件。
内核态是最高的级别,处于内核态的进程,可以访问内核资源以及硬件。
值得注意的是,内核态是可以访问用户态程序的数据,也是在理论上可以访问用户态程序代码的,但是操作系统不允许这种情况发送,因为会出现安全性问题!
如果进程需要通过系统调用访问内核资源或者硬件,就一定需要从用户态切换到内核态。
(比如用户态的进程访问系统内核:waitpid、getpid,访问硬件:printf、fopen、write、read。再比如C++STL中vector的扩容需要访问内存,但是其扩容机制也一定避免了多次调用系统调用。)
系统调用也因此相比普通调用,耗时更多,因此尽量少使用系统调用。
用户态和内核态怎么表示的?
其实进程运行的时候,进程对应上下文被加载到CPU的寄存器中,CPU内有些寄存器指向进程有关的结构,比如PCB,页表等。
其中有一个CR3寄存器,表示当前CPU的运行级别,0代表内核态,3代表用户态。
4.2 用户态到内核态的切换
前面说了,进程如果需要系统调用访问内核资源或硬件,就需要从用户态切换到内核态。
这之中有个问题:
进程又是如何找到对应系统调用的呢?
进程通过用户级页表将虚拟地址空间数据映射到物理内存,每个进程都有自己的虚拟地址空间和自己的用户级页表,这样就能确保进程间的独立性,使得物理内存出现不一样的数据。
当操作系统加载到物理内存的时候,操作系统也为进程准备了一个内核级页表,内核级页表是为了维护在虚拟和物理之间的操作系统的代码而构成的内核级映射表。开机时会将操作系统加载到内存,因此操作系统在物理空间只存在一份,这就决定了内核级页表在内核中只有一份就够了。同时在CPU内也有一个寄存器一直指向这个内核级页表。内核级页表将物理内存中系统代码,在进程地址空间对应的3-4G空间进行映射,所以进程就可以通过内核级页表访问系统调用。
也就是说,在进程运行的时候,对应上下文会加载到寄存器中,这就使得一些寄存器能找到进程的PCB与页表,并且也能找到内核级页表,那么当需要调用系统接口的时候,与动态链接类似就可以直接从虚拟地址空间进行跳转,再通过内核级页表从物理内存找到对应代码,再返回到用户空间。
并且,每一个进程的虚拟地址3-4G空间都共享同一个内核级页表,所以无论进程怎么切换,进程对应3-4G空间是不会变的。因此,进程是可以随意访问系统调用的。
那么用户凭什么能访问内核呢?
其实进程以用户态调用系统接口一开始也是用户态的,Linux在系统调用接口初始位置有一个从用户态到内核态的转换(通过汇编指令int 80进行陷入内核),因此进程能以内核态的身份访问内核资源或硬件资源。
4.3 信号捕捉过程
接着前面 首先进程收到信号也在没有阻塞信号下,不会立马处理信号,而是在合适的情况下,由内核态到用户态再处理。 这也说明,此前是处于内核态的。那么什么时候会进入内核态呢?系统调用和进程切换。
从整个信号捕捉过程来看:
5、信号集操作函数以及测试
前面说的信号保存的数据结构都是内核中的,而在用户层可以通过一些手段访问它们。
比如:
sigset_t是一个信号集,它是一个位图结构,可以表示每个信号的有效和无效状态。在阻塞信号集中有效和无效对应阻塞和未阻塞,在未决信号集中有效和无效对应是否处于未决状态。阻塞信号集也称为当前进程的信号屏蔽字,这里的屏蔽是阻塞的意思而不是忽略。
在用户层有以下函数调用可以用来设置这个信号集:
详细可以通过man 3 手册查看
#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);// 判断信号集里是否有指定信号
除此以外还有以下函数调用可以修改内核相应信号结构:
sigprocmask
sigprocmask针对的是对PCB中block结构(阻塞位图)进行修改。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1
参数部分:
how有三种选择:
SIG_BLOCK 代表在原来的基础上添加set信号集中的信号。
SIG_UNBLOCK 代表在原来的基础上删除set信号集中的信号。
SIG_SETMASK 代表直接将block位图修改成set。
set代表需要传的信号集
oset 代表block位图修改前的信号集
sigpending
sigpending就是将PCB当前的pending位图传给set信号集。
set参数作为一个输出型参数。
#include <signal.h>
int sigpending(sigset_t *set);
知道了上面,就可以做一个小小实验。
大概内容如下:
创建一个阻塞的信号集,可以将2、3号信号添加进去,然后再添加到block位图中,再给进程发送对应信号,打印pending位图,看是否阻塞了该信号。
(并且添加解除阻塞功能,再捕获信号将对应默认处理改成自定义处理)
代码如下:
#include <iostream>
#include <vector>
#include <signal.h>
#include <unistd.h>
using std::cout;
using std::endl;#define MAX_SIGNO 31static std::vector<int> blocks = {2, 3};static void show_pending(const sigset_t& pending)
{for(int signo = MAX_SIGNO; signo > 0; --signo){if(sigismember(&pending, signo)){cout << "1";}else{cout << "0";}}cout << endl;
}static bool pendingIsEmpty(const sigset_t& pending)
{for(int signo = MAX_SIGNO; signo > 0; --signo){if(sigismember(&pending, signo)){return false;}}return true;
}static void myhandler(const int signo)
{cout << signo << " 号信号已经被捕捉" << endl;
}int main()
{//对信号做捕捉 自定义操作for(const int& signo : blocks) signal(signo, myhandler);//1 添加阻塞信号sigset_t block, oblock, pending;//1.1 初始化信号集sigemptyset(&block);sigemptyset(&oblock);sigemptyset(&pending);//1.2 添加阻塞信号到阻塞信号字for(const int& signo : blocks) sigaddset(&block, signo);//1.3 设置阻塞信号 (进入内核 直接将阻塞修改成block信号集)sigprocmask(SIG_SETMASK, &block, &oblock);//2 打印pending信号 显示收到信号但是没有做出处理动作,意味着被阻塞int cnt = 5;while(true){//2.1 接收pending信号集sigpending(&pending);//2.2 打印pending表show_pending(pending);sleep(1); //缓慢打印if(cnt-- == 0){if(pendingIsEmpty(pending)){cout << "未收到任何信号" << endl;break;}else{//2.3 解除信号的阻塞,完成自定义动作后,再次阻塞信号cout << "解除信号的阻塞" << endl;sigprocmask(SIG_SETMASK, &oblock, &block);//解除后信号由内核到用户态执行自定义处理方法sigprocmask(SIG_SETMASK, &block, &oblock);cnt = 5;}}}return 0;
}
实验结果:
本章完~
相关文章:
【Linux】信号的产生、保存、捕捉处理 (四种信号产生、核心存储、用户态与内核态、信号集及其操作函数)
文章目录1、什么是信号?2、信号的产生2.1 通过键盘产生信号2.2 通过系统调用产生信号2.3 硬件异常产生的信号2.4 由软件条件产生的信号2.5 进程的核心转储3、信号的保存4、信号的捕捉4.1 用户态和内核态4.2 用户态到内核态的切换4.3 信号捕捉过程5、信号集操作函数以…...
redis经典五种数据类型及底层实现
目录一、Redis源代码的核心部分1.redis源码在哪里2.src源码包下面该如何看?二、我们平时说redis是字典数据库KV键值对到底是什么1.6大类型说明(粗分)2.6大类型说明3.上帝视角4.Redis定义了redisObject结构体4.1 C语言struct结构体语法简介4.2 字典、KV是什么4.3 red…...
三十而立却被裁,打工人要如何应对职场危机?
又到金三银四就业季,对于部分职场人来说,年龄成为了他们找工作的最大限制。 因为绝大部分企业招聘中层干部以下岗位的时候,都会要求年龄不超过35周岁,再加上每年千万毕业生涌入社会,竞争程度相当激烈,这就导…...
java面试-java基础
char 变量能不能存贮一个中文汉字?为什么? char 变量可以存贮一个汉字,因为 Java 中使用的默认编码是 Unicode ,一个 char 类型占 2 个字节(16 bit),一个汉字是2个字节,所以放一个中…...
Kafka 消息不丢失
Kafka 消息不丢失生产者丢失消费者丢失不丢失配置Kafka 保证消息不丢失:只对已提交的消息 (committed message) 做有限度的持久化保证 已提交的消息:当 n 个 Broker 成功接收到该消息并写入到日志文件后,就告诉生产者该消息已成功提交有限度…...
ASEMI高压MOS管10N65参数,10N65规格,10N65封装
编辑-Z ASEMI高压MOS管10N65参数: 型号:10N65 漏极-源极电压(VDS):650V 栅源电压(VGS):30V 漏极电流(ID):10A 功耗(PDÿ…...
LeetCode-416. 分割等和子集
目录题目分析回溯法动态规划动态规划(压缩)题目来源 416. 分割等和子集 题目分析 这道题目是要找是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。 那么只要找到集合里能够出现 sum / 2 的子集总和,就算是可以分割成两个相同元素和子集了…...
2021年 第12届 蓝桥杯 Java B组 省赛真题详解及小结【第2场省赛 2021.05.09】
一、试题A:求余(本题总分:5 分) 得:5分 本题总分:5 分 【问题描述】 在 C/C/Java/Python 等语言中,使用 % 表示求余,请问 2021%20 的值是多少? 【答案提交】 这是一道结果…...
elasticSearch写入原理
elasticSearch写入原理 最近学习完了es相关的课程整理除了es的核心内容,学习这东西知其然知其所以然,自己按照自己的理解整理了es相关的面试题。先热个身,整理一下es的写入原理,有不对的地方请大家指正。 这些原理的东西我觉得还是…...
第十四届蓝桥杯模拟赛(第三期)Python
1 进制转换 问题描述 请找到一个大于 2022 的最小数,这个数转换成十六进制之后,所有的数位(不含前导 0)都为字母(A 到 F)。 请将这个数的十进制形式作为答案提交。 答案:2730 def ch…...
Pytorch模型参数的保存和加载
目录 一、前言 二、参数保存 三、参数的加载 四、保存和加载整个模型 五、总结 一、前言 在模型训练完成后,我们需要保存模型参数值用于后续的测试过程。由于保存整个模型将耗费大量的存储,故推荐的做法是只保存参数,使用时只需在建好模…...
面试热点题:回溯算法之组合 组合与组合总和 III
什么是回溯算法? 回溯算法也可以叫回溯搜索算法,回溯是递归的"副产品",回溯的本质是穷举,然后选出我们需要的数据,回溯本身不是特别高效的算法,但我们可以通过"剪枝"来优化它。 理解回溯算法 回溯…...
java面试-jvm
JVM JVM 是 java 虚拟机,简单来说就是能执行标准 java 字节码的虚拟计算机 JVM 是如何工作的 首先程序在执行之前先要把 Java 代码(.java)转换成字节码(.class),JVM 通过类加载器(ClassLoade…...
vscode下载与使用
1.vscode下载 官网下载地址:Download Visual Studio Code - Mac, Linux, Windows下载太慢,推荐文章:解决VsCode下载慢问题_vscode下载太慢_迷小圈的博客-CSDN博客下载太慢,推荐下载链接:https://vscode.cdn.azure.cn/s…...
人员摔倒识别预警算法 opencv
人员摔倒识别预警算法通过opencv网络模型技术,人员摔倒识别预警算法能够智能检测现场画面中人员有没有摔倒,无需人为干预可以立刻抓拍告警。OpenCV的全称是Open Source Computer Vision Library,是一个跨平台的计算机视觉处理开源软件库&…...
华为OD机试题 - 火星文计算(JavaScript)| 机考必刷
更多题库,搜索引擎搜 梦想橡皮擦华为OD 👑👑👑 更多华为OD题库,搜 梦想橡皮擦 华为OD 👑👑👑 更多华为机考题库,搜 梦想橡皮擦华为OD 👑👑👑 华为OD机试题 最近更新的博客使用说明本篇题解:火星文计算题目输入输出示例一输入输出说明Code解题思路版权说明…...
AI人工智能 - 初探
1.应用场景 主要用于了解和系统学习AI,从而可以在工作生活中利用AI做一些事。 2.学习/操作 1.文档阅读 下面的内容来自于与chatGPT的对话 2.整理输出 介绍AI 人工智能(Artificial Intelligence,简称AI)是计算机科学中的一个分支&…...
Spring-AOP工作流程
Spring-AOP工作流程 3,AOP工作流程 3.1 AOP工作流程 由于AOP是基于Spring容器管理的bean做的增强,所以整个工作过程需要从Spring加载bean说起: 流程1:Spring容器启动 容器启动就需要去加载bean,哪些类需要被加载呢?需要被增强的类,如:B…...
C51---串口发送指令,控制LED灯亮灭
1.Code: #include "reg52.h" #include "intrins.h" sfr AUXR 0x8E; sbit D5 P3^7; void UartInit(void) //9600bps11.0592MHz { //PCON & 0x7F; //波特率不倍速 AUXR 0x01; SCON 0x50; //8位数据,可变波…...
【Wiki】XWiki数据备份
XWiki为主题使用java开发的开源wiki,官网地址如下: https://www.xwiki.org/xwiki/bin/view/Main/ 目录1、 XWiki升级数据备份1.1、 获取XWiki配置的数据库与持久化目录信息1.2 备份数据库信息1.3 备份持久化目录2、XWiki数据迁移如果一个知识库不能确保数…...
ctk框架开发Qt插件应用示例工程
目录 前言 约定 插件工程pluginApp: 主启动工程StartApp: 效果演示 结语...
spring5源码篇(4)——beanFactoryPostProcessor执行/注解bean的装配
spring-framework 版本:v5.3.19 前面研究了beanDefinition的注册,但也仅仅是注册这一动作。那么在spring容器启动的过程中,是何时/如何装配的?以及装配的bean是如何注入的? (考虑到xml方式基本不用了以及篇…...
masstransit的message几个高级用法
1)问题,Class MessageA 基类,Class MessageB继承自MessageA; 用bus.Publish方法本想把有些消息只发给B队列,结果由于其继承关系A队列也获得了消息; 解决方法用send, Uri uri new Uri(RabbitM…...
漏洞分析丨cve-2012-0003
作者:黑蛋一、漏洞简介这次漏洞属于堆溢出漏洞,他是MIDI文件中存在的堆溢出漏洞。在IE6,IE7,IE8中都存在这个漏洞。而这个漏洞是Winmm.dll中产生的。二、漏洞环境虚拟机调试工具目标软件辅助工具XP-SP3、KaliOD、IDAIE6Windbg组件gflags.exe三…...
rm命令——删除文件或目录
rm命令是英文单词remove的缩写,主要功能是删除文件或目录。 因为删除文件是一个破坏性动作,因此,在使用时需要格外小心,在执行之前一定要再三确认删除的是哪个目录中的什么文件。 rm命令的语法格式如下: rm [选项] …...
【零基础入门学习Python---Python的基本语法使用】
一.Python基本语法使用 Python是一种易学且功能强大的编程语言,具有简洁的语法和广泛的应用领域。在本文中,我们将介绍Python的基本语法使用,以帮助初学者快速入门Python编程。 1.1 注释 Python 支持两种类型的注释:单行注释和多行注释。 单行注释:以 # 符号开头,从 # …...
数据仓库相关概念的解释
数据仓库相关概念的解释 文章目录数据仓库相关概念的解释1 ETL是什么?ETL体系结构2 数据流向何为数仓DW3 ODS 是什么?4 数据仓库层DWDWD 明细层DWD 轻度汇总层(MID或DWB,data warehouse basis)DWS 主题层(D…...
1/4车、1/2车、整车悬架模糊PID控制仿真合集
目录 前言 1. 1/4悬架系统 1.1数学模型 1.2仿真分析 2. 1/2悬架系统 2.1数学模型 2.2仿真模型 2.3仿真分析 3. 整车悬架系统 3.1数学模型 3.2仿真分析 4.总结 前言 前面几篇文章介绍了LQR、SkyHook、H2/H∞、PID控制,接下来会继续介绍滑模、反步法、M…...
Linux性能补丁升级,避免不必要的跨核Wake-Up
导读一个由英特尔发起的、旨在改进Linux内核公平调度程序代码的补丁系列,也看到了来自AMD工程师和其他利益相关者的测试/反馈,并继续进行改进。这个补丁系列的重点是避免在不必要的情况下发生过多的跨核唤醒(Cross-CPU Wake-up)。这样一来,这…...
Spring Cloud Alibaba全家桶(六)——微服务组件Sentinel介绍与使用
前言 本文小新为大家带来 微服务组件Sentinel介绍与使用 相关知识,具体内容包括分布式系统存在的问题,分布式系统问题的解决方案,Sentinel介绍,Sentinel快速开始(包括:API实现Sentinel资源保护,…...
商城网站开发公司排名/收录提交入口
retrun与exit()均可用于函数的返回,但return只是本函数的返回,而exit()则是整个程序的退出。 #include <stdlib.h>int sum(int a, int b);int main(void){int s;printf("test");ssum(2,3);printf("sum%d",s);return 0; }int s…...
河源市网站建设/外链发布平台有哪些
近期学习了C Primer Plus,感觉这本书确实够厚,越到后边就感觉用处不是很多了,而且没有项目支持,对综合的理解差了好多,针对这个问题,我可以多看看资料,多做点题,多了解些,…...
网站在淘宝上做靠谱吗/竞价恶意点击报案
待完成。。。转载于:https://www.cnblogs.com/py-coder/p/10037144.html...
建设部网站 光纤到户/深圳推广系统
localStorage存储的变量可以在同域名的页面里互相访问,在不同域名的网页里是无法访问的,sessionStorage存储的是会话变量,tab关闭或者浏览器关闭之后就会销毁变量。 用http://localhost/1.html和http://www.cyb.com/1.html两个url访问&#…...
网站建设与制作/西地那非
摘要: wget:无法解析主机地址。这就能看出是DNS解析的问题。 wget:无法解析主机地址。这就能看出是DNS解析的问题。 解决办法: 登入root(VPS)。 进入/etc/resolv.conf。 修改内容为下 nameserver 8.8.8.…...
站群网站/怎么做公司网页
uni-app组件之间跳转传对象的方法 传----- data JSON.stringify(data)wx.navigateTo({url: /pages/subscribe/subscribe?data data,})收----- onLoad: function(data) {data data.data.replace(/""/g, "");dataJSON.parse(data)console.log(data)…...