【Linux】进程信号 --- 信号产生 信号递达和阻塞 信号捕捉

news2024/11/23 1:03:26

🍎作者:阿润菜菜
📖专栏:Linux系统编程


文章目录

  • 一、预备知识
  • 二、信号产生
    • 1. 通过终端按键产生信号
      • 1.1 signal()
      • 1.2 core dump标志位、核心存储文件
    • 2.通过系统调用向进程发送信号
    • 3.由软件条件产生信号
      • 3.1 alarm函数和SIGALRM信号
      • 3.2 使用alarm()系统接口 验证 IO的效率 --- 很慢
    • 4. 硬件异常产生信号


一、预备知识

我们想要杀死某个后台进程的时候,无法通过ctrl+c热键终止进程时,我们就会通过kill -9的命令来杀死信号。
查看信号也比较简单,通过kill -l命令就可以查看所有信号的种类,虽然最大的信号编号是64,但实际上所有信号只有62个信号,1-31是普通信号,34-64是实时信号,本文不对实时信号做讨论,只讨论普通信号,感兴趣的老铁可以自己下去研究一下。
在这里插入图片描述
以过红绿灯为例,其实在红绿灯出现之前,我们大脑里已经有了红灯停绿灯行的意识了,对于进程来说也是如此,进程在收到信号之前就已经知道怎么处理对应信号了,已经保存了对应处理的方法;那绿灯亮起,此时有朋友打来视频电话,我们也可以选择忽略该绿灯信号,暂时不通行,去忙别的事情,对应进程来说如果不能里面处理这个信号,那进程就要有信号保存的能力!

我们Linux操作时用户输入命令,在Shell下启动一个前台进程。.用户按下Ctrl-C ,这个键盘输入产生一个硬件中断,被OS获取,解释成信号,发送给目标前台进程, 前台进程因为收到信号,进而引起进程退出。这就是技术角度的信号处理过程。

注意:

  1. Ctrl-C 产生的信号只能发给前台进程。一个命令后面加个&可以放到后台运行,这样Shell不必等待进程
    结束就可以接受新的命令,启动新的进程。
  2. Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像 Ctrl-C 这种控制键产生
    的信号。
  3. 前台进程在运行过程中用户随时可能按下 Ctrl-C 而产生一个信号,也就是说该进程的用户空间代码执行
    到任何地方都有可能收到 SIGINT 信号而终止,所以信号相对于进程的控制流程来说是异步
    (Asynchronous)的。

所以有了下面的预备知识:
两个先导结论:

  1. 进程在没有收到信号之前,就已经知道怎么处理信号了
  2. 进程不能立马处理这个信号,就需要进程具有信号保存的能力

二、信号产生

1. 通过终端按键产生信号

1.1 signal()

我们最常用的发送信号方式就是一个热键ctrl+c,这个组合键其实会被操作系统解释成2号信号SIGINT,通过man 7 signal命令就可以查看到对应的信号和其默认处理行为等等信息。
在这里插入图片描述
这里SIGINT的默认处理动作是终止进程 — Term,上图介绍了各种处理动作。但进程不是立即处理该信号的,而是先将信号保存下来,信号保存部分后面讲解;同时其实我们可以定义收到信号后的处理动作的 ---- 我们利用signal()系统调用

signal()是一个系统调用,用于设置或处理信号
我们知道信号是一种通知进程发生了某种事件的机制。当进程收到一个信号时,它可以执行以下三种操作之一

  • 忽略信号,不做任何处理。(后面讲解)
  • 执行默认动作,通常是终止进程或忽略信号。
  • 调用一个自定义的信号处理函数,执行特定的操作。

signal()函数的原型是:

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

它接受两个参数:signum是要处理的信号的编号,handler是要执行的信号处理函数的指针。它返回一个函数指针,指向之前设置的信号处理函数,或者SIG_ERR表示出错。所以这里我们可以自定义一个函数方法,当进程接收到某个信号后,利用signal函数调用我们的自定义信号处理函数。
注意:我们显示写signal函数其实相当于注册了一个信号处理时的自定义行为,然后这个自定义行为handler不会平白无故被调用的,只有当对应信号发送给进程时,这个handler才会被调用,否则这个函数是永远不会被调用的。
那么handler方法什么时候调用? 当2号信号产生的时候,就被调用,相当于提前设定好了方法。
同时注意调用singal并没有调用handler方法,只是更改了2号信号函数指针的映射关系

例如,如果要捕获SIGINT信号(由键盘中断产生),并调用一个名为my_handler的函数来处理它,可以这样写:

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

void my_handler(int signum) {
    printf("Received signal %d\n", signum);
}

int main() {
    signal(SIGINT, my_handler); // 设置SIGINT的处理函数为my_handler
    while (1) {
        // do something
    }
    return 0;
}

signo:自动填充对应的信号在这里插入图片描述

如果要恢复SIGINT的默认动作,可以这样写:

signal(SIGINT, SIG_DFL); // 设置SIGINT的处理函数为默认动作

如果要忽略SIGINT信号,可以这样写:

signal(SIGINT, SIG_IGN); // 设置SIGINT的处理函数为忽略

注意:9号信号不可被自定义!!

Linux支持多种信号,每种信号都有一个名称和一个编号。我们可以在signal.h头文件中查看它们的定义。一些常见的信号有:

  • SIGTERM:终止信号,通常用来请求进程正常退出。
  • SIGKILL:杀死信号,强制进程立即退出,不能被忽略或捕获。
  • SIGSEGV:段错误信号,由于无效的内存访问产生。
  • SIGALRM:闹钟信号,由alarm()函数设置的定时器到期时产生。(后面讲解)
  • SIGCHLD:子进程终止信号,当一个子进程退出或停止时产生。

我们可以使用kill()函数或kill命令来向一个进程发送一个信号。例如,如果要向进程1234发送SIGTERM信号,可以这样写:

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

int main() {
    kill(1234, SIGTERM); // 向进程1234发送SIGTERM信号
    return 0;
}

在命令行中输入:

kill -TERM 1234 # 向进程1234发送SIGTERM信号

一个热键是ctrl+\,这个组合键会被操作系统解析为3号信号SIGQUIT,这个信号的默认处理行为是Core,除终止进程外还会进行核心转储。Core于Term有什么不同?在这里插入图片描述

1.2 core dump标志位、核心存储文件

core dump 标志位是一个退出信息中的一个位,用来表示进程是否产生了core dump文件。core dump文件是一个包含进程内存内容的文件,当进程意外终止时,由操作系统生成,用于后期调试。core dump标志位为1,表示进程产生了core dump文件;为0,表示没有产生。core dump标志位可以通过kernel.core_pattern这个系统参数来设定,这个参数指定了core dump文件的保存位置和格式。例如,执行命令sysctl -w kernel.core_pattern=/var/crash/core.%u.%e.%p,就可以将core dump文件保存在/var/crash目录下,文件名为core.用户ID.可执行文件名.进程ID。

在这里插入图片描述
什么是核心存储文件

核心存储文件,也叫core dump文件,是一个当程序运行过程中发生异常,程序异常退出时,由操作系统把程序当前的内存状况存储在一个文件中的文件。核心存储文件包含了程序运行时的内存状态、寄存器状态、堆栈指针、内存管理信息以及各个函数使用堆栈信息等等。核心存储文件的作用是用于后期调试,可以通过gdb等工具分析核心存储文件,定位程序出错的原因和位置

在这里插入图片描述
所以core dump文件有什么用? 事后调试!
在这里插入图片描述
生产环境中服务器为什么要关闭核心存储文件,有以下几个原因

  • 核心存储文件通常很大,占用大量的磁盘空间,如果不及时清理,可能会影响服务器的性能和稳定性³。
  • 核心存储文件可能包含敏感的数据,比如用户信息、密码、密钥等,如果被恶意获取或泄露,可能会造成安全风险³。
  • 生产环境中的程序应该经过充分的测试和优化,不应该频繁出现异常退出的情况,如果出现了,应该及时修复,并通过日志或监控等手段进行分析和定位³。

简单说服务器重启可是会疯狂写core文件的,所以要关!
在这里插入图片描述

控制core dump文件开关的方法我们一般使用ulimit命令的-c选项,可以设置core文件的生成开关和大小限制。例如,ulimit -c 0表示关闭core dump功能,ulimit -c unlimited表示不限制core文件的大小,ulimit -c 1024表示限制core文件的大小为1024KB。这种方法是临时的,系统重启后会失效

在这里插入图片描述

另外补充一个知识点,linux规定,当用户在和shell交互时,默认只能有一个前台进程,所以当我们自己编写的程序运行时,bash进程就会自动由前台进程转换为后台进程。

2.通过系统调用向进程发送信号

其实除上面那种用组合键或者是手动的通过kill指令加信号编号的方式给进程发送信号外,我们还可以通过系统调用的方式给进程发送信号。操作系统有向进程发送信号的能力,但是他并没有这个权力,操作系统的能力是为用户提供的,用户才有发送信号的权力,操作系统通过给用户提供系统调用赋予用户让OS向进程发送信号的权力。就像我们将来可能都会变成程序员,我们有写代码的能力,我们的能力是服务于公司或老板的,让我们写代码的权力来自于老板。

kill系统调用和raise、abort库函数都是用来发送信号的函数,但是有一些区别

  • kill系统调用可以向任意进程或进程组发送任意信号,只要发送者有足够的权限。它的原型是:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

它接受两个参数:pid是要发送信号的进程或进程组的标识,sig是要发送的信号的编号。它返回0表示成功,-1表示失败,并设置errno。

  • raise库函数可以向自己发送一个信号,相当于kill(getpid(), sig)。它的原型是:
  • 参数sig是要发送的信号的编号,可以参考kill -l命令查看信号列表
#include <signal.h>
int raise(int sig);

它接受一个参数:sig是要发送的信号的编号。它返回0表示成功,非0表示失败。

  • abort库函数可以向自己发送一个SIGABRT信号,通常用来表示程序异常终止。它的原型是:
#include <stdlib.h>
void abort(void);

它没有参数和返回值,也不能被阻塞或忽略。如果SIGABRT信号没有被捕获或处理,程序会终止并生成一个core文件。

我们上面所说的raise()和abort()都在man 3号手册,这代表他们都是库函数,而kill在2号手册,是纯正的系统调用。但3号手册的库函数可以分为两类,底层封装了系统调用的库函数和没有封装系统调用的库函数,很明显,raise和abort库函数就是底层封装了kill系统调用的库函数。就连kill指令底层其实也是封装的kill系统调用来实现的。

6号信号和2号信号的区别是什么?

  • 6号信号是SIGABRT,表示进程异常终止,通常由abort()函数产生。它的默认动作是终止进程并生成一个core文件,用于保存进程的状态信息,方便调试。它不能被忽略或捕获。
  • 2号信号是SIGINT,表示键盘中断,通常由Ctrl+C产生。它的默认动作是终止进程,但是它可以被忽略或捕获,并执行自定义的处理函数。

如果你在循环中使用abort()函数,那么程序会立即终止,并且不会再执行循环。如果你在循环中使用raise(SIGINT)函数,那么程序会收到2号信号,如果你没有设置信号处理函数,那么程序也会终止;如果你设置了信号处理函数,那么程序会执行你的处理函数,并且可能会继续执行循环。abort用于终止进程,类似于exit(),因为有了exit(),所以实际中我们很少用abort()

3.由软件条件产生信号

3.1 alarm函数和SIGALRM信号

  • alarm函数是用来设置一个定时器,告诉内核在指定的秒数之后给当前进程发送一个SIGALRM信号。它的原型是:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);

它接受一个参数:seconds表示定时器的时间间隔,单位为秒。如果seconds为0,则取消之前设置的定时器。它返回0或者是以前设定的闹钟时间还剩余的秒数。

  • SIGALRM信号是由alarm函数产生的定时信号,表示定时器时间到达。它的默认动作是终止当前进程,但是它可以被忽略或捕获,并执行自定义的处理函数。

我们可以使用alarm函数和SIGALRM信号来实现定时任务,例如定时执行某个操作或定时检查某个状态。需要注意的是,由于alarm函数只能设置一个定时器,因此如果需要同时设置多个定时器,需要使用其他方法,例如使用select()或poll()函数来等待多个信号。

3.2 使用alarm()系统接口 验证 IO的效率 — 很慢

通过alarm闹钟,我们可以计算出1s内CPU能够累加数据多少次,下面测试的代码中其实分了两种情况进行测试,一种是每次将累加数据之后的结果打印到显示器上,一种是在1s内只进行数据的累加,等到1s到了的时候,我们捕捉信号在handler里面进行累加后数据的值的打印
在这里插入图片描述
实验结果二者差了大概1w多倍数,可以看到一旦访问外设CPU的执行速度就会慢下来,因为等待硬件就绪很慢,硬件就绪的时间和CPU计算的时间根本不在一个量级。
注意:我们可以设置剩余的alarm时间 — 一个进程只能设置一个闹钟 — 会覆盖,闹钟会通过pid标识是谁设置的

那为什么alarm闹钟是软件条件异常呢?
闹钟实际就是软件,他不就是数据结构和属性的集合吗?所以闹钟本身就是软件,当前进程可以设定闹钟,那么其他进程也可以设定闹钟,所以操作系统内部一定会存在很多的闹钟,那么操作系统要不要对这些闹钟进行管理呢?当然要,管理的方式就是先描述,再组织。所以闹钟在操作系统中实际就是内核数据结构,此内核数据结构用于描述闹钟,组织的最常见方式就是通过链表,但闹钟的组织方式也可以通过堆,也就是优先级队列来实现。

下面是闹钟内核数据结构的伪代码,其内部有一个闹钟响铃的时间,表示在当前进程的时间戳下,经过所传参数second秒后,闹钟就会响铃,这个响铃时间即为当前进程时间戳+second参数大小。
另外闹钟还需要一个PCB结构体指针,用于和设置闹钟的进程进行关联,在闹钟响了之后,便于操作系统向对应进程发送14号信号SIGALRM,此信号默认处理动作也是终止当前进程。

在这里插入图片描述

OS会周期性的检查这些闹钟,也就是通过遍历链表的方式,检查当前时间戳超过了哪个闹钟数据结构中的when时间,一旦超过,说明此闹钟到达设定时间,那么这个时候操作系统就该给闹钟对应的进程发送14号信号,如何找到这个进程呢?通过alarm类型的结构体指针便可以拿到alarm结构体的内容,其结构体中有一个字段便是PCB指针,通过PCB指针就可以找到闹钟对应的进程了。

在这里插入图片描述

除链表这样经典的组织方式之外,另一种组织方式就是优先级队列,priority_queue,实际就是堆结构,按照闹钟结构体中的when的大小建大堆,如果堆顶闹钟的时间小于当前进程时间戳,则说明整个堆中所有的闹钟均为达到响铃的条件。如果堆顶闹钟的时间大于当前进程时间戳,那就要给堆顶闹钟对应进程发送14号信号了,检查过后再pop堆顶元素,重新看下一个堆顶闹钟是否超时,大概就是这么一个逻辑。

4. 硬件异常产生信号

为什么代码除0会收到警告?其实作系统检测到后会立即向进程PCB位图中写入信号(通俗叫发送信号) — 8号信号 SIGFPE

int main()
{
int n =3;
cout << n /0 <<endl;
return 0;
}

代码除0会收到警告,是因为除0是一种硬件异常,也就是由处理器检测到的非法操作。当处理器执行除0指令时,它会产生一个中断信号,通知操作系统发生了错误。操作系统收到中断信号后,会根据中断向量表找到对应的中断处理程序,然后执行它。在这个过程中,操作系统会向进程的PCB位图中写入信号(通俗叫发送信号),这个信号就是8号信号 SIGFPE**,表示浮点异常**。如果进程没有捕捉或忽略这个信号,那么它的默认动作是终止进程,并打印出错误信息。所以,代码除0会收到警告,是因为操作系统根据硬件异常产生了一个信号,并执行了默认的终止动作。
在这里插入图片描述
那这样那我们利用signal()系统调用捕捉8号信号,为什么循环打印呢因为MMU硬件报错一直都存在,没有被修复;那为什么没有退出进程?因为我们捕捉信号自定义处理方法了

MMU在哪里呢? 被集成到CPU里了,硬件!MMU就是帮助地址转换,帮助页表映射的硬件!

空指针会引起程序崩溃 – 11号信号
在这里插入图片描述

所以程序崩溃的根本原因不一定是硬件异常,也可能是软件异常,比如内存访问越界,空指针解引用,非法指令等。操作系统向目标进程发送信号,只是一种通知进程发生了异常的方式,并不一定导致程序崩溃。如果进程能够捕捉或忽略信号,或者执行自定义的处理动作,那么程序可能还能继续运行。所以,程序崩溃的根本原因是进程无法处理异常的情况。

那既然这么多信号默认处理都是干掉进程,那要这么多信号干什么?进程的死法不一样,我们人类要知道啊,就像是你定义的退出码一样

总结思考一下:
1.上面所说的所有信号产生,最终都要有OS来进行执行,为什么?OS是进程的管理者
2.信号的处理是否是立即处理的?在合适的时候
3.信号如果不是被立即处理,那么信号是否需要暂时被进程记录下来?记录在哪里最合适呢?
4.一个进程在没有收到信号的时候,能否能知道,自己应该对合法信号作何处理呢?
5.如何理解OS向进程发送信号?能否描述一下完整的发送处理过程?


未完待续

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

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

相关文章

vue3通过ref拿element弹框中的组件问题

写在<el-dialog>中的组件&#xff0c;在一开始&#xff0c;弹框没有弹出来的时候&#xff0c;<el-dialog>中的组件是没有被渲染出来的&#xff0c;因此在<el-dialog>中使用ref标记某个组件&#xff0c;在el-dialog没有被渲染出来之前去拿的话&#xff0c;是拿…

ml常见代码片段

常用ML代码片段 变换一列 new_df[brand] new_df[prod_name].apply(lambda x: x.split()[0])变换2列 new_df[chip_total_sales] new_df.apply(lambda x: x[total_sales] * x[is_chip], axis 1) # 重要的是axis1groupby 计数&#xff0c;求和&#xff0c;取第一个值&#x…

C语言起源、特性和发展历程

本文从ALGOL 60语言谈起&#xff0c;简述C语言的起源和发展历程&#xff0c;然后对C语言的一些特性做了探讨&#xff0c;最后说说C语言为什么在众多编程语言中&#xff0c;起到了承上启下的作用。 本文介绍以下内容&#xff1a; C语言的起源C语言的发展C语言的特性C语言的重要…

外卖项目优化-01-redis缓存短信验证码、菜品数据

文章目录 外卖项目优化-01课程内容前言1. 环境搭建1.1 版本控制解决branch和tag命名冲突 1.2 环境准备 2. 缓存短信验证码2.1 思路分析2.2 代码改造2.3 功能测试 3. 缓存菜品信息3.1 实现思路3.2 代码改造3.2.1 查询菜品缓存3.2.2 清理菜品缓存 3.3 功能测试3.4 提交并推送代码…

AutoGPT安装教程

最近安装AutoGPT时遇到了一些问题&#xff0c;写下这篇文章记录一下 1 下载AutoGPT AutoGPT链接&#xff1a;https://github.com/Significant-Gravitas/Auto-GPT/tree/v0.2.2 下载AutoGPT 推荐下载stable 版本 2 申请openai 的api key 获取api的key&#xff0c;这里就不介…

【超算/先进计算学习】日报8

目录 今日已完成任务列表遇到的问题及解决方案任务完成详细笔记阶段一阶段二阶段三阶段四 对自己的表现是否满意简述下次计划其他反馈 今日已完成任务列表 超算/高性能计算总结 遇到的问题及解决方案 无 任务完成详细笔记 阶段一 在学习的第一阶段&#xff0c;我们首先对需要…

ChatGPT+智能家居在AWE引热议 OpenCPU成家电产业智能化降本提速引擎

作为家电行业的风向标和全球三大消费电子展之一&#xff0c;4月27日-30日&#xff0c;以“智科技、创未来”为主题的AWE 2023在上海新国际博览中心举行&#xff0c;本届展会展现了科技、场景等创新成果&#xff0c;为我们揭示家电与消费电子的发展方向。今年展馆规模扩大至14个…

【pytest里的参数化:看几个例子就够了!】

参数化多个参数&#xff1a;可以使用多个参数来参数化测试。例如&#xff1a; import pytestpytest.mark.parametrize("x, y, expected", [(1, 2, 3),(3, 4, 7),(5, 6, 11), ]) def test_addition(x, y, expected):assert x y expected参数化列表&#xff1a;可以…

轻叶H5营销单页,让你的营销更加清爽高效

网络营销就是营销企业品牌形象、产品信息发布、优惠促销活动&#xff0c;最终目的就是争抢流量和客户。现在为了吸引流量&#xff0c;各种营销方式、广告玩法层出不穷&#xff0c;成本投入大&#xff0c;带来的转化不一定好。今天&#xff0c;我们要来讲一讲H5营销单页。 H5营销…

项目管理软件project下载安装配置图文教程

目录 前言 配置安装过程 总结 前言 Project是一种计划、组织和管理任务的工具&#xff0c;通常用于团队协作和项目管理。它可以帮助用户创建任务列表、分配任务、设置截止日期、跟踪进度、分析数据等。Project还可以生成各种报告&#xff0c;如甘特图、资源使用情况、任务分…

稀疏矩阵存储格式总结

稀疏矩阵是指矩阵中的元素大部分是0的矩阵&#xff0c;实际问题中大规模矩阵基本上都是稀疏矩阵&#xff0c;很多稀疏度在90%甚至99%以上,大规模的稀疏造成了大量无效数据的计算和存储资源占用&#xff0c;也无法有效的载入有限内存计算。因此我们需要有高效的稀疏矩阵存储格式…

SpringCloud:ElasticSearch之数据同步

elasticsearch中的酒店数据来自于mysql数据库&#xff0c;因此mysql数据发生改变时&#xff0c;elasticsearch也必须跟着改变&#xff0c;这个就是elasticsearch与mysql之间的数据同步。 1.思路分析 常见的数据同步方案有三种&#xff1a; 同步调用异步通知监听binlog 1.1.同…

Nacos配置中心的详解与搭建

Namespace 简介 用于进行租户粒度的配置隔离&#xff0c;不同的命名空间下&#xff0c;可以存在相同的 Group 或 Data ID 的配置 配置Namespace 点击nacos的命名空间——点击新建命名空间 开发环境【dev】测试环境【test】正式环境【prod】 DataID 简介 Data ID 通常用于…

Node.js 下载与安装教程

文章目录 Node.js 下载Node.js 安装npm 配置配置node_path修改用户变量更换npm源为淘宝镜像全局安装基于淘宝源的cnpm Node.js 下载 1.进入nodejs官网&#xff1a;https://nodejs.org/en 2.单击downloads 3.此时滑动滚动条&#xff0c;找到并单击 previous release 4.在此页…

<C++>lesson1.C++入门上

文章目录 1. C关键字(C98)&#x1f49a;2. 命名空间&#x1f90e;2.1 命名空间定义2.2命名空间的使用 3. C输入/输出&#x1f5a4;4.缺省参数&#x1f499;4.1 缺省参数概念4.2 缺省参数分类 5. 函数重载❤️5.1 函数重载的概念5.2 C支持函数重载的原理 6. 引用&#x1f49c;6.…

Day4_Springboot集成Mybatis

上一节使用springboot框架搭建了项目&#xff0c;并创建了数据库user表&#xff0c;接下来集成mybatis对用户表实现增删改查操作~~~~ 目录 SpringBootApplication.java 创建model/entity文件夹&#xff0c;存放实体类 UserDao.java UserController.java 浏览器Json插件&am…

Leetcode刷题日志3.0

目录 前言&#xff1a; 1.相对名次​​​​​​ 2.学生出勤记录 I 3.重塑矩阵 4.分糖果 5.最长和谐子序列 6.种花问题 前言&#xff1a; 今天我就分享一下最近在leetcode刷到的题&#xff0c;希望对大家有所帮助。编程语言&#xff1a;Python3。好了废话不多讲了&…

消息队列使用场景介绍

消息队列中间件是分布式系统中重要的组件&#xff0c;主要解决应用耦合&#xff0c;异步消息&#xff0c;流量削锋等问题 实现高性能&#xff0c;高可用&#xff0c;可伸缩和最终一致性架构 使用较多的消息队列有ActiveMQ&#xff0c;RabbitMQ&#xff0c;ZeroMQ&#xff0c;Ka…

【华中农业大学2023年十二届程序设计竞赛(同步赛)】B. 写信

文章目录 题目描述思路代码 题目描述 思路 错位排序&#xff0c;可搜索引擎。复杂度太高 递推式&#xff1a; f [ n ] ( n − 1 ) ∗ ( f [ n − 1 ] f [ n − 2 ] ) f[n](n-1)*(f[n-1]f[n-2]) f[n](n−1)∗(f[n−1]f[n−2]) 正解&#xff1a;打表&#xff01;YYDS 1e9的数…

12.Hadoop练习题

1.网络问题 &#xff08;1&#xff09;机器联网出现问题 情况&#xff1a;ping一下百度&#xff0c;发现百度ping不通 sudo vim /etc/sysconfig/network-scripts/ifcfg-ens33检查GATEWAY是否正确&#xff0c;修改过来之后保存退出&#xff0c;重启虚拟机 sudo systemctl re…