Linux操作系统学习(信号处理)

news2024/12/25 0:43:44

文章目录

  • 进程信号
    • 信号的产生方式(信号产生前)
      • 1. 硬件产生
      • 2.调用系统函数向进程发信号
      • 3.软件产生
      • 4.定位进程崩溃的代码(进程异常退出产生信号)
    • 信号保存的方式(信号产生中)
    • 获取pending表&&修改block表
    • 信号的处理(信号处理时)
    • 修改handler函数补充—— sigaction 系统调用

进程信号

信号在生活中无处不在,例如闹钟、红绿灯,快递到达发的短信等等

  • 信号的例子

    例如在网上你买了一个东西就是信号的注册;

    快递员该你打电话要你拿一下快递,就是给你发送了一个信号;

    你收到信号之后,你知道怎么去处理这个信号,在这里就是去拿快递;

    但是你也不一定立马去拿,你可能会等你忙完现在的事在去处理;

    从技术的角度说,平时电脑上按alt+f4就是一种信号,它会关闭当前的窗口,Linux中ctrl+c可以终止进程

信号是进程之间事件异步通知的一种方式,属于软中断

  • 信号的种类

信号的种类可以通过kill -l命令来查看 Linux 系统的信号列表:

如图,其中1-31是普通信号,也是我们在这里要重点学习的信号;34-64是实时信号

信号的产生方式(信号产生前)

1. 硬件产生

​ 例如当我们的程序发生死循环时,按下CTRL+C,就可以终止进程。CTRL+C的本质其实是向进程发送2号信号SIGINT,而SIGINT的默认处理动作是从键盘中断(man 7 signal查看手册)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VTvqddQK-1677869450654)(G:\Typora\图片保存\image-20221214154303667.png)]

​ 那我们可以把信号2的默认处理动作更换成我们自己的验证一下,这里需要用到

sighandler_t signal(int signum, sighandler_t handler);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3kNBz0Xz-1677869450655)(G:\Typora\图片保存\image-20221214155833259.png)]

这里我让收到2号信号后先打印一段话再退出

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void mysignal_2(int signo)
{
  printf("你好signo:%d\n",signo);
  exit(1);
}

int main()
{
  signal(2,mysignal_2);	//相当于一个函数指针,这里填的函数名相当于函数地址
  while(1)
  {
    printf("hello\n");
    sleep(1);
  }
  return 0;
}


可以看到运行结果:按下ctrl+c后,先打印再终止进程

例如野指针问题引起的段错误,只要运行程序就会崩溃,那么下面验证一下

先把1-31的信号处理动作全部换成自定义,之后捕捉信号查看野指针对应的信号

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void handler(int signo)
{
  printf("signo:%d\n",signo);
  exit(1);
}

int main()
{
  int sig = 1;
  for(;sig <= 31; sig++)
  {
    signal(sig,handler);
  }

  while(1)
  {
    int* p = NULL;
    *p = 100;
    printf("hello\n");
    sleep(1);
  }
  return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qxXzYKmv-1677869450655)(G:\Typora\图片保存\image-20221214163925778.png)]

运行结果可知:野指针引起的进程崩溃是收到了11信号

再例如除0错误,把上面的代码修改一下进行验证:

  while(1)
  {
	int i= 0;
    i /= 0;
    printf("hello\n");
    sleep(1);
  }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CIMfELxe-1677869450655)(G:\Typora\图片保存\image-20221214164611138.png)]

可以看到,进程也是直接就崩溃了,是因为收到了8号信号

上述提到的ctrl+c、除0错误、野指针的段错误为什么会产生退出信号呢?

首先要知道,os是硬件的管理者,负责管理好硬件资源监视硬件状态等等,

  • ctrl+c是由键盘发出的,os检测到键盘发出的信号就会向进程发出2号信号;
  • 除0时是在CPU中运算的,CPU中有一些寄存器,会记录运行的状态,除0时的运算会产生异常寄存器就会记录这个状态,OS检测到这个异常状态,就会向进程发送8号信号;
  • 当我们对空指针进行直接赋值时,OS会发现我们的进程地址空间与物理内存的映射对不上,就会向进程发送11号信号

​ 综上这些错误分别体现在键盘、CPU、内存中,所以软件层面上的错误。

​ 也就是体现在硬件或其他软件上,而OS是硬件的管理者,发现硬件出现一些异常,就会反馈回出现异常的代码所在的进程上,进程根据对应的信号进行处理(上面提到的错误都是默认终止进程)


2.调用系统函数向进程发信号

系统提供了一些函数接口,可以手动发送信号

  1. int kill(pid_t pid, int signo);
  • pid 表示目标进程的 pid 。
  • sig 表示要发给目标进程的信号。
  • 成功返回0,失败返回-1
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

static void Usage(const char* proc)
{
  printf("Usage:\n\t %s signo who \n",proc);
}

int main(int argc ,char* argv[])
{
  if(argc != 3)
  {
    Usage(argv[0]);
    return 1;
  }
  int signo = atoi(argv[1]);
  int who = atoi(argv[2]);
  kill(who,signo);
  printf("signo:%d who:%d\n",signo,who);
  return 0;
}

  1. int raise(int signo);

    给自己发送信号,signo:要发送的信号

    int main()
    {
      sleep(1);
      raise(3);
      return 0;
    }
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mgEF0iBy-1677869450655)(G:\Typora\图片保存\image-20221214201136487.png)]

  2. void abort(void);

    给自己发送固定的信号6


3.软件产生

unsigned int alarm(unsigned int seconds);

  • seconds是代表几秒后执行
  • 执行成功返回0,取消定时则返回剩余秒数

​ 调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。

int main()
{
  alarm(30);
  sleep(4);
  int ret = alarm(0);	//取消定时
  printf("%d\n",ret);
  return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dP9bGlnH-1677869450656)(G:\Typora\图片保存\image-20221214202909726.png)]

还有管道通信时,写端退出,读端收到13号信号等等

4.定位进程崩溃的代码(进程异常退出产生信号)

进程退出一般分为三种:

  • 代码运行完毕,结果正常
  • 代码运行完毕,结果不正常
  • 代码异常直接终止

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zOC7U3zf-1677869450656)(G:\Typora\图片保存\image-20221214173606756.png)]

如图:退出码+退出信号+core dump标志位共16位,waitpid函数接口的第二个参数可以获取到并查看退出码和退出信号

(具体用法自行搜索,这里不做介绍)

​ 在Linux中是可以定位到具体崩溃到哪一行的代码的,当一个进程退出的时候它会根据退出的情况置退出码或者退出信号,表明退出的原因,如果必要,OS会设置退出信息中的core dump标志位,并将进程在内存中的数据转存到磁盘上,以便后期调试

云服务器上的core dump是默认关闭的,可以输入 ulimit -c 102400打开core dump,ulimit -a 可以查看

这里用除0错误演示一下:

int main()
{
  while(1)
  {
    int i = 0;
    i /= 0;
    printf("hello\n");
    sleep(1);
  }
  return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dp8esyod-1677869450656)(G:\Typora\图片保存\image-20221214175457061.png)]

​ 在我们运行程序后,除了显示退出原因,还会显示(core dumped),并且多了一个 core.5195文件,之后利用gdp调试打开我的可执行程序(Linux默认是release版本的,需要在编译时加上 -g)

之后输入core-file core.5195加载 core.5195文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6yR75XBw-1677869450656)(G:\Typora\图片保存\image-20221214180814488.png)]

也不是所有信号都会core dump,例如死循环使用ctrl+c的2号信号,或者 kill -9杀掉进程就没有core dump

总结:信号产生的方式有很多,但本质都是由OS向目标进程发送的


信号保存的方式(信号产生中)

在Linux中,一个进程收到信号也不一定马上处理,待当前工作处理完成后寻找合适时机处理,那就需要PCB有保存信号的能力

先来解释三个名词:

  • 实际执行信号的处理动作称为信号递达(delivery)

    信号递达可以是自定义捕捉、默认、忽略

  • 信号从产生到递达之间的状态称为信号未决(Pending)

    本质是这个信号因为一些原因(如优先级低等)被暂时存在了task_struct中

  • 进程可以选择阻塞某个信号(block)

    本质是OS允许进程暂时屏蔽指定的信号,被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作

注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

在进程的task_struct内有三张表,是用来保存信号状态的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vsoqPlGr-1677869450657)(G:\Typora\图片保存\image-20221219151642069.png)]

  • pending 表和 block 表都是位图,而 handler 表是一个函数指针数组,指向的是信号递达时对应的处理动作(默认/忽略/自定义)

  • pending和block位图的比特位的位置代表信号的编号

  • pending位图的内容代表是否收到信号

  • block位图内容代表是否被阻塞(阻塞位图也叫信号屏蔽字)

信号处理过程示例:


只要block为1,那么pending不管是1还是0,该信号都是阻塞状态

有了这三张表,进程就可以识别信号了

因为task_struct是内核的,没有人能写入到内核,只有OS可以,所以信号的产生方式都是由OS统一向PCB发送的,

(不是所有信号都会被屏蔽,例如9号信号)

获取pending表&&修改block表

​ pending表和block表的位图结构,就是由这个sigset_t的类型来存储的。sigset_t是一个位图结构类型,称为信号集;这个类型可以表示每个信号的有效或无效状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决定状态。

​ 由于每个平台的sigset_t信号集实现位图的方法不一定一样,所以不推荐用户对这个信号集直接进行修改信号集,需要通过OS提供的信号集操作函数进行修改。

#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); 
  • int sigemptyset(sigset_t *set):

    初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号

    成功返回0,失败返回-1

  • int sigfillset(sigset_t *set):

    初始化 set ,将其中所有信号对应的比特位置 1 。

    成功返回0,失败返回-1

  • int sigaddset (sigset_t *set, int signo)

    把signo信号添加到 set 集合里,其实就是把signo信号对应的比特位由 0 置 1

    成功返回0,失败返回-1

  • int sigdelset(sigset_t *set, int signo)

    把 signo 信号从 set 集合里删去,其实就是把 signo 信号对应的比特位由 1 置 0

    成功返回0,失败返回-1

  • int sigismember(const sigset_t *set, int signo)

    判定 signo 信号是否在 set 集合里,其实就是判定 signo 信号对应的比特位是否为 1

    包含就返回1,不包含返回0,失败返回-1

有了上面的信号集操作函数后,想要修改block表就需要用到sigprocmask 系统调用

sigprocmask:获取或更改当前进程的信号屏蔽字

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nWmHfr8g-1677869450657)(G:\Typora\图片保存\image-20221219211854058.png)]

  • how:表示要对信号屏蔽字进行的操作类型

    SIG_BLOCK:set 包含了我们希望添加到当前信号屏蔽字的信号。 (mask = mask | set )
    SIG_UNBLOCK:set 包含了我们希望从当前信号屏蔽字中解除阻塞的信号。 (mask = mask & ~set)
    SIG_SETMASK:设置当前信号屏蔽字为 set 。 (mask = set) 常用

  • set:输入型参数,就是我们要设置的信号集的指针

  • oldset:输出型参数,用来获取原信号集(可设置NULL)

  • 返回值:成功返回0,失败返回-1

而想要获取pending表就要用到sigpending 系统调用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3M7fQ57x-1677869450657)(G:\Typora\图片保存\image-20221219212903416.png)]

  • 参数:传出型参数,用于存储进程pending表
  • 返回值:成功返回0,失败返回-1

示例:阻塞2号信号

void test1()
{
  sigset_t iset,oset;
  //1.先清空
  sigemptyset(&iset);
  sigemptyset(&oset);

  //2.给iset指定信号编号
  sigaddset(&iset,2);

  //3.设置屏蔽字
  sigprocmask(SIG_SETMASK,&iset,&oset);

  while(1)
  {
    printf("hello\n");
    sleep(1);
  }
  
}


这时按ctrl+c是无效的,只能通过kill -9来结束进程

示例:先阻塞2号信号,后获取pending表并打印,20秒后放开2号信号

void test2()
{
  //设置信号集
  sigset_t iset,oset;
  sigemptyset(&iset);
  sigemptyset(&oset);
  //初始化信号集
  sigaddset(&iset,2);
  sigprocmask(SIG_SETMASK,&iset,&oset);	//阻塞

  
  sigset_t pending;//用于获取打印用
  int count = 0;
  while(1)
  {
    if(count == 20)
      sigprocmask(SIG_SETMASK,&oset,NULL);		//解除2号的阻塞(更改阻塞的信号集为oset,oset为全0)

    sigemptyset(&pending);		//先清空
    sigpending(&pending);		//在获取
    sleep(1);					//延时1s
    count++;					//计数
    show_pending(&pending);		//打印
  }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EJaJZlWU-1677869450658)(G:\Typora\图片保存\image-20221219213539924.png)]

20s后,2号被取消阻塞,信号递达,进程退出

综上步骤:

想要获取pending表或者更改block表的必备步骤是:

  1. 设置信号集
  2. 初始化信号集

信号的处理(信号处理时)

​ 每个进程都有自己的地址空间和对应的页表,地址空间一般一共4G,3G的用户空间,而剩下的1G空间是内核空间,存储的是OS的数据和代码,且内核空间也有对应的内核页表映射到物理内存上,但是内核页表不管有多少个进程,都只共享一份保证无论进程怎么切换,都能够找到同一个OS

​ 进程是可以看到内核和用户的内容的,但是不一定能够访问,需要有权限来证明进程处于哪种工作模式,在进程里面是有对应的相关数据来标识进程的工作模式的(用户模式 / 内核模式)

  • 想要访问内核数据,就需要切换至内核态
  • 想要访问用户数据,就需要切换至用户态

这个数据会被加载到 CPU 的其中一个寄存器当中(CR3),用于保存当前进程是处于用户态还是处于内核态。

  • 内核态:执行 OS 的代码和数据时所处的状态。OS 的代码的执行全部都是在内核态。
  • 用户态:用户的代码和数据被访问或执行时所处的状态。也就是说我们写的代码全部都是在用户态执行的。

而系统调用就是:将进程切换至内核态去调用系统函数


信号是如何处理的?

​ 信号是被保存在PCB中的pending位图里面,处理的工作分为检测、递达(默认、忽略、自定义),当进程从内核态返回到用户态的时候,进行信号的处理工作

处理的流程大致如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RxIa9Pev-1677869450658)(G:\Typora\图片保存\image-20221220232624326.png)]

抽象图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XAH2ArSt-1677869450658)(G:\Typora\图片保存\image-20221220232732351.png)]

为什么不能由OS直接执行捕捉函数方法?

​ 要知道OS是不相信任何人的,handler方法是用户定义的,内核态只能执行OS的代码数据,所以首先再身份上就不合适。其次因为OS只相信自己,也就是只相信自己的代码和数据是安全的,假如handler的函数方法定义一些恶意代码,那么OS等于以它的权限去执行,造成安全隐患。所以OS要保护好自己

什么时候处理信号?从内核态切换回用户态的时候进行信号检测并处理

​ **当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字。**这样就保证了在处理某个信号时,如果这种信号再次产生,那么它(第二次)会被阻塞到当前处理结束为止

在 Linux 中,如果把进程的某一个普通信号屏蔽了,然后 OS 给这个进程多次发送该信号,该进程只能记住一次,因为记录信号的标记位只有一个比特位。也就是说,在 Linux 中,普通信号是可能会被丢失的。而实时信号不会被丢失,因为内核是以链表队列的形式把所有的实时信号组织起来的,来一个就链一个。(数据结构的差别)


修改handler函数补充—— sigaction 系统调用

和signal函数的功能是一样的,都是修改handler表中的方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c4LNjVSh-1677869450658)(G:\Typora\图片保存\image-20221221002146165.png)]

  • signum 表示要设置的信号
  • act 输入型参数,需填充该结构体里面 signum 的对应处理动作。
  • oldact 输出型参数,若为非空,则带回内含老的处理动作的结构体;若不关心,则可设为 NULL 。
  • 成功返回0,失败返回-1

与信号集sigset_t相关的操作类似

  • sa_handler 代表 signum 的对应处理动作
  • sa_mask 代表在调用信号处理函数时需要额外屏蔽的信号
  • sa_flags 代表选项,我们在这里设为 0
  • sa_sigaction 和 sa_restorer 通常与实时信号相关联,我们在这里不关心

​ 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用 struct sigaction 结构体中的 sa_mask 字段说明这些需要额外屏蔽的信号(mask是信号集,可以用信号集操作函数)

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

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

相关文章

四轮两驱小车(五):蓝牙HC-08通信

前言&#xff1a; 在我没接触蓝牙之前&#xff0c;我觉得蓝牙模块应用起来应该挺麻烦&#xff0c;后来发觉这个蓝牙模块的应用本质无非就是一个串口 蓝牙模块&#xff1a; 这是我从某宝上买到的蓝牙模块HC-08&#xff0c;价格还算可以&#xff0c;而且可以适用于大多数蓝牙调试…

闲人闲谈PS之三十八——混合制生产下WBS-BOM价格发布增强

惯例闲话&#xff1a;最近中《三体》的毒很深&#xff0c;可能是电视剧版确实给闲人这种原著粉带来太多的感动&#xff0c;又一次引发了怀旧的热潮&#xff0c;《我的三体-罗辑传》是每天睡前必刷的视频&#xff0c;结尾BGM太燃了。闲人对其中一句台词感触很深——人类不感谢罗…

taobao.itemprops.get( 获取标准商品类目属性 )

&#xffe5;开放平台基础API不需用户授权 通过设置必要的参数&#xff0c;来获取商品后台标准类目属性&#xff0c;以及这些属性里面详细的属性值prop_values。 公共参数 请求地址: HTTP地址 http://gw.api.taobao.com/router/rest 公共请求参数: 公共响应参数: 请求参数 点…

数据结构:复杂度的练习(笔记)

数据结构&#xff1a;复杂度的练习&#xff08;笔记&#xff09; 例题一&#xff1a; 可以先给数组排序&#xff0c;然后再创建一个i值&#xff0c;让他循环一次一次&#xff0c;遍历这个排序后的数组&#xff0c;但如果用qsort函数进行排序&#xff0c;时间复杂度就和题目要求…

Vue组件进阶(动态组件,组件缓存,组件插槽,具名插槽,作用域插槽)与自定义指令

Vue组件进阶与自定义指令一、Vue组件进阶1.1 动态组件1.2 组件缓存1.3 组件激活和非激活1.4 组件插槽1.5 具名插槽1.6 作用域插槽1.7 作用域插槽使用场景二、自定义指令2.1 自定义指令--注册2.2 自定义指令-传参一、Vue组件进阶 1.1 动态组件 多个组件使用同一个挂载点&#x…

如何打造一款专属于自己的高逼格电脑桌面

作为一名电脑重度使用者&#xff0c;你是否拥有一款属于你自己的高逼格电脑桌面呢&#xff1f;你是不是也像大多数同学一样&#xff0c;会把所有的内容全部都堆积到电脑桌面&#xff0c;不仅找东西困难&#xff0c;由于桌面内容太多还会导致C盘空间不足&#xff0c;影响电脑的反…

Java分布式解决方案(一)

随着互联网的不断发展&#xff0c;互联网企业的业务在飞速变化&#xff0c;推动着系统架构也在不断地发生变化。 如今微服务技术越来越成熟&#xff0c;很多企业都采用微服务架构来支撑内部及对外的业务&#xff0c;尤其是在高 并发大流量的电商业务场景下&#xff0c;微服务…

Linux内核学习笔记——页表的那些事。

目录页表什么时候创建内核页表变化什么时候更新到用户页表源码分析常见问题解答问题一&#xff1a;页表到底是保存在内核空间中还是用户空间中&#xff1f;问题2&#xff1a;页表访问&#xff0c;软件是不是会频繁陷入内核&#xff1f;问题3&#xff1a;内存申请&#xff0c;软…

LaTeX表格自定义行高+自定义列宽+大表格自适应页面宽度

一、自定义行高 默认行高效果 自定义行高效果&#xff1a;看起来更美观、大方些 实现方式&#xff1a;在LaTeX表格中的\begin{table}和\begin{tabular}之间插入命令\renewcommand\arraystretch{1.5}&#xff0c;其中1.5这个数值是可以自定义的&#xff0c;数值越大&#xff0c;…

xmu 离散数学 卢杨班作业详解【8-12章】

文章目录第八章 树23456810第九章46811第十章24567第十一章14571116第十二章131317第八章 树 2 (2) 设有k片树叶 2∗m2∗43∗3k2*m2*43*3k2∗m2∗43∗3k n23kn23kn23k mn−1mn-1mn−1 联立解得k9 T中有9片树叶 3 有三颗非同构的生成树 4 (1) c --abc e–abed f–dgf…

2023.03.05 学习周报

文章目录摘要文献阅读1.题目2.摘要3.介绍4.SAMPLING THE OUTPUT5.LOSS FUNCTION DESIGN5.1 ranking loss: Top1 & BPR5.2 VANISHING GRADIENTS5.3 ranking-max loss fuction5.4 BPR-max with score regularization6.实验7.结论深度学习1.相关性1.1 什么是相关性1.2 协方差1…

套接字实现TCP

套接字 套接字的意义就是客户端与服务器进行双向通信的端点&#xff0c;如果有不理解点上面套接字三字更近距离了解套接字。 网络套接字与客户连接的特定网络有关的服务端口号&#xff0c;这个端口号允许linux进入特定的端口号的连接转到正确的服务器进程。 套接字通信的建立过…

【数据结构与算法】数据结构有哪些?算法有哪些?

1. 算法与数据结构总览图 2.常用的数据结构 2.1.数组&#xff08;Array&#xff09; 数组是一种聚合数据类型&#xff0c;它是将具有相同类型的若干变量有序地组织在一起的集合。数组可以说是最基本的数据结构&#xff0c;在各种编程语言中都有对应。一个数组可以分解为多个数…

k8s篇之Pod 干预与 PDB

文章目录自愿干预和非自愿干预PDBPDB 示例分离集群所有者和应用程序所有者角色如何在集群上执行中断操作自愿干预和非自愿干预 Pod 不会消失&#xff0c;除非有人&#xff08;用户或控制器&#xff09;将其销毁&#xff0c;或者出现了不可避免的硬件或软件系统错误。 我们把这…

Vue+ECharts实现可视化大屏

由于项目需要一个数据大屏页面&#xff0c;所以今天学习了vue结合echarts的图标绘制 首先需要安装ECharts npm install echarts --save因为只是在数据大屏页面绘制图表&#xff0c;所以我们无需把它设置为全局变量。 可以直接在该页面引入echarts&#xff0c;就可以在数据大…

『MyBatis技术内幕』源码调试前提

准备源代码包 下载源代码 3.4.6 版本 https://github.com/mybatis/mybatis-3/releases?page2 通过 idea 导入然后回自动下载所有依赖&#xff0c;根据 3.4.6 版本的 pom.xml 找到依赖的 mybatis-parent 版本 <parent><groupId>org.mybatis</groupId><ar…

《计算机网络:自顶向下方法》学习笔记——第一章:计算机网络和因特网

计网 第一章 计算机网络和因特网 1.1 什么是因特网 回答这个问题有两种方式 其一&#xff0c;我们能够描述因特网的具体构成&#xff0c;即构成因特网的基本硬件和软件组件&#xff1b;其二&#xff0c;我们能够根据为分布式应用提供服务的联网基础设施来描述因特网。 1.1.…

加油站ai视觉识别系统 yolov7

加油站ai视觉识别系统通过yolov7网络模型深度学习&#xff0c;加油站ai视觉识别算法对现场画面中人员打电话抽烟等违规行为&#xff0c;还有现场出现明火烟雾等危险状态。除此之外&#xff0c;模型算法还可以对卸油时灭火器未正确摆放、人员离岗不在现场、卸油过程静电释放时间…

20230304学习笔记

1、Mybatis #{}和${}的区别是什么 a、#{}是预编辑处理、是占位符&#xff0c;${}是字符串拼接符。 b、#{}替换为&#xff1f;号&#xff0c;用PreparedStatement来赋值&#xff0c;${}直接替换变量的值&#xff0c;用Statement赋值。 c、#{}在DBMS中、自动加入单引号&#…

XSS-labs靶场1-13关解法答案

目录 XSS-labs克隆/下载地址: 第一关 解法 第二关 解法 第三关 解法 第四关 解法 第五关 解法 第六关 解法 第七关 解法 第八关 解法 第九关 解法 第十关 解法 第十一关 解法 第十二关 解法 第十三关 解法 从XSS payload 中关于浏览器解码的一些总结 XSS-labs克隆/下载地…