当前位置: 首页 > news >正文

【Linux】了解信号产生的五种方式

文章目录

  • 正文前的知识准备
    • kill 命令查看信号
    • man手册查看信号
    • 信号的处理方法
  • 认识信号产生的5种方式
    • 1. 工具
    • 2. 键盘
    • 3. 系统调用
      • kill 向任意进程发送任意信号
      • raise 给调用方发送任意信号
      • abort 给调用方发送SIGABRT信号
    • 4. 软件条件
    • 5. 异常

正文前的知识准备

kill 命令查看信号

在命令行终端输入命令kill -l快速查看信号列表
在这里插入图片描述

信号列表中的每一项都由【数字】和【名字】两部分构成,但是信号本质上就只是一个【数字】而已,【名字】只是一个宏,如何证明,看源码定义:
在这里插入图片描述

信号列表中一共有62个信号,没有0、32、33号信号,信号可以分成实时信号(34 ~ 64)和非实时信号(1 ~ 31),后面谈到的所有内容只涉及非实时信号。

man手册查看信号

kill -l只是简单罗列一下OS中有哪些信号而已,这种方式不足以了解到更多的知识,还有另外一种查看信号的方式——man 手册。

man 7 signal 命令可用于查看 Linux 中有关信号的手册页。这个命令会显示与信号相关的信息,其中包含了关于信号的详细信息,如信号的编号、名称、含义、默认处理方式等,可以更好地理解信号的概念和用法。(文章后面的很多地方都会用到表里的内容)
在这里插入图片描述

信号的处理方法

信号的完整生命周期通常分为3个阶段:信号的产生、信号的保存、信号的处理。
本来信号的处理不是放在文章的内容,但是为了更好的理解信号产生的现象,这里提前了解一下。在这里插入图片描述

信号的处理方法有三种:

  1. 默认:man手册中提到,OS为每个信号都设置了一个默认行为,当一个进程收到信号时会执行对应的行为。
  2. 自定义捕捉:当进程收到一个信号,不执行默认行为转而执行程序员指定的行为,这个过程称之为 “自定义捕捉”。
  3. 忽略:进程收到信号之后什么都不做,即忽略该信号。

这里介绍一个能够更改进程信号处理方法的系统调用signal

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

说明:

  • 使用该方法要包含头文件signal.h
  • sighandler_t是一个函数指针,指向的函数参数个数为1,参数类型为int,返回值类型为sighandler_t
  • signum是指某个信号,表示对该信号设置自定义处理方法。
  • handler是程序员对信号signum的自定义处理方法。
  • 该方法的作用是将进程收到信号signum的处理动作由默认改为程序员自定义的handler方法,即当进程收到信号signum时,不再执行默认的处理动作,转而执行程序员自定义的handler方法来处理处理信号signum

注意:signal被调用时,handler方法不会被立即执行,而是进程收到信号之后才会被执行,如果没有收到对应的信号,handler方法永远也不会执行。

认识信号产生的5种方式

1. 工具

第一种信号产生的手段就是Linux操作系统内置的 kill 工具,通过该工具能够做到向任意进程发送任意信号。

LJH@hecs-66624:~$ ll /usr/bin/kill
-rwxr-xr-x 1 root root 30952 Sep  9  2021 /usr/bin/kill*

通常,kill命令的使用形式是

kill -<信号编号> <进程ID>

例如,输入命令 kill -9 1234,kill 工具会向 ID 为 1234 的进程发送 SIGKILL 信号,SIGKILL 信号的默认行为强制终止进程。

再介绍一下2号信号 SIGINT,它的默认处理动作的 Term(terminate,终止),即终止一个正在运行的进程。

这里就通过一个实现来验证一下kill命令能否对一个进程发送信号,同时还演示一下该如何使用系统调用signal来设置进程的信号处理方法。


实验内容如下:

  1. 写一个程序死循环向显示器输出 "I am working, my pid is " + 进程 ID。
  2. 在 1 的基础上,通过kill工具发送 2 号信号 SIGINT 来终止该进程,验证进程处理信号 SIGINT 的默认动作。
  3. 在 1 的基础上,先调用signal将进程对信号 SIGINT 的处理动作设置为向显示器输出 "get a signal: " + 收到的信号的数字、然后退出,再进程执行过程中向该进程发送信号 SIGINT,如果输出 “get a signal: 2” 并退出,则说明猜测正确。
  4. 在 1 的基础上,调用signal将进程对信号SIGINT的处理动作设置为忽略,然后在进程运行过程中向该信号发送信号SIGINT,如果信号发送无效,则说明猜测正确。

实验第2步:
代码:

// cpp
#include <iostream>
// system call
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>using namespace std;int main()
{while (true){cout << "I am working, my pid is " << getpid() << endl;sleep(2);}return 0;
}

运行过程:
在这里插入图片描述

结论:验证成功,进程处理信号SIGINT的默认动作确实是终止。


实验第3步
代码:

#include <iostream>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>using namespace std;void handler(int signum)
{cout << "get a signal: " << signum << endl;exit(0);
}int main()
{signal(SIGINT, handler);while (true){cout << "I am working, my pid is " << getpid() << endl;sleep(2);}return 0;
}

运行结果:
在这里插入图片描述

结论:验证成功,实现了对信号SIGINT的自定义捕捉。


实验第4步
SIG_IGN的定义:

#define	SIG_IGN	 ((__sighandler_t)  1)	/* Ignore signal.  */

代码:

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>using namespace std;int main()
{signal(SIGINT, SIG_IGN);while (true){cout << "I am working, my pid is " << getpid() << endl;sleep(2);}return 0;
}

运行结果
在这里插入图片描述

结论:验证成功,实现了对信号SIGINT的忽略操作。

2. 键盘

Ctrl+C这个组合键是我们在Linux最常用的组合键之一,它能终止正在执行过程中的进程,就像下面这样:
在这里插入图片描述

对于Ctrl+C这个组合键,我们虽然会用,但是却不怎么了解它的原理,既然这里提到了它,就说明它的原理和信号有关,理由是我们输入Ctrl+C组合键被操作系统解析成SIGINT信号,然后发送给了正在运行的进程,SIGINT信号的默认动作是终止进程,所以死循环被终止了。

详细的原理可以看一下个人写的另外一篇文章《解析Linux键盘组合键产生信号的完整过程:从硬件中断到信号发送》

凭什么这么说?接下来就要用一个实验来验证这个结论是否正确。


实验内容如下:
写一个程序,内容是死循环向显示器输出 "I am working, my pid is " + 进程ID,同时调用signal设置对信号SIGINT的捕捉方法为向显示器输出 "get a signal: " + 信号数字,但是不退出,进程执行过程中,不断按下ctrl + c,观察实验现象,如果信号数字是2,就说明收到了信号SIGINT,结论正确。

代码:

#include <iostream>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>using namespace std;void handler(int signum)
{cout << "get a signal: " << signum << endl;// exit(0);
}int main()
{signal(SIGINT, handler);while (true){cout << "I am working, my pid is " << getpid() << endl;sleep(2);}return 0;
}

运行结果:
这里能够看到在进程给执行过程中,连续按下Ctrl+C,自定义的handler方法不断被执行,然后向屏幕输出收到了2号信号,说明了上面的结论是正确的。
在这里插入图片描述


于此同时,再分别看一下源码中对信号SIGINT的定义中的注释和man手册对SIGINT的描述

#define	SIGINT		2	/* Interactive attention signal.  */Signal      Standard   Action   Comment
────────────────────────────────────────────────────────────────────────
SIGINT       P1990      Term    Interrupt from keyboard

“Interactive Interrupt Signal”,交互式中断信号,强调了它是通过用户的交互操作触发的,用于引起程序的注意并中断执行,而man手册中 “Interrupt from keyboard” 的注释表明,这个“交互操作”指的就是 Ctrl+C 这个组合键,Action 项为 Term 表示默认行为是终止进程。

3. 系统调用

在第2点时提到,shell程序通过系统调用来向目标进程发送信号,而系统调用就是产生信号的第3种方式,所以接下来说一下,都有哪些系统调用可以发送信号(说是系统调用,但其实这里只会讲到一个系统调用,其余两个都是C的库函数,不过它们两个的底层都是封装了系统调用,而且3者都是通过代码的形式来实现信号发送,所以这里就一起介绍)。

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

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

kill 系统调用的功能是向指定的进程发送指定的信号,参数pid是目标进程ID,参数sig是待发送信号,信号发送成功,返回值为0,信号发送失败,返回值为-1,同时错误码(errno)被设置。

理论部分完了,接下来是实验验证,到底 kill 系统调用能不能向一个进程发送信号。

实验内容:
使用 kill 系统调用简单实现一个属于自己的 kill 命令,即 mykill,输入 ./mykill -<信号编号> <进程ID> 能够做到和 kill 命令一样的功能,然后启动一个死循环输出 "I am working, my pid is " + 进程ID 的程序,在另一个窗口向该该进程发送信号SIGKILL,如果死循环进程 Killed,说明 kill 系统调用确实能够向一个进程发送信号。

代码:

---------------./testsig-----------------------
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>using namespace std;int main()
{while (true){cout << "I am working, my pid is " << getpid() << endl;sleep(1);}return 0;
}---------------./mykill------------------------
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <signal.h>
#include <sys/types.h>using namespace std;// ./mykill -9 pid
int main(int argc, char* argv[])
{if (argc != 3){cout << "Usage: " << argv[0] << " -signumber pid" << endl;return 1;}int signumber = stoi(argv[1]+1);int pid = stoi(argv[2]);int n = kill(pid, signumber);if (n < 0){cout << "kill error, error message: " << strerror(errno) << endl;return 2;}return 0;
}

运行结果:
在这里插入图片描述

raise 给调用方发送任意信号

#include <signal.h>
int raise(int sig);

raise 方法还有下面的 abort 方法严格上来说不算是系统调用,但都属于调用函数发送信号的范畴,并且比较常见,这里也一起研究一下。raise 方法的作用是给调用 raise 的进程发送一个指定的信号 sig,发送成功返回0,发送失败返回一个非0整数。

使用演示:
写一个程序,每秒向显示器输出 "I am working, my pid is " + 进程ID,3秒后向自己发送信号SIGKILL。

代码:

#include <iostream>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>using namespace std;int main()
{int cnt = 0;while (true){if (cnt == 3) {cout << "raise: send SIGKILL to caller" << endl;raise(SIGKILL);}cout << "I am working, my pid is " << getpid() << endl;cnt++;sleep(1);}return 0;
}

运行结果:
在这里插入图片描述

abort 给调用方发送SIGABRT信号

#include <stdlib.h>
void abort(void);

说明:
abort 方法会向调用该方法的进程发送 SIGABRT 信号,SIGABRT 的全称是 “Signal Abort”,“abort” 的中文意思是 “中止” 或 “放弃”,在计算机术语中指的是异常终止程序的行为,所以,进程处理信号 SIGABRT 的默认动作就是终止进程。


代码验证及使用演示:

#include <iostream>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>using namespace std;void handler(int signo)
{std::cout << "get a signo: " << signo << std::endl;exit(0);
}int main()
{signal(SIGABRT, handler);int cnt = 0;while (true){if (cnt == 3) {cout << "abort: send SIGABRT to caller" << endl;abort();}cout << "I am working, my pid is " << getpid() << endl;cnt++;sleep(1);}return 0;
}

运行结果:
在这里插入图片描述

4. 软件条件

产生信号的第4种方式就是【软件条件】,相比起前三种,这个看起来就很抽象,个人是这样理解的:在 “软件条件产生信号” 中,“软件” 指的是各种进程或者程序。当某个进程满足了某种条件,比如一个错误的操作、一个特定的事件发生或者一个状态的改变,它可能会向其他进程、操作系统或者它自己发送一个信号,以通知它们发生了某个事件或者需要采取某种行动。

下面来讲一个经典的软件条件的例子,13号 管道信号 SIGPIPE:

管道是进程间通信的手段之一,管道本身是OS提供的基于文件系统实现的一段内核级文件缓冲区,进程A先将数据写入缓冲区中,进程B来读取这样就实现了两个进程间的通信。

而一个管道只能支持单向通信,换言之,通信的进程双方得协商谁是管道的写端,谁是管道的读端,讲这个有意义吗,有意义,因为管道通信中规定了这样一种情况,当管道的读端进程关闭了管道的读端之后,OS会强制终止写端进程,因为管道数据已经没人读取了,再写也已经没有意义。

在这里插入图片描述

上面是结论,中间的原理是这样的,管道本身是OS提供用于进程间通信的资源,OS内部有通信需求的进程肯定不止这两个,所以,OS内肯定会创建很多的管道资源来 为进程提供通信服务,而OS作为计算机软硬件资源的管理者,它内部肯定会有一个描述管道信息的结构体以及组织管理所有管道的数据结构,因此,当某个管道的读端进程将管道的读端关闭了,OS肯定就会知道并将描述管道信息的结构体对象内的信息做修改,当该管道的写端进程尝试写入数据时,OS会检查管道的状态,如果发现该进程满足 “ 向不满足写入条件的管道进行写入 ” 的条件时,OS就会向该进程发送 SIGPIPE 信号,通知它写入操作失败,该进程需要处理这个信号。

首先来看一下man手册对于 SIGPIPE 信号的描述

Signal      Standard   Action   Comment
────────────────────────────────────────────────────────────────────────
SIGPIPE      P1990      Term    Broken pipe: write to pipe with no readers; see pipe(7)

其次惯例操作实验验证,内容如下:

  • pipe_server进程作为管道的读端,从管道中读取数据并输出,读端进程会在运行3秒后退出
  • pipe_client进程作为管道的写端,不停向管道写入数据,同时写端进程对SIGPIPE信号做自定义捕捉,看是否真的收到了SIGPIPE信号。

代码如下:

---------------Command.hpp------------------------
#ifndef __COMMAND_HPP__
#define __COMMAND_HPP__#include <iostream>
#include <string>
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>using std::cerr;
using std::cin;
using std::cout;
using std::endl;
#define Mode 0666class Fifo
{
public:Fifo(const char *path) : _path(path){umask(0);int n = mkfifo(_path.c_str(), Mode);if (n == 0){cout << "mkfifo success" << endl;}else{cerr << "mkfifo failed, errno: " << errno << ", errmsg: " << strerror(errno) << endl;exit(1);}}~Fifo(){int n = unlink(_path.c_str());if (n == 0){cout << "remove fifo success" << endl;}else{cerr << "remove fifo failed, errno: " << errno << ", errmsg: " << strerror(errno) << endl;exit(1);}}protected:std::string _path;
};#endif
---------------PipeServer.cc------------------------
#include "Command.hpp"const char *path = "fifo";int main()
{// 创建管道Fifo fifo(path);// 获取管道读端int rfd = open("fifo", O_RDONLY);if (rfd >= 0){cout << "open fifo success" << endl;}else{cerr << "open fifo failed, errno: " << errno << ", errmsg: " << strerror(errno) << endl;exit(1);}// 读数据char buffer[1024]{0};int cnt = 4;while (cnt--){ssize_t n = read(rfd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = 0;cout << "get msg from client: " << buffer << endl;}else if (n == 0){cout << "client quit, me too!" << endl;break;}else{cerr << "read from fifo failed, errno: " << errno << ", errmsg: " << strerror(errno) << endl;break;}}close(rfd);return 0;
}
---------------PipeClient.cc------------------------
#include "Command.hpp"void handler(int signum)
{cout << "get a signum: " << signum << endl;cout << "Pipe is broken, now I quit" << endl;exit(1);
}int main()
{signal(SIGPIPE, handler);// 获取管道写端int wfd = open("fifo", O_WRONLY);if (wfd >= 0){cout << "open fifo success" << endl;}else{cerr << "open fifo failed, errno: " << errno << ", errmsg: " << strerror(errno) << endl;exit(1);}// 发消息const char *inbuffer = "I am process A and I am client";while (true){ssize_t n = write(wfd, inbuffer, strlen(inbuffer));if (n < 0){cerr << "write to fifo failed, errno: " << errno << ", errmsg: " << strerror(errno) << endl;break;}sleep(1);}close(wfd);return 0;
}

运行结果:
在这里插入图片描述
在这里插入图片描述

5. 异常

了解过进程的都知道,一个进程退出只会有以下3种场景:

  1. 代码运行完毕,结果正确。
  2. 代码运行完毕,结果不正确。
  3. 代码运行过程中,进程异常终止。

而异常终止的本质原因是因为进程收到了信号,所以,第5种产生信号的方式就是异常。

举两个常见的代码异常,进程收到信号的例子:

  1. 代码除零 8) SIGFPE

下面是一段演示代码,里面写了除零操作。

#include <iostream>
using namespace std;int main()
{int a = 10;a /= 0;return 0;
}

运行结果:
在这里插入图片描述

运行之后看到,程序输出 “Floating point exception (core dumped)” 后就结束了,为了确认其是否收到了信号,这里使用signal设置自定义捕捉 SIGFPE 信号,代码修改如下:

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>using namespace std;void handler(int signum)
{cout << "get a signum: " << signum << endl;sleep(1);
}int main()
{signal(SIGFPE, handler);int a = 10;a /= 0;return 0;
}

输出结果:
在这里插入图片描述

捕捉了之后,确实验证了进程收到了 SIGFPE 信号,但同时也看到了一个很奇怪的现象,明明代码里从头到尾都没有写循环,为什么handler方法会被一直执行?

理由:信号的处理方法从Core退出被改成的向显示器输出一句话,这就导致了原本该退出的进程没有退出,进程没有退出,除零异常就一直存在,进程在被CPU调度时就会一直触发异常,OS会不断地向进程发送 SIGFPE 信号,进程收到信号并处理就会一直向屏幕输出,导致了一种死循环的局面。

  1. 野指针 11) SIGEGV

下面是一份演示代码,里面写了野指针访问操作。

#include <iostream>
using namespace std;int main()
{int *p = nullptr;*p = 100;return 0;
}

运行结果:
在这里插入图片描述

运行之后看到,程序输出 “Segmentation fault (core dumped)” 后直接退出,为了验证进程是否真的收到了信号,设置自定义捕捉 SIGSEGV 信号,观察是否收到信号,代码修改如下:

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
using namespace std;void handler(int signum)
{cout << "get a signum: " << signum << endl;sleep(1);
}int main()
{signal(SIGSEGV, handler);int *p = nullptr;*p = 100;return 0;
}

运行结果:
在这里插入图片描述

相关文章:

【Linux】了解信号产生的五种方式

文章目录 正文前的知识准备kill 命令查看信号man手册查看信号信号的处理方法 认识信号产生的5种方式1. 工具2. 键盘3. 系统调用kill 向任意进程发送任意信号raise 给调用方发送任意信号abort 给调用方发送SIGABRT信号 4. 软件条件5. 异常 正文前的知识准备 kill 命令查看信号 …...

【nuxt3国际化i18n】vue3+nuxt3+vite+TS国际化的正确做法

1、创建nuxt3请看Nuxt3官网 2、下面是添加i18n的叫教程&#xff0c;适用于企业前端项目。 添加依赖 依赖 yarn add vue-i18n yarn add nuxtjs/i18nnext -D配置文件nuxt.config.ts //nuxt.config.ts export default defineNuxtConfig({modules: [nuxtjs/i18n,],i18n: {stra…...

数据库管理-第185期 23ai:一套关系型数据干掉多套JSON存储(20240508)

数据库管理185期 2024-05-08 数据库管理-第185期 23ai:一套关系型数据干掉多套JSON存储&#xff08;20240508&#xff09;1 上期示例说明2 两个参数2.1 NEST/UNNEST2.2 CHECK/NOCHECK 3 一数多用3.1 以用户维度输出订单信息3.2 以产品维度3.3 以产品种类维度 4 美化输出总结 数…...

7 zip 介绍

7-Zip是一款广受好评的开源文件存档与压缩工具&#xff0c;支持高比率的压缩&#xff0c;适用于Windows、Linux和macOS等多种操作系统平台。以下是关于7-Zip的详细介绍&#xff1a; - **高压缩比**&#xff1a;7-Zip最显著的特点是其提供的高压缩率&#xff0c;尤其是使用其独…...

前端页面 贴边拖拽 盒子

vue 悬浮球(带自动吸附功能)_vue悬浮球-CSDN博客...

【408真题】2009-10

“接”是针对题目进行必要的分析&#xff0c;比较简略&#xff1b; “化”是对题目中所涉及到的知识点进行详细解释&#xff1b; “发”是对此题型的解题套路总结&#xff0c;并结合历年真题或者典型例题进行运用。 涉及到的知识全部来源于王道各科教材&#xff08;2025版&…...

WebSocket概述

TCP和HTTP规范有连接超时一说&#xff0c;所以长轮询并不能一直持续&#xff0c;服务端和客户端的连接需要定期的连接和关闭再连接。 WebSocket在请求头中有一个Connection:Upgrade字段&#xff0c;表示客户端想对协议进行升级&#xff0c;还有一个Upgrade:websocket字段&…...

人机协同是虚拟与真实的协同

“人机协同”是指人类与机器之间的合作与协同工作。在这种协同中&#xff0c;机器可以作为助手、辅助或扩展人类的能力&#xff0c;帮助人们完成任务&#xff0c;提高工作效率和质量。 虚拟与真实的协同是指在人机协同的过程中&#xff0c;虚拟想象世界和真实世界之间的协同。通…...

【编程向导】Docker-常用命令

常用命令 管理命令 管理命令说明builder管理构建config管理配置container管理容器context管理上下文engine管理引擎image管理镜像network管理网络node管理 Swarm 节点plugin管理插件secret管理 Docker secretsservice管理服务stack管理 Docker stacksswarm管理 Swarm 集群sys…...

LeetCode题练习与总结:不同的二叉搜索树Ⅱ--95

一、题目描述 给你一个整数 n &#xff0c;请你生成并返回所有由 n 个节点组成且节点值从 1 到 n 互不相同的不同 二叉搜索树 。可以按 任意顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[[1,null,2,null,3],[1,null,3,2],[2,1,3],[3,1,null,nul…...

idea SpringBoot + Gradle 环境配置到项目打包

一、前言 Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建开源工具。它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置&#xff0c;也增加了基于Kotlin语言的kotlin-based DSL&#xff0c;抛弃了基于XML的各种繁琐配置。 面向Java应用为主。当前其支持…...

深入理解tengine的sysguard模块

目录 1. 引言2. 开启sysguard模块2.1 编译2.2 配置3. 源码分析3.1 配置参数分析3.2 模块的初始化3.3 ngx_http_sysguard_handler函数3.4 各项负载指标的获取3.4.1 load系统负载的获取3.4.2 cpu使用率的获取3.4.3 内存使用情况的获取3.3.5 请求平均响应时间的获取1. 引言 Tengin…...

探索多模态LLM作为驾驶的世界模型

24年5月MIT的论文“Probing Multimodal LLMs as World Models for Driving”。 主要对多模态大语言模型&#xff08;MLLM&#xff09;在自动驾驶领域的应用进行了审视&#xff0c;并挑战/验证了一些常见的假设&#xff0c;重点关注它们通过图像/帧序列推理和解释在闭环控制环境…...

掌握Vim:Linux系统维护的瑞士军刀 - 常用命令深度解析

在Linux的世界里&#xff0c;Vim编辑器犹如一位沉默的剑客&#xff0c;它的命令就是那锋利的剑刃&#xff0c;能够在代码的海洋中劈波斩浪。对于每一位Linux系统用户来说&#xff0c;掌握Vim的常用命令&#xff0c;就如同获得了维护系统的瑞士军刀。今天&#xff0c;让我们一起…...

C++数组和指针应用实例 -- 实现计算器

C 的数组和C 语言一样&#xff0c;C完全兼容C语言的指针&#xff0c;但是会多出一个this指针 用C实现计算器 case1: 基本实现: #include <iostream>using namespace std;int add(int a,int b) {return ab; }int minu(int a,int b) {return a-b; }int mul(int a,int b) …...

【多电压流程 Multivoltage Flow】- 5.特定工具使用建议(6.Formality)

使用Formality进行形式验证 Formality支持具有低功耗特性的功能等效性检查,如时钟门控、多阈值电压(multiple-Vt)、多电压供电、电源门控以及动态电压和频率缩放。Formality能够识别低功耗单元,例如隔离单元、电平转换器、始终开启单元、保持寄存器和电源门。 Formality支持…...

力扣 72. 编辑距离 python AC

动态规划 class Solution:def minDistance(self, word1, word2):size1 len(word1)size2 len(word2)dp [[0] * (size2 1) for _ in range(size1 1)]for i in range(1, size1 1):dp[i][0] dp[i - 1][0] 1for i in range(1, size2 1):dp[0][i] dp[0][i - 1] 1for i in…...

vue 发布项目

You are not allowed to force push code to a protected branch on this project. 分支做了保护&#xff0c;git中设置允许强制推送...

springBoot实现发送邮箱验证码 redis缓存源码

要在Spring Boot中实现发送邮箱验证码并使用Redis进行缓存&#xff0c;你需要遵循几个步骤。以下是一个简化的示例&#xff0c;展示了如何整合这些功能&#xff1a; 添加依赖 首先&#xff0c;确保你的pom.xml&#xff08;Maven&#xff09;或build.gradle&#xff08;Gradle…...

QT--4

QT 使用定时器完成闹钟 #include "widget.h" #include "ui_widget.h"void Widget::timestart() {timer.start(1000); }void Widget::timeend() {timer.stop(); }Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(t…...

感染了后缀为.360勒索病毒如何应对?数据能够恢复吗?

导言&#xff1a; 在数字化时代的浪潮中&#xff0c;网络安全问题如同暗流涌动&#xff0c;威胁着每一个互联网用户的安宁。而近年来&#xff0c;一种名为.360勒索病毒的新型网络威胁逐渐浮出水面&#xff0c;以其独特的加密方式和狡猾的传播策略&#xff0c;给全球网络安全带…...

JavaSE多态

多态&#xff1a;一个对象在不同条件下表示的不同形态就叫多态。在程序中&#xff0c;多态是父类引用指定子类对象就叫多态。 多态是面向对象程序设计中的第三个特征 // 多态 class Father {String name;public void desc() {System.out.println("----------");Sys…...

M 有效算法

M 有效算法 本题考验二分知识&#xff0c;思路是二分k的取值&#xff0c;就按第一组样例来说当我们k取值为1的时候我们遍历数组想让|8-x|<k1的话x的取值范围是7-9&#xff0c;想让|3-x|<k2的话x的取值范围是1-5&#xff0c;两者x的区间不重合&#xff0c;说明肯定没有x能…...

知识付费系统制作,托管机构如何提高体验课转化率?要注意什么?

现在托管机构非常流行&#xff0c;一所学校周边就会出现好几家托管机构&#xff0c;所以竞争非常激烈。很多托管机构为了扩大生源&#xff0c;会选择体验课来让学生体验&#xff0c;至于如何提高体验课转化率&#xff0c;就看机构的本事了。 1、市场调研&#xff1a;摸清当前我…...

【iOS逆向与安全】网上gw如何自动登录与签到SM2,SM3,SM4算法加解密

1.下载 app 2.frida 调试 3.抓包查看接口 4.分析加密数据 5.易语言编写代码 1 .开始下载 下载好发现有越狱检测&#xff0c;检测点为&#xff1a; -[AppDelegate isJailBreak]; 于是编写插件xm代码 : %hook AppDelegate- (void)isJailBreak{NSLog("AppDelegate is…...

《CKA/CKAD应试指南/从docker到kubernetes 完全攻略》学习笔记 第14章 包管理helm v3

前言 考试大纲: 了解helm是如何工作的,从而实现快速部署应用 本章要点: 考点1:添加helm源 考点2:使用helm 部署应用 前面在使用wordpress + mysql 部署博客应用的时候,需要做许多工作,需要每个pod创建pv和pvc,然后分别创建每个应用pod及svc,整个过程非常麻烦. 如果搭建博客的…...

蓝桥杯备战.19有奖问答dfs

P9230 [蓝桥杯 2023 省 A] 填空问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) #include<bits/stdc.h> using namespace std; #define endl \n //#define int long long const int N 2e510; int a[N],w[N]; int ans 0; void dfs(int score,int cnt) {if(cnt>3…...

【JS红宝书学习笔记】第1、2章 初识JS

第1章 什么是JavaScript JavaScript 是一门用来与网页交互的脚本语言&#xff0c;包含以下三个组成部分。 ECMAScript&#xff1a;由 ECMA-262 定义并提供核心功能。文档对象模型&#xff08;DOM&#xff09;&#xff1a;提供与网页内容交互的方法和接口。浏览器对象模型&…...

学习java

在实验室看见这本书&#xff0c;无聊看了下&#xff0c;写出了第一个java代码 成功下载了eclipse并且汉化。 写了自己的第一个java程序&#xff1a; package ttttt;public class ttttt {public static void main(String[] args) {System.out.println("hello world")…...

Redis日常维护流程及技巧:确保稳定性与性能

目录 一、监控和报警设置 1.实时监控&#xff1a;洞察Redis的脉搏 &#xff08;1&#xff09;. 资源使用监控 &#xff08;2&#xff09;. 数据访问模式监控 &#xff08;3&#xff09;. 持久化监控 &#xff08;4&#xff09;. 客户端连接 2.报警机制&#xff1a;快速响…...