Linux之进程信号(上)

news2024/10/7 13:23:45

文章目录

  • 前言
  • 一、进程信号
  • 二、查看命令kill -l与信号解释man 7 signal
    • 1.kill -l
    • 2.man 7 signal
  • 三、信号的产生
    • 1.按键
      • ctrl + c
      • ctrl + z
      • ctrl + \
    • 2.系统调用
      • kill——向任意进程发送信号
      • raise——进程给自己发送任意信号
      • abort——进程给自己指定的信号(6号信号)
    • 3.硬件异常产生信号
      • 除零,发送8号信号
      • 野指针——发送11号信号
    • 4.软件条件
      • 管道——13号信号SIGPIPE
      • 定时器——4号信号SIGALRM
        • 管理闹钟
    • 小结
  • 四、捕捉信号的方法
    • 1.signal
    • 2.sigaction
  • 总结


前言

生活中有各种各样的信号,比如:闹钟、红绿灯、上下课铃声……我们可以知道信号产生时对应的要做些什么,幼儿园的小朋友也明白红灯停、绿灯行的道理。
但是,人是怎么识别出这些信号的呢?人是只有通过认识,才能产生行为:有人通过教育的手段让我们在大脑里记住了红绿灯属性及其对应行为。
但是,当信号产生时,我们并不是总能及时去处理这个信号。信号的发生是随时的(异步),但是我们去处理信号并不都是即时的。因为,我们在信号来临时可能会有其他更重要的事情要做(优先级更高的事情),所以从信号发生到信号被处理中间会有一个时间窗口,当然我们在未处理这个信号时需要将这个信号记录下来,等能处理时再处理。
当我们处理信号时,处理信号的方式也是有所不同的(不同的信号有不同的处理方式,不同的人对对同一个信号的处理方式也可能不同,相同的人对相同的信号在不同的场景下处理信号方式也可能不同)。处理信号的方式大致分为以下三种:

  1. 默认动作:例如,红灯停,绿灯行等。
  2. 自定义动作:例如,红灯唱歌,绿灯跳舞等。
  3. 忽略动作:例如,早晨闹钟响了,我们默认动作是起床,忽略动作是忽略闹钟继续睡觉。
    那么,进程与人处理信号的方式有什么异同呢?信号又是如何产生的呢?本文我们来了解Linux中的进程信号。

一、进程信号

前言中,我们通过生活中的信号引入了进程中的信号,下面我们简单了解以下进程信号的概念。进程本身是被程序员编写的代码,是属性和逻辑的组合,所以进程处理信号的识别和对应的动作都是程序员所赋予的。

  1. 信号是给进程发送的,那么进程是如何识别信号的? 认识 + 动作
  2. 进程在处理信号的时候有三种动作:默认动作、自定义动作、忽略动作。
  3. 处理信号也被称为信号被捕捉
  4. 如果进程收到信号的时候,有优先级更高的代码需要执行,我们就不能即时的处理信号,因此进程需要有保存信号的能力
  5. 进程是如何保存不能即时处理的信号的?
    信号会被保存在进程的PCB(task_struct)中,我们用比特位来表示信号的编号,比特位的内容表示是否收到对应信号(0表示没收到,1表示收到了)。
  6. 如何理解信号的发送和接收?
    信号的发送和接收,实际上就是改变PCB中的信号位图。PCB是内核维护的数据结构对象,所以PCB的管理者是OS,因此只有OS可以改变PCB中的内容,因此无论我们之后学习到多少种发送信号的方式,本质上都是OS向目标进程发送信号。当然,这也说明了系统必须要提供发送信号、处理信号相关的系统调用。(我们之前使用的kill命令就是一种系统调用)

二、查看命令kill -l与信号解释man 7 signal

1.kill -l

在这里插入图片描述
查看系统定义的信号列表;
每一个信号都有与之对应的编号和宏定义名称,这些宏定义可以在signal.h中找到。

2.man 7 signal

在这里插入图片描述
查看信号详细信息。
Term表示正常结束(OS不会做任何额外工作);
Core表示OS出了终止的工作;
其他的见下文。

三、信号的产生

文件test.c

  1 #include<stdio.h>
  2 int main()
  3 {
  4         int cnt = 0;
  5         while(1)
  6         {       
  7                 printf("hello %d, i am %d\n",cnt++, getpid());
  8                 sleep(1);
  9         }
 10         return 0;
 11 }

1.按键

ctrl + c

ctrl + c:热键,它实际上是个组合键,OS会将它解释为2信号。
进程对3信号的默认行为是终止进程,所以当我们按ctrl + c时当前运行的进程将被直接终止。
在这里插入图片描述
用ctrl + c:
在这里插入图片描述
用kill -2
在这里插入图片描述

ctrl + z

ctrl + z:热键,实际上是20号信号(即,按ctrl + \和kill -20 (进程pid)是一样的)。

ctrl + \

ctrl + \:热键,实际上是3信号。

2.系统调用

用键盘向前台进程发送信号,前台进程会影响shell,Linux规定跟shell交互时只允许有一个前台进程,实际上当我们运行自己的进程时,我们的进程就变成了前台进程,而sbash会被自动切到后台(默认情况下bash也是一个进程)。
当然,除了用键盘向前台进程发送信号外,我们可以用系统调用向进程发送信号。

kill——向任意进程发送信号

在这里插入图片描述
发送信号的能力是OS的,但是有这个能力并不一定有使用这个能力的权利,一般情况下要由用户决定向目标进程发送信号(通过系统提供的系统调用接口来向进程发送信号)。
通过kill与命令行参数相结合:
文件mykill.cc

  1 #include<iostream>
  2 #include<stdio.h>
  3 #include<errno.h>
  4 #include<stdlib.h>
  5 #include<sys/types.h>
  6 #include<signal.h>
  7 using namespace std;
  8 
  9 static void Usage(const string &proc)
 10 {
 11         cout<<"\nUsage:"<<proc<<" pid signo\n"<<endl;
 12 }
 13 
 14 int main(int argc, char* argv[])
 15 {
 16         if(argc != 3)
 17         {
 18                 Usage(argv[0]);
 19                 exit(1);
 20         }
 21         pid_t pid = atoi(argv[1]);
 22         int signo = atoi(argv[2]);
 23         int n = kill(pid, signo);
 24         if(n != 0)
 25         {
 26                 perror("kill");
 27         }
 28         return 0;
 29 }

在这里插入图片描述
kill命令底层实际上就是kill系统调用,信号的发送由用户发起而OS执行的。

raise——进程给自己发送任意信号

在这里插入图片描述
文件mysignal.cc

  1 #include<iostream>
  2 #include<signal.h>
  3 #include<unistd.h>
  4 using namespace std;
  5 int main(int argc, char* argv[])
  6 {
  7         int cnt = 0;
  8         while(cnt <= 10)
  9         {
 10                 sleep(1);
 11                 cout<<cnt++<<endl;
 12                 if(cnt >= 5)
 13                 {
 14                         raise(3);//发送3号信号
 15                 }
 16         }
 17         return 0;
 18 }

在这里插入图片描述

abort——进程给自己指定的信号(6号信号)

在这里插入图片描述
文件mysignal.cc

  1 #include<iostream>
  2 #include<stdlib.h>
  3 #include<signal.h>
  4 #include<unistd.h>
  5 using namespace std;
  6 int main(int argc, char* argv[])
  7 {
  8         int cnt = 0;
  9         while(cnt <= 10)
 10         {
 11                 sleep(1);
 12                 cout<<"cnt:"<<cnt++<<",pid:"<<getpid()<<endl;
 13                 if(cnt >= 5)
 14                 {
 15                         abort();//发送6号信号
 16                 }
 17         }
 18         return 0;
 19 }

在这里插入图片描述
不同的信号代表不同的事件,但是对事件发送后的处理动作是可以一样的。大多数信号处理的默认动作都是终止进程。

3.硬件异常产生信号

信号的产生,不一定非要用户显示的发送,有些情况下,信号会自动在OS内部产生。

除零,发送8号信号

文件test.cc

  1 #include<iostream>
  2 using namespace std;
  3 #include<unistd.h>
  4 int main(int argc, char* argv[])
  5 {
  6         while(true)
  7         {
  8                 cout<<"i am running..."<<endl;
  9                 sleep(1);
 10                 int a = 10;
 11                 int b = 0;
 12                 a /= b;
 13         }
 14         return 0;
 15 }

在这里插入图片描述

  1. 为什么进程除零会终止进程?
    因为除零会导致当前进程收到来自OS的SIGFPE信号。
  2. OS怎么知道应该给当前进程发送8号信号呢?
    因为CPU出现异常,除零错误。
    CPU中由很多寄存器(eax/edx等),执行int a = 10;int b = 0;a /= b;时CPU内除了数据保存,还要保证运行有没有问题。因此CPU内有状态寄存器,状态寄存器可以用来衡量本次运算结果,10/0的结果是无穷大,它会引起状态寄存器溢出标记位用0变为1,CPU就发送了运算异常。OS得知CPU发送运算异常,就要识别异常:状态寄存器的标记位置为1,是由当前进程导致的,因此会向当前进程发送信号,最后就终止了进程。

通过signal接口,将SIGFPE信号自定义捕捉。
文件test.cc

  1 #include<iostream>
  2 using namespace std;
  3 #include<unistd.h>
  4 #include<signal.h>
  5 void catchSig(int signo)
  6 {
  7         cout<<"获取到一个信号,信号编号是:"<<signo<<endl;
  8 }
  9 int main(int argc, char* argv[])
 10 {
 11         signal(SIGFPE, catchSig);//捕捉任意信号
 12         int a = 10;
 13         int b = 0;
 14         a /= b;
 15         while(true)
 16         {
 17                 cout<<"i am running..."<<endl;
 18                 sleep(1);
 19         }
 20         return 0;
 21 }

在这里插入图片描述
通过上面的例子,我们可以知道:收到信号并不一定会引起进程退出。
如果进程没有退出,则还有被调度的可能。CPU内的寄存器只有一份,寄存器内的内容是独立属于当前进程的上下文,一旦出现异常,我们没有能力去修正这个问题,所以当进程被切换时,会有无数次状态寄存器被保存和恢复的过程,每一次恢复的时候都会让OS识别到CPU内的状态寄存器中的溢出标志位为1,所以每一次都会发送8号信号。

野指针——发送11号信号

文件test1.cc

  1 #include<iostream>
  2 using namespace std;
  3 #include<unistd.h>
  4 #include<signal.h>
  5 int main(int argc, char* argv[])
  6 {
  7         while(true)
  8         {
  9                 cout<<"i am running..."<<endl;
 10                 sleep(1);
 11                 int* p;
 12                 *p = 10;
 13         }
 14         return 0;
 15 }

在这里插入图片描述
野指针的使用,导致程序崩溃,进程收到了来自OS的11号信号。
文件test1.cc

  1 #include<iostream>
  2 using namespace std;
  3 #include<unistd.h>
  4 #include<signal.h>
  5 void catchSig(int signo)
  6 {
  7         cout<<"获取到一个信号,信号编号是:"<<signo<<endl;
  8 }
  9 int main(int argc, char* argv[])
 10 {
 11         signal(11, catchSig);//捕捉任意信号
 12         while(true)
 13         {
 14                 cout<<"i am running..."<<endl;
 15                 sleep(1);
 16                 int* p;
 17                 *p = 10;
 18         }
 19         return 0;
 20 }

在这里插入图片描述
OS会给当前进程发送11号信号,11号信号代表非法的内存引用。
OS怎么知道野指针?
访问野指针会导致虚拟地址到物理内存之间转化时对应的MMU报错,进而OS识别到报错,转化成信号。
在这里插入图片描述

4.软件条件

当软件条件被触发时,OS会发送对应的信号。

管道——13号信号SIGPIPE

我们之前在了解管道时,有讲到一种情况:当读端关闭时,OS会立即终止写端。OS是向写端进程发送13号信号,即当管道的读端关闭软件条件触发是,OS会向进程发送13号信号。

定时器——4号信号SIGALRM

定时器软件条件:alarm():设定闹钟。
调用alarm函数可以设定一个闹钟,即告诉内核在seconds秒后给当前进程发送SIGALRM信号,该信号的默认处理动作时终止当前进程。
在这里插入图片描述
该函数的返回值是:0或者之前设定的闹钟事件剩余的秒数。
例如,早上设定的6:30的闹钟,但是在6:20被人吵醒了,想着再睡一会睡到6:40,于是将闹钟设定为20分钟后再响。
于是,重新设定的闹钟为20分钟后响,以前设定的闹钟还剩余的时间为10分钟。如果seconds值为0,表示取消之前设定的闹钟,函数返回值仍然是之前设定的闹钟的剩余秒数。
在这里插入图片描述
这份代码的意义是统计1s左右,我们的计算机可以将数据累积多少次。但,实际上这种方式效率较低,因为打印在屏幕上是需要访问外设的,而外设的运行速度较慢。
如果不进行打印:
文件test.cc

  1 #include<iostream>
  2 using namespace std;
  3 #include<unistd.h>
  4 #include<signal.h>
  5 int cnt = 0;
  6 void catchSig(int signo)
  7 {
  8         cout<<"获取到一个信号,他的编号是:"<<signo<<",在1秒内计算机累加了:"<<cnt<<"次"<<endl;
  9 }
 10 int main()
 11 {
 12         signal(SIGALRM, catchSig);
 13         alarm(1);//软件条件
 14         while(1)
 15         {
 16                 cnt++;
 17         }
 18         return 0;
 19 }

在这里插入图片描述

理解闹钟是软件条件
“闹钟”其实就是用软件实现的,任意一个进程都可以通过alarm系统调用在内核中设置闹钟。OS内可能会存在很多的“闹钟”,因此需要对“闹钟”进行管理:先描述,再组织。因此,在OS内部设置闹钟时,需要为闹钟创建特定的数据结构对象
在这里插入图片描述
OS会周期性检查这些闹钟
curr_timestamp > alarm.when;//超时了,OS会发送SIGALRM ->alarm.p

struct alarm{
	uint64_t when;	//未来到的超时时间
	int type;		//闹钟类型(一次性、周期性)
	task_struct* p;
	stryct alarm* next;
	//…
};

管理闹钟

内核管理闹钟的数据结构是堆:大堆或者小堆。例如,100个闹钟,可以根据100个闹钟的when建小堆,最小的在堆顶。只要堆顶的没有超时,其他的闹钟自然也没有超时,所以只需要检查堆顶即可管理好这100个闹钟。

小结

  1. 上面说的所有产生信号的方式,本质都是由OS来执行发送信号的工作,因为OS是进程的管理者。
  2. 信号是在合适的时间进行处理,如果不是被立即处理,那么该信号就需要被记录下来,记录在进程PCB中
  3. 一个进程在未收到信号之前就知道自己应该如如何对该信号进行处理,这是程序员默认在系统中编写好的。
  4. OS向进程发送信号的本质是修改目标进程PCB中的信号位图。

四、捕捉信号的方法

1.signal

通过signum方法设置回调函数,来设置某一个信号的对应动作
练习:
文件test.cc

  1 #include<iostream>
  2 using namespace std;
  3 #include<sys/types.h>
  4 #include<unistd.h>
  5 #include<signal.h>
  6 void catchSig(int signo)
  7 {
  8         cout<<"获取到一个信号,信号编号是:"<<signo<<endl;
  9 }
 10 int main(int argc, char* argv[])
 11 {
 12         signal(2, catchSig);//捕捉任意信号
 13         while(true)
 14         {
 15                 cout<<"i am running...,my pid = "<<getpid()<<endl;
 16                 sleep(1);
 17         }
 18         return 0;
 19 }

在这里插入图片描述
可以看到此时向进程发送2号信号或者按ctrl + c都能捕捉到信号(之所以在发送信号时没有终止进程,是因为我们将默认动作改为自定义动作,如果想让进程也终止,可以加上exit(0);或者直接kill -9 ,注意killl -9的对应动作是不会被修改的)

2.sigaction

它的作用域和signal一样,对特定的信号设置特定的回调方法。
在这里插入图片描述
一个正在运行的进程,势必会收到大量同类型的信号。那么问题来了,如果收到同类型的信号,但当前进程正在处理相同的信号,此时会出现什么情况?OS是否会运行频繁进行重复的信号提交?
文件test.cc

  1 #include<iostream>
  2 using namespace std;
  3 #include<stdio.h>
  4 #include<sys/types.h>
  5 #include<unistd.h>
  6 #include<signal.h>
  7 void Count(int cnt)
  8 {
  9         while(cnt)
 10         {
 11                 printf("cnt:%2d\r", cnt);
 12                 fflush(stdout);
 13                 cnt--;
 14                 sleep(1);
 15         }
 16         printf("\n");
 17 }
 18 void handler(int signo)
 19 {
 20         cout<<"get a signo:"<<signo<<"正在处理中…"<<endl;
 21         Count(20);
 22 }
 23 int main(int argc, char* argv[])
 24 {
 25         cout<<"i am "<<getpid()<<endl;
 26         struct sigaction act, aact;
 27         act.sa_handler = handler;
 28         act.sa_flags = 0;
 29         sigemptyset(&act.sa_mask);
 30         sigaction(SIGINT, &act, &aact);
 31         while(true) sleep(1);
 32         return 0;
 33 }

在这里插入图片描述

  1. 当我们在传递一个信号的期间,它同类型的信号是无法传递的
    例子中,当前信号正在被捕捉,系统会自动将当前信号加入到进程的信号屏蔽字,即在block表中自动将2号信号屏蔽。
  2. 当系统完成对当前信号的捕捉,会自动解除对该信号的屏蔽
    一般信号被解除屏蔽,如果该信号已经被pending的话,系统会自动传递当前屏蔽信号,否则就不做任何动作。
  3. 进程处理信号的原则是:串行的处理同类型信号,不允许递归式处理。
  4. 特殊的:如果我们在屏蔽了一个信号的同时还想屏蔽另一个信号,则可以像下面的例子:
sigemptyset(&act.sa_mask);当前我们正在处理当前信号的同时,我们还想同时屏蔽另一个信号,例如3号信号,可以用下面的代码
sigaddset(&act.sa_mask, 3);

总结

以上就是今天要讲的内容,本文从现实中的信号引入,介绍了进程信号的部分内容,包括进程信号的基本概念、进程中有什么信号,如何查看进程中的信号、信号是如何产生的、如何捕捉信号(信号的自定义动作)等相关知识。
本文作者目前也是正在学习Linux相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/635257.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

主流解压缩软件有哪些?这四款可以满足你的所有需求

作者&#xff1a;Insist-- 个人主页&#xff1a;insist--个人主页 作者会持续更新网络知识和python基础知识&#xff0c;期待你的关注 目录 第一款&#xff1a;bandizip&#xff08;推荐&#xff09; 第二款&#xff1a;7-zip 第三款&#xff1a;Nanazip 第四款&#xff1a…

Vue中如何进行表单自定义验证

Vue中如何进行表单自定义验证 在Vue应用中&#xff0c;表单验证是非常重要的一部分。Vue提供了一些内置的验证规则&#xff0c;比如required、min、max等。但是有时候我们需要对表单进行自定义验证&#xff0c;以满足特定的业务需求。本文将介绍如何在Vue中进行表单自定义验证…

第十章 数学相关

第十章 数学相关 第一节 集合 真题&#xff08;2010-53&#xff09;-数学相关-集合-画饼集能力-朴素逻辑 53.参加某国际学术研讨会的 60 名学者中&#xff0c;亚裔学者 31 人&#xff0c;博士 33 人&#xff0c;非亚裔学者中无博士学位的 4 人。根据上述陈述&#xff0c;参…

Java16:集合

一&#xff1a;Collecction接口 1.单列集合框架结构 》Collection接口&#xff1a;单列集合&#xff0c;用来存储一个一个的对象 》 List接口&#xff1a;存储有序的&#xff0c;可重复的数据---》动态数组&#xff0c;实现类&#xff1a;ArrayList&#xff0c;LinkedList,…

【深蓝学院】手写VIO第3章--基于优化的 IMU 与视觉信息融合--作业

0. 题目 1. T1 T1.1 绘制阻尼因子曲线 将尝试次数和lambda保存为csv&#xff0c;绘制成曲线如下图 iter, lambda 1, 0.002000 2, 0.008000 3, 0.064000 4, 1.024000 5, 32.768000 6, 2097.152000 7, 699.050667 8, 1398.101333 9, 5592.405333 10, 1864.135111 11, 1242.7567…

【书影观后感 十四】左晖-做难而正确的事

距离上一本完整读完的书《李自成》及据此而作的读后感【书影观后感 十三】甲申三百七十八年祭已经接近一年时间了&#xff0c;最近心血来潮读了李翔的访谈形式系列《详谈》的第一本《左晖-做难而正确的事》。 虽然这本薄薄的书只有171页&#xff0c;访谈的形式也看似比较随意…

因果推断18--估计个体治疗效果:泛化界和算法CRF(个人笔记)

目录 1. 介绍 2. 相关工作 3.估计ITE&#xff1a;误差界 4. 估计ITE的算法 5.实验 5.1. 模拟结果&#xff1a;IHDP 5.2. 现实世界的结果&#xff1a;工作 5.3. 结果 6.结论 参考 英文题目&#xff1a;Estimating individual treatment effect: generalization bounds…

【Python plotly】零基础也能轻松掌握的学习路线与参考资料

Python plotly是一个优秀的数据可视化工具&#xff0c;通过使用Python语言和Plotly的图表支持库&#xff0c;可以轻松地创建交互式和动态图表&#xff0c;Python plotly的可视化效果美观且易于实现。 学习路线&#xff1a; Python基础语法和Numpy、Pandas基础学习 Python是一…

碳排放预测模型 | Python实现基于LSTM长短期记忆神经网络的碳排放预测模型(预测未来发展趋势)

文章目录 效果一览文章概述研究内容环境准备源码设计学习总结参考资料效果一览 文章概述 碳排放预测模型 | Python实现基于LSTM长短期记忆神经网络的碳排放预测模型(预测未来发展趋势) 研究内容 这是数据集的链接:https://github.com/owid/co2-data/blob/master/owid-co2-da…

windows一键安装redis3.2.100

下载 下载地址:https://gitcode.net/zengliguang/windows_redis_offline_install.git 使用git进行进行clone下载 在电脑桌面或者其他文件夹下 &#xff0c;鼠标右键点击 选择git clone &#xff0c;下图中url为下载地址&#xff0c;Directory为本地存储路径&#xff0c;点击…

ORB_SLAM3 闭环检测

ORB SLAM3系统初始化ORB SLAM3 构建FrameORB_SLAM3 单目初始化ORB_SLAM3 双目匹配ORB_SLAM3_IMU预积分理论推导(预积分项)ORB_SLAM3_IMU预积分理论推导(噪声分析)ORB_SLAM3_IMU预积分理论推导(更新)ORB_SLAM3_IMU预积分理论推导(残差)ORB_SLAM3_优化方法 Pose优化ORB_SLAM3 闭环…

【量化交易笔记】8.基于深度学习(LSTM)预测股票价格

前言 前一章节&#xff0c;已作随机森林来预测股票价格&#xff0c;也是一种比较常见的方法&#xff0c;本章基于深度学习算法来处理时间序列&#xff0c;来预测股票未来的价格。LSTM是一种特殊类型的循环神经网络&#xff08;RNN&#xff09;&#xff0c;在自然语言处理和时间…

【C语言之函数栈帧】(动态图—巨细)一文带你了解局部变量随机值及栈区上的函数调用

&#x1f6a9;纸上得来终觉浅&#xff0c; 绝知此事要躬行。 &#x1f31f;主页&#xff1a;June-Frost &#x1f680;专栏&#xff1a;C语言 局部变量为什么是随机值?函数是如何调用的&#xff1f; ✉️ 该篇将使用该编译器&#xff0c;通过介绍栈帧的创建和销毁来深入了解局…

计算机网络开荒3-传输层

文章目录 一、传输层概述1.1 网络层 vs 传输层 二、多路复用 多路分用三、UDP3.1 RDT3.1.1 Rdt3.1.1.1 Rdt1.03.1.1.2 Rdt2.03.1.1.3 Rdt2.13.1.1.4 Rdt2.23.11.5 Rdt 3.0 四、滑动窗口协议4.1 流水线机制4.1.2 滑动窗口协议GBNSR 五、TCP5.1 可靠数据传输5.1.1 RTT和超时 5.2 …

Vue中如何进行图表绘制

Vue中如何进行图表绘制 数据可视化是Web应用中非常重要的一部分&#xff0c;其中图表绘制是其中的重要环节。Vue作为一款流行的前端框架&#xff0c;提供了很多优秀的图表库&#xff0c;以满足不同业务场景下的需求。本文将介绍如何在Vue中进行图表绘制&#xff0c;包括使用Vu…

MM32F3273G8P火龙果开发板MindSDK开发教程4 - 滴嗒定时器Systick的配置

MM32F3273G8P火龙果开发板MindSDK开发教程4 - 滴嗒定时器Systick的配置 1、Systick寄存器 Systick是ARM内核的一个外设&#xff0c;所以在不同芯片的代码上移植比较方便&#xff0c;他总共有4个寄存器&#xff0c; 从Systick定义中可以看到&#xff1a; typedef struct {__I…

一文看懂Java中的锁

阅读本文你可以获得 Synchronized、ReentrantLock、ReentrantReadWriteLock、StampedLock、Condition、Semaphore、CountDownLatch、CyclicBarrier、JMM、Volatile、Happens-Before。 全文共16000字左右&#xff08;包含示例代码&#xff09;、欢迎收藏、在看、转发分批食用 一…

基于粒子群优化算法的配电网光伏储能双层优化配置模型[IEEE33节点](选址定容)(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【MySQL 数据库】11、学习 MySQL 中的【锁】

目录 一、锁的概述与分类二、全局锁&#xff08;全库数据备份&#xff09;三、表级锁(1) 表锁(2) 元数据锁&#xff08;Meta Data Lock&#xff09;(3) 意向锁 四、行级锁(1) 行锁(2) 间隙锁&临键锁 一、锁的概述与分类 锁是计算机协调多个进程或线程并发访问某一资源的机…

Whistle(基于 Node 实现的跨平台抓包调试工具)的使用

Whistle(基于 Node 实现的跨平台抓包调试工具)的使用 基于Node实现的跨平台抓包调试工具 可以劫持网络请求&#xff0c;并进行请求和响应的修改&#xff0c;来提高我们的开发调试效率 1.一键安装(装包/证书) npm i -g whistle && w2 start --init 证书的问题 安装…