【Linux】信号概念与信号产生

信号概念与信号产生

  • 一、初识信号
    • 1. 信号概念
    • 2. 前台进程和后台进程
    • 3. 认识信号
    • 4. 技术应用角度的信号
  • 二、信号的产生
    • 1. 键盘组合键
    • 2. kill 命令
    • 3. 系统调用
    • 4. 异常
      • (1)观察现象
      • (2)理解本质
    • 5. 软件条件
      • 闹钟

一、初识信号

1. 信号概念

生活中类似信号的概念也不少,例如上课铃声响,就是信号的发出,我们听到上课铃声,就是接收到信号,我们快速回到教室上课就是对信号做出处理。那么我们是怎么认识这些信号的呢?那必定是有人教我们,然后我们记住了。而且我们不单单要认识信号,还要识别信号,知道信号的处理方法!

当信号产生了,我们可能并不立即处理这个信号,我们可能会在合适的时候再去处理,因为我们可能还有更重要的事情要做,所以在信号产生之后,必定有一个时间窗口,在这个时间窗口内,我们必须记住信号的到来!

其实在计算机中,上面中的“我们”其实就是进程!所以进程必须识别并处理信号,并且信号没有产生,也要具备处理信号的能力!所以信号的处理能力,属于进程内置功能的一部分!也就是说,当进程收到了一个信号,进程也可能并不会立即处理这个信号,在合适的时候才会处理。所以,一个进程必须当信号产生,到信号开始被处理,也一定会有时间窗口,也就是说,进程具有临时保存哪些信号已经发生了的能力。

而信号的处理方式有三种:默认动作、忽略、自定义动作;其中我们把自定义动作称为信号的捕捉。

2. 前台进程和后台进程

我们先写一个死循环,如下:

				int main(){while(true){cout << "i am a process, pid: " << getpid() << endl;sleep(1);}return 0;}

当我们运行起来之后,我们可以通过 ctrl + c 杀掉这个进程。而这种进程,当我们运行起来之后,我们的 bash 就不能接收任何指令了,我们把这种进程称为前台进程

我们也可以在运行该程序的时候,在后面加上 &,此时我们运行程序,我们可以输入指令,bash 可以接收我们的指令,也就是说我们还能正常使用 bash 命令行,但是此时我们使用 ctrl + c 就杀不掉该进程了,这种进程我们称为后台进程,如下图:

在这里插入图片描述

Linux中,一次登录中,一个终端一般会配上一个 bash,每一个登录,只允许一个进程是前台进程,但是可以允许多个进程是后台进程。所以我们运行一个程序的时候,默认是在前台运行的,此时 bash 进程就变成后台进程了,所以此时我们运行指令是没有用的。所以前台进程后台进程的区别在于谁来获取键盘输入!

那么我们在运行后台进程的时候,bash 依旧是前台进程,我们输入指令的时候,从上面的结果中我们可以看到,指令已经和打印的内容混合在一起了,此时为什么还能运行我们的指令呢?其实我们输入 ls 的时候,我们是通过键盘输入的,我们键盘输入的消息,会给我们回显出来,虽然回显出来是乱的,但键盘里输入的时候输入依旧是 ls;键盘有键盘的缓冲区,显示器有显示器的缓冲区,只是我们在输入的时候默认给显示器也拷贝了一份,但是这个并不重要,重要的是输入的内容被显示器拿到就行了。

3. 认识信号

实际上,ctrl + c 本质上是被进程解释成为收到了 2号 信号,然后进程就被中断了。我们可以查看Linux中的信号列表,指令为:

				kill -l

在这里插入图片描述

其中我们发现,0号、32号和33号信号是没有的。也就是一共有62个信号;其中我们把 1~31 号信号称为普通信号;往后的称为实时信号,当信号产生必须立即处理就是实时信号;其中我们只学习普通信号。

其实信号本质上就是一个数字,我们看到上面的信号编号中,旁边的大写单词就是它的宏!

那么我们知道,进程收到2号信号的默认动作,就是终止自己。我们可以验证一下,此时我们需要认识一个接口:signal()

			   #include <signal.h>typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);

signal() 就是设置对特定一个进程的自定义处理方法;其中 typedef void (*sighandler_t)(int); 函数指针类型,返回值是空,参数是 int 的类型;那么第一个参数就是信号的编号;handler 就是对当前进程进行 signum 号信号的自定义捕捉。也就是说,当进程收到2号信号,我们就可以指定进程进行我们自定义的方法!如下代码:

				void myhandler(int signum){cout << "process get a signal: " << signum << endl;}int main(){signal(2, myhandler);while(true){cout << "i am a process, pid: " << getpid() << endl;sleep(1);}return 0;}

结果如下:

在这里插入图片描述

所以我们知道了,ctrl + c 的本质就是向进程发送2号信号!并且进程的默认动作已经被我们捕捉了,执行的是我们的自定义的方法。此时我们使用 ctrl + c 杀不掉进程了,所以我们可以使用 kill -9 pid 杀掉。我们也可以在我们自定义方法中加入 exit() 函数,直接退出。

注意,signal() 方法我们只需要设置一次,在该进程生命周期中,往后都有效。在我们的自定义方法中,为什么还要在参数加上信号的编号呢?因为我们可以将所有信号都设置为同一个方法,此时该方法就需要分辨是哪个信号了,所以需要加上信号的编号。

前台进程在运行过程中用户随时可能按下 Ctrl+C 而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到 SIGINT 信号而终止,所以信号相对于进程的控制流程来说是异步的!

4. 技术应用角度的信号

接下来我们了解一下,键盘数据是如何输入给内核的,ctrl + c 又是如何变成信号的。

首先我们需要知道,键盘被按下,肯定是操作系统先知道,因为键盘设备并不能被进程直接访问。那么操作系统怎么知道键盘上有数据了呢?最简单的方法是,操作系统定期去检查键盘上的数据,因为键盘也是文件,所以在操作系统内部会有键盘的描述符、缓冲区等等,所以键盘读取的本质就是将用户层的缓冲区拷贝到内存级的缓冲区中,这就是输入的过程,如下:

在这里插入图片描述

但是操作系统怎么知道键盘上有数据了呢?同理,操作系统怎么知道我们的外设就绪了呢?例如操作系统怎么知道我们的话筒有声音呢?怎么知道摄像头有数据了呢?

曾经我们学过,CPU是不和外设打交道的,因为冯诺依曼体系,但是在控制层面上,CPU是要读取外设的!

首先,CPU 上有许多的引脚集成在主板上,而外设各种设备也是插在主板上的,而键盘在物理上其实是可以间接地和CPU相连的,CPU虽然不在键盘中读数据,但是键盘是可以在硬件上给CPU发送一个硬件中断的!

也就是说,操作系统该干嘛就干嘛,一旦键盘上有数据了,就会通过一些硬件单元将键盘当中的信息发送给 CPU;那么当有其它外设给 CPU 发送中断的时候,CPU怎么知道是哪种设备呢?所以每一种中断都有一个中断号的概念,类似于数字,我们下面假设键盘的中断号为iptnum;其实就是给 CPU 的引脚发送高低电平,由CPU来解释这个中断号是几,所以CPU就得记录下来对应外设的中断号;而在操作系统内,会有一张中断向量表,其实就是一个数组,而中断向量表中都是方法的地址,主要是包括直接访问外设的方法,包括磁盘、键盘、显示器等。所以当CPU 收到了键盘的中断号,操作系统就立马识别到CPU收到了中断号,所以操作系统会立马以中断号为索引,去中断向量表中找对应的方法,然后执行该方法,然而这个方法就是将数据从外设中拷贝到内存级缓冲区的方法!如下图:

在这里插入图片描述

其实我们学习的信号,就是用软件的方式,对进程模拟的硬件中断。

那么如果我们输入的组合键呢?操作系统怎么对 ctrl + c 这样的组合键的数据拷贝到内存级缓冲区呢?其实键盘上的按键是有分类的,有的是用来输入的,有的是用来控制的,比如 ctrl + c、z等,所以操作系统在拷贝数据的时候会进行判断,输入的是数据还是控制,如果是控制,会转化为相应的信号发送给进程!

二、信号的产生

1. 键盘组合键

上面我们已经知道了,我们可以通过 ctrl + c 这样的键盘组合键产生信号。

除了 ctrl + c 外还有 ctrl + \ ,其中 ctrl + \ 就是发送3号信号;我们将3号信号捕捉,如下:

在这里插入图片描述

还有 ctrl + z 可以发送19号信号,让对应的进程暂停。如下:

在这里插入图片描述

如果我们把19号信号捕捉呢?如下:

在这里插入图片描述

如上图,它没有捕捉到19号信号;所以我们得出,不是所有的信号都是可以被 signal 捕捉的。

所以键盘组合键发送信号常见的组合就是以下三个:

  • ctrl + c
  • ctrl + \
  • ctrl + z

我们从上面知道,不是所有的信号都是可以被 signal 捕捉的,所以我们可以尝试将所有信号都捕捉一下,然后使用 kill 指令尝试使用所有的信号编号,观察哪个信号不可以被捕捉;如下代码:

				void myhandler(int signum){cout << "process get a signal: " << signum << endl;}int main(){for(int i = 1; i <= 31; i++){signal(i, myhandler);}while(true){cout << "i am a process, pid: " << getpid() << endl;sleep(1);}return 0;}

最终经过实验我们得出在 1~31 号信号中只有 9号19号 信号不能被捕捉,其它都可以。

2. kill 命令

kill 命令我们就不用介绍了,直接在命令行使用即可,使用格式如下,其中 signo 为信号编号,pid 为进程的 pid

				kill -signo pid

3. 系统调用

接下来我们认识两个可以产生信号的系统调用接口,kill()raise().

  • kill()

      			int kill(pid_t pid, int sig);
    

在这里插入图片描述

其中 pid 为进程的 pidsig 为信号编号。既然有了系统接口,那么我们也可以自己实现一个 kill 指令!参考代码如下:

				int main(int argc, char* argv[]){if(argc != 3) exit(1);int sig = stoi(argv[1]);pid_t pid = stoi(argv[2]);int n = kill(pid, sig);if(n < 0) exit(2);return 0;}
  • raise()

      			int raise(int sig);
    

在这里插入图片描述

raise() 是经过封装的;参数就是信号编号;该函数就是哪个进程调用,就将指定信号发送给哪个进程。所以 raise() 函数相当于 kill(getpid(), sig);

  • abort()

abort() 就是引起一个正常的进程直接终止。abort() 也是经过封装,其实它就是给调用进程发送 6 号信号。但是它内部做了处理,当我们捕捉了 6 号信号,但是调用了 abort() 后,虽然也调用了我们自定义方法,但是它还是会终止进程。

4. 异常

(1)观察现象

异常在我们的程序中也很常见,我们常见的异常有除0错误和越界访问,接下来我们模拟一下这两种场景,分析一下这两种场景。

  • 除0错误

      			int main(){int a = 2, b = 0;cout << "...before " << endl;sleep(2);a /= b;sleep(2);cout << "...after " << endl;return 0;}
    

运行起来后结果如下:

在这里插入图片描述

其实是异常后,进程收到了操作系统发送的信号,终止了进程。我们看一看一下信号表,其实进程是收到了 8 号信号:

在这里插入图片描述

我们也可以使用 man 7 signal 查看信号的详细信息,如下我们找到8号信号,看到它确实是除0错误:

在这里插入图片描述

我们还可以将该信号捕捉进行验证:

				void headler(int signo){cout << "... get a signal: " << signo << endl;sleep(1);}int main(){signal(8, headler);int a = 2, b = 0;cout << "...before " << endl;a /= b;cout << "...after " << endl;return 0;}

结果如下:

在这里插入图片描述

如上,默认的信号处理被我们捕捉后就调用了我们的方法,而且进程不退出了,更重要的是,我们的自定义方法被一直调用,也就是说,信号一直在被触发,这是为什么呢?我们下面再解释。

  • 越界访问(野指针)

我们下面模拟一下越界访问的场景:

				int main(){signal(8, headler);const char* str;cout << "...before " << endl;cout << str[10] << endl;cout << "...after " << endl;return 0;}

结果如下:

在这里插入图片描述

如上图,其实是进程接受到了11号信号而被终止,如下:

在这里插入图片描述

我们也对11号信号捕捉一下,结果如下:

在这里插入图片描述

如上,进程依旧也没有退出。所以进程一旦出异常了,不一定会退出,但是一旦异常退出了,一定是执行了信号所对应的异常处理方法。

(2)理解本质

下面我们进一步理解为什么除0错误和野指针会让进程崩溃。本质上是出现异常后,给对应的进程发信号了,而进程收到信号默认的处理动作就是终止自己,这就是进程崩溃的原因。那么为什么除0错误和野指针会给进程发信号呢?那么根据我们的理解,一定是操作系统识别到了异常问题,然后给进程发信号,那么操作系统是怎么检测到异常问题的呢?

  • 除0错误

当进程执行代码的时候,我们知道,CPU中的eip或者pc指针会保存代码的下一条指令的地址;其中还有一种寄存器叫做状态寄存器,其中有一个比特位表示状态标志位,称为溢出标志位,当我们发生除0的时候,在CPU中会进行计算,但是除0之后数字变成非常大,这个溢出标志位就会溢出了,由0变成1;我们还要知道,整个CPU中的数据其实都属于当前进程的上下文,我们以前也介绍过,也就是虽然CPU只有一个,但是CPU中的数据可以有无数份,所以硬件只有一套,但是进程在被调度期间,CPU里放的都属于当前进程的上下文!所以CPU在进行调度运行的时候,一旦出现异常了,对应的状态寄存器由0置1了,该进程是否出异常与进程切换无关,也就是说,该进程必定是出异常了,但是它不会影响其它进程,因为出异常的数据是属于当前进程的上下文,当该进程被切换时,其它进程的上下文会放上CPU上正常运行!

那么当溢出标志位溢出之后,操作系统需要知道CPU出现溢出了吗?计算出错了吗?需要!操作系统在调度进程时必须要知道已经出现异常了,因为操作系统是硬件的管理者!CPU也是硬件!所以操作系统需要时刻知道CPU的状态寄存器的状态!所以当操作系统发现CPU发生了除0溢出,操作系统就会向进程发送信号,然后进程接收到信号崩溃了!

  • 越界访问(野指针)

我们已经知道,进程的地址空间通过页表映射到物理内存,访问自己的代码和数据。如果我们出现野指针,我们当前访问的时候,通过页表完成对虚拟地址到物理地址的转化,查表的过程并不是操作系统直接来查的,因为对于操作系统来说很费时间,效率低下,所以这个过程是由一个叫做 MMU 的硬件(内存)管理单元完成的,它如今是集成在CPU中的。那么我们也知道,CPU里读到的都是虚拟地址,当CPU通过页表转换野指针的物理地址的时候,会转换失败!CPU中还有一个寄存器,当CPU进行对虚拟到物理地址的转换时,当发生转换失败了,它会把转换失败的虚拟地址放到该寄存器中。当转换失败时 MMU 也会发生报错,硬件报错会被操作系统识别到,因为不同种类的CPU报错信息,所以操作系统可以识别是哪种错误,所以此时操作系统就会发送对应的信号给进程!

所以我们捕捉了信号之后,没有退出,为什么会一直死循环不退出呢?因为至始至终,进程引发了硬件异常问题,也没有修正问题,所以硬件异常一直存在,随着进程被调度,上下文错误也一直存在,所以操作系统一直检测到有这个异常,就一直给该进程发信号,而我们也一直在捕捉这个信号没有处理,所以该进程才不会退出!

5. 软件条件

那么异常只会由硬件产生吗?不一定,比如我们之前学的管道,当读端关闭后,写端一直在写入的时候,操作系统就会因为效率问题关掉写端,并给写端发送 SIGPIPE 13号信号,这就算一种软件异常。

闹钟

其实软件上不仅仅是可以出异常,也可以出一些特殊事件,我们把这些特殊事件称为软件条件,下面我们介绍一种特殊事件 - - - 闹钟。我们可以给进程设置闹钟,闹钟响了,就可以给进程触发对应的条件,执行对应的动作,这个就称为软件条件。

我们可以看看闹钟的系统调用,alarm()

在这里插入图片描述

alarm() 就是给进程设定一个闹钟,一旦闹钟响了,就会给进程发送信号。参数就是我们设定的时间,单位为秒。那么 alarm() 发送的信号是信号编号中的 SIGALRM 14号信号。

其中返回值我们要理解一下,当我们设定好闹钟,我们可能会提前醒来,那么进程也是一样,当进程被提前发送了14号信号,就相当于提前醒来,那么返回值就是上一次设定闹钟的剩余时间。

假设我们设定一个5秒的闹钟,如下:

				int main(){int n = alarm(5);while(1){cout << "i am a process..." << endl;sleep(1);}return 0;}

在这里插入图片描述

我们可以捕捉该信号验证一下,注意我们上面只设定了一次闹钟,一旦响过之后就不会再响了,所以我们下面验证的时候再设定每隔五秒响一次,如下:

				void headler(int signo){cout << "... get a signal: " << signo << endl;int n = alarm(5);}int main(){int n = alarm(5);signal(14, headler);while(1){cout << "i am a process..." << endl;sleep(1);}return 0;}

在这里插入图片描述

其中我们查看详细的信号信息的时候,如下图,发现闹钟在 Action 这一列中是 Term

在这里插入图片描述

而有些信号却是 Core,下面我们说一下这两个的区别。我们在以前学习进程控制的时候,进程在正常终止的时候,它会有自己的退出状态,也就是wait时,获取的 status 参数,这个 status 一共是32个比特位,我们只使用低十六位,其中我们通过次低八位,用来表示进程退出时的退出码;而一旦异常了会收到退出信号,退出信号是低七位比特位;而还有一位是 core dump,我们并没有介绍,而这个字段就是当进程在终止的时候,这个标志位只有一个比特位,为0或者1,它是用来表示进程是 Term 这种终止方式还是 Core 这种终止方式。

在这里插入图片描述

下面我们获取一下验证一下:

				int main(){pid_t id = fork();// childif(id == 0){int cnt = 50;while(cnt--){cout << "i am a child process, pid: " << getpid() << endl;sleep(1);}exit(0);}// fatherint status = 0;pid_t rid = waitpid(id, &status, 0);if(rid == id){cout << "child quit info, rid: " << rid << ", exit core: " << ((status >> 8) & 0xFF) << ", exit signal: " << (status & 0x7F) << ", core dump: " << ((status >> 7) & 1) << endl;}return 0;}

下面我们使用 2 号信号杀掉子进程,因为 2 号信号是 Term 的终止方式:

在这里插入图片描述

那么我们看到 core dump 是0;接下来我们使用 8 号信号杀掉子进程,因为 8 号信号是以 Core 方式终止进程的:

在这里插入图片描述

但是我们发现,core dump 也是0,这是为什么呢?这是因为默认云服务器上的 core 功能是被关闭的!虚拟机是默认没有关闭的。我们可以使用指令 ulimit -a 查看系统中一些标准的配置,其中有一个叫做 core file size 的选项,它默认是0的:

在这里插入图片描述

我们也可以使用 ulimit -c 直接查看它,这就是 core 功能:

在这里插入图片描述

我们可以使用 ulimit -c size 设置它,如下:

在这里插入图片描述

如上,core file size 的大小就被我们设置成 1024 了;

此时当我们再次测试上面的结果的时候,我们使用 2 号信号杀掉子进程 core dump 还是 0,但是使用 8 号信号杀掉子进程的时候 core dump 就变成了 1. 如下:

在这里插入图片描述

而且我们在当前目录下多了一个文件!如下:

在这里插入图片描述

所以我们得出结论,打开系统中的 core dump 功能,一旦进程异常退出,操作系统会将进程在内存中的运行信息,给我们 dump(转储) 到进程的当前目录!形成的 core.pid 文件的过程就是核心转储

那么为什么要进行核心转储呢?其实当发生核心转储时,一定发生了运行时错误,当发生了运行时错误,我们肯定最想知道发生了什么错误,而且更想知道代码在哪一行出错了!所以这个 core.pid 可以告诉我们代码哪一行出错了!我们先在 makefile 中加上 -g 选项,让该程序可以被调试。然后我们测试一下,使用除0错误的代码测试:

				int main(){int a = 2, b = 0;cout << "...before " << endl;a /= b;cout << "...after " << endl;return 0;}

此时我们再运行程序,发现这次报错中后面多了个括号,表示当前已经被核心转储了:

在这里插入图片描述

那么接下来我们想知道哪一行出错,就可以直接开始调试,再输入 core-file core.pid 直接将我们的 core.pid 文件导进来即可,如下:

在这里插入图片描述

所以 core.pid 就是直接复现问题之后,直接定位到出错行,这种先运行,再 core-file 的我们称为事后调试。

那么云服务器上为什么要默认关闭 core dump 呢?我们可以看到形成的 core.pid 相对于其它文件非常大,而且我们的代码量还不大,如下:

在这里插入图片描述

但是当在服务器中,服务器挂掉后,会自动重启,但是如果一个服务器有问题,一启动就挂,又重启,那么一直重复的话,如果 core dump 打开,那么磁盘就有可能被 core.pid 文件打满了,此时的影响就更大了!所以 core dump 是默认被关闭的。

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

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

相关文章

任务管理软件的实用价值及优选推荐:提升工作效率的利器

任务管理软件是一种用于组织任务、将任务分配给个人并监控其进展的软件。该软件可以帮助确保任务在预算内按时完成。它在协同工作环境中特别有用&#xff0c;在这种环境中多人在处理需要跟踪和监视的任务。无论是初创公司、中小型企业还是大型组织&#xff0c;都可以从任务管理…

【Linux】make和Makefile

目录 make和Makefile make和Makefile 我们使用vim编辑器的时候&#xff0c;在一个文件里写完代码要进行编译&#xff0c;要自己输入编译的指令。有没有一种可以进行自动化编译的方法——makefile文件&#xff0c;它可以指定具体的编译操作&#xff0c;写好makefile文件&#x…

电磁兼容故障整改-辐射发射超标

设备的辐射于扰发射超标有两种可能:一种是设备外壳的屏蔽性能不完善;另一种是射频干扰经由电源线和其他线缆逸出。判断方法是拔掉不必要的电线和电源插头&#xff0c;或者将电缆长度减小至最短&#xff0c;继续做试验&#xff0c;如果没有任何改善迹象&#xff0c;则应怀疑是设…

Peter算法小课堂—单调队列

祝大家新年快乐&#xff01; 今天这一次有点简单。 单调队列有两个要点&#xff0c;一个是单调&#xff0c;另一个就是我们的队列。 听到队列&#xff0c;我相信大家一定会想到它的好朋友BFS吧。但是……今天……可……没……那么……简单哦。 西佳佳偶像天团1 题目描述 …

滑动窗口的最大值

题目 239. 滑动窗口最大值 - 力扣&#xff08;LeetCode&#xff09; 思路 使用一个队列充当不断滑动的窗口&#xff0c;每次滑动记录其中的最大值&#xff1a; 如何在 O(1) 时间计算最大值&#xff0c;只需要一个特殊的数据结构「单调队列」&#xff0c;push 方法依然在队尾添…

分布式搜索引擎 elasticsearch

分布式搜索引擎 elasticsearch 第一部分 1.初识elasticsearch 1.1.了解ES 1.1.1.elasticsearch的作用 elasticsearch是一款非常强大的开源搜索引擎&#xff0c;具备非常多强大功能&#xff0c;可以帮助我们从海量数据中快速找到需要的内容 例如&#xff1a; 在GitHub搜索…

【EAI 016】VIMA: General Robot Manipulation with Multimodal Prompts

论文标题&#xff1a;VIMA: General Robot Manipulation with Multimodal Prompts 论文作者&#xff1a;Yunfan Jiang, Agrim Gupta, Zichen Zhang, Guanzhi Wang, Yongqiang Dou, Yanjun Chen, Li Fei-Fei, Anima Anandkumar, Yuke Zhu, Linxi Fan 作者单位&#xff1a;Stanfo…

【Spring源码分析】Spring的启动流程源码解析

阅读此需阅读下面这些博客先【Spring源码分析】Bean的元数据和一些Spring的工具【Spring源码分析】BeanFactory系列接口解读【Spring源码分析】执行流程之非懒加载单例Bean的实例化逻辑【Spring源码分析】从源码角度去熟悉依赖注入&#xff08;一&#xff09;【Spring源码分析】…

2 月 7 日算法练习- 数据结构-并查集

并查集 并查集是一种图形数据结构&#xff0c;用于存储图中结点的连通关系。 每个结点有一个父亲&#xff0c;可以理解为“一只伸出去的手”&#xff0c;会指向另外一个点&#xff0c;初始时指向自己。 一个点的根节点是该点的父亲的父亲的的父亲&#xff0c;直到某个点的父亲…

【NodeJS】006- API模块与会话控制介绍d

1.简介 1.1 接口是什么 接口是 前后端通信的桥梁 简单理解&#xff1a;一个接口就是 服务中的一个路由规则 &#xff0c;根据请求响应结果 接口的英文单词是 API (Application Program Interface)&#xff0c;所以有时也称之为 API 接口 这里的接口指的是『数据接口』&#…

游戏服务器租用价格表_TOP3费用对比

游戏服务器租用多少钱一年&#xff1f;1个月游戏服务器费用多少&#xff1f;阿里云游戏服务器26元1个月、腾讯云游戏服务器32元&#xff0c;华为云26元&#xff0c;游戏服务器配置从4核16G、4核32G、8核32G、16核64G等配置可选&#xff0c;游戏专业服务器公网带宽10M、12M、15M…

大模型训练所需的硬件配置

1. 引入 训练一个大模型&#xff0c;到底需要投入多少块GPU&#xff0c;需要多少数据&#xff0c;训练多长时间能达到一个不错的效果&#xff1f; 本文引用靠谱的数据&#xff0c;来回答这些问题。 2. 全流程训练 大模型的训练&#xff0c;简单来说&#xff0c;分为Pretrain…

C#,普洛尼克数(Pronic Number)的算法与源代码

1 普洛尼克数(pronic number) 普洛尼克数(pronic number)&#xff0c;也叫矩形数、欧波朗数(oblong number)&#xff0c;是两个连续非负整数的积&#xff0c;即mn*(n1)。第n个普洛尼克数侪是n个三角形数个两倍。 2 计算结果 3 源程序 using System; namespace Legalsoft.Tru…

02 数据库管理 数据表管理

文章目录 数据库管理数据表管理基础数据类型表的基本操作 数据库管理 查看已有库 show databases; 创建库 create database 库名 [character set utf8]; e.g. 创建stu数据库&#xff0c;编码为utf8 create database stu character set utf8; create database stu charsetutf8;…

禁止文件外发,文件禁止外发的方法

在当今的企业环境中&#xff0c;数据安全至关重要。 什么是企业文件外发&#xff1f; 企业文件外发指的是将企业内部的电子文件发送给组织外部的人员使用。 这种行为可能带来数据安全风险&#xff0c;因为电子文件自身具有易拷贝、易扩散、易传播的特性。 如果带有核心资产或…

LLMs之Llama2 70B:《Self-Rewarding Language Models自我奖励语言模型》翻译与解读

LLMs之Llama2 70B&#xff1a;《Self-Rewarding Language Models自我奖励语言模型》翻译与解读 目录 《Self-Rewarding Language Models》翻译与解读 Abstract 5 Conclusion结论 6 Limitations限制 《Self-Rewarding Language Models》翻译与解读 地址 文章地址&#xff1…

疑似针对安全研究人员的窃密与勒索

前言 笔者在某国外开源样本沙箱平台闲逛的时候&#xff0c;发现了一个有趣的样本&#xff0c;该样本伪装成安全研究人员经常使用的某个渗透测试工具的破解版压缩包&#xff0c;对安全研究人员进行窃密与勒索双重攻击&#xff0c;这种双重攻击的方式也是勒索病毒黑客组织常用的…

Scrum敏捷开发管理全流程-敏捷管理工具

Leangoo领歌是款永久免费的专业的敏捷开发管理工具&#xff0c;提供端到端敏捷研发管理解决方案&#xff0c;涵盖敏捷需求管理、任务协同、进展跟踪、统计度量等。 Leangoo领歌上手快、实施成本低&#xff0c;可帮助企业快速落地敏捷&#xff0c;提质增效、缩短周期、加速创新。…

娱乐直播APP开发:引领潮流,创新无界

随着互联网技术的飞速发展&#xff0c;娱乐直播APP已经成为现代人生活的重要组成部分。它以其独特的互动性、即时性和个性化&#xff0c;吸引了大量用户。本文将深入探讨娱乐直播APP开发的关键要素&#xff0c;以及如何在这个竞争激烈的市场中脱颖而出。 一、娱乐直播APP的核心…

第4章 表单与类视图

学习目标 熟悉Flask处理表单的方式&#xff0c;能够归纳在Flask程序中如何处理表单 掌握Flask-WTF扩展包的安装&#xff0c;能够借助pip工具安装Flask-WTF扩展包 掌握使用Flask-WTF创建表单的方式&#xff0c;能够独立使用Flask-WTF创建表单 掌握在模板中渲染表单的方式&…