Linux 操作系统 --- 信号
序言
 在本篇内容中,将为大家介绍在操作系统中的一个重要的机制 — 信号。大家可能感到疑惑,好像我在使用 Linux 的过程中并没有接触过信号,这是啥呀?其实我们经常遇到过,当我们运行的进程当进程尝试访问非法内存地址时,我们的进程会被中断,这是因为操作系统向该进程发送了中断信号。
  Linux 操作系统离不开信号机制,在这篇文章中,让我们走进信号,了解信号的从哪里来,又到哪里去。
1. 信号的概念
1.1 定义
 信号是操作系统向进程发送的一种通知,表示某个特定事件已经发生。在Unix、类Unix 以及其他系统中,信号被广泛使用。
 
1.2 特点
信号具有如下的特点:
异步性:信号的产生对进程来说是异步的,即进程无法预知信号何时到来。通知机制:信号是一种软件中断(由软件程序触发的中断方式),用于中断进程的正常执行流程,使其处理特定事件。进程间通信:虽然信号主要用于异常处理和系统调试,但也可以用于进程间的基本通信。
1.3 种类
 在 Linux 系统下,使用指令:kill -l 即可查看所有的信号:
 
信号是使用宏定义的,每个信号前面的数字,就是该信号宏对应的值。
前 31 个信号为常规信号,其余为实时信号。在本篇文章中,我们主要讨论前 31 个常规信号。
 你也可以使用指令: man 7 signal 查看每一个详细信息:
 
补充知识点:Core && Term
 在描述信号的字段中,有一个叫做 Action 的特征,他的值大多都是 Core Term 这是什么呢?
 
Term
 term 是 terminate 的缩写,表示默认动作是终止进程。当进程接收到一个默认动作为 term 的信号时,进程会被立即终止。
 
Core
 core 表示 默认动作是终止进程并生成一个核心转储(core dump)文件。核心转储是一个包含进程在终止时的内存映像的文件,它对于调试程序非常有用,因为它 提供了进程终止时的状态信息。
  咦?就比如,SIGSEGV 段错误 当我的程序非法访问被终结时,被没有产生传说中的核心转储文件呀?这是因为你的服务器默认关闭了该功能,使用指令 ulimit -a 查看:
 
 现在我们使用指令 ulimit -c 4096 开启该功能:
 
现在我们运行下一段程序:
   8 int main()9 {10 11     int *ptr = NULL;12     *ptr = 1;13     14     return 0;15 }
 
程序不负众望地报错并退出了,产生了一个文件:
 
 这个文件可以干嘛呢?当我们的程序出现异常时,相当该文件保存了案发现场,具体的用法是:
- 首先使用 
gdb调试你的程序:

 - 之后输入指令 
core-file your_core

 
可以看到,直接就复原了事故现场。
 
区别
- 进程终止:
term信号会终止进程,但不生成核心转储文件。term信号通常是用于请求进程正常终止的情况。 - 调试信息:
core信号不仅会终止进程,还会生成核心转储文件,这包含了进程的内存映像、寄存器状态、堆栈跟踪等信息,用于调试目的。 
2. 信号的产生
信号是从哪里产生的呢?虽然最后都是操作系统来执行对一个进程发送信号,但是是谁告诉操作系统这样做的呢?
2.1 用户操作 — kill 指令
 
 当我们运行一个程序时,可以通过指令 kill 来让操作系统对该进程发送相应的信号,就比如,我们可以手动发送 SIGKILL 9号 信号将该进程终结,这里有一个程序:
 1 TestSig1.cc                                                                                                                                                                                                  X 1 #include <iostream>2 #include <unistd.h>3 4 5 int main()6 {7     while(true)8     {9         std::cout << "I am Running, my pid is " << getpid() << std::endl;10         sleep(1);11     }12     return 0;13 }
 
该程序会每秒打印相应的内容,现在我们可以使用相关的指令 kill -9 [pid] 来杀掉该进程:
 
 可以看到该进程被杀掉了!!!
 
2.2 用户操作 — 按键操作
 我们也可通过按键来让操作系统发送相关的信号,就比如我们平时终止一个进程的方式更多的是通过键盘按键,就比如 ctrl + c :
 
 其实这个按键对应的就是 3 号信号 SIGQUIT。
 
2.3 用户操作 — 系统调用
 操作系统提供一个系统调用 int kill(pid_t pid, int sig); 该函数你可以想指定进程发送信号:
pid: 表示要发送信号的进程IDsig: 表示要发送的信号- 返回值:成功返回 0 ,失败返回 -1 ,错误码被设置
 
还有一个函数是 int raise(int sig);,该函数是向当前进程发送指定信号,简单来说,相当于简化的 kill => kill(getpid(), int sig);
 
2.4 触发软件条件
 在之前管道的学习中,我们了解到如果 读端被关闭了,写端一直再写,那么操作系统就会认为这是一个坏掉的管道,就会发送 13 号信号 SIGPIPE 终止该进程,这就是触发了某种软件条件。
  现在,在这里先向大家介绍几个非常重要的函数:
signal 函数
 
 该函数允许程序员定义当特定信号发生时,程序应该如何响应, 简单说,这个函数用于捕获特定信号,然后执行指定操作的。sighandler_t signal(int signum, sighandler_t handler);:
signum:指定要处理的信号类型。注意,SIGKILL和SIGSTOP这两个信号不能被捕获、阻塞或忽略。handler:指定信号的处理方式。它可以是一个函数指针,指向一个用户定义的信号处理函数;也可以是SIG_IGN,表示忽略该信号;或者是SIG_DFL,表示采用信号的默认处理方式。- 返回值:成功时,
signal函数返回之前为该信号设置的信号处理函数的指针。如果之前没有为该信号设置过处理函数,则返回SIG_DFL。失败时,返回SIG_ERR,并设置errno以指示错误原因。 
看着描述这么多,其实用起来不复杂,比如,现在我要捕获 2 号信号 SIGINT,他的默认操作是退出,现在我不想要推出,想要执行我的逻辑:
 1 TestSig1.cc                                                                                       X 1 #include <iostream>2 3 #include <unistd.h>4 #include <sys/types.h>5 #include <signal.h>6 7 void signal_handle(int signum)8 {9     std::cout << "I got you signal: " << signum << std::endl;10 }11 12 int main()13 {14 15     // 2号信号的捕获16     signal(2, signal_handle);17 18     while(true)19     {20         std::cout << "I am Running, my pid is " << getpid() << std::endl;21         sleep(1);22     }23     return 0;24 }
 
现在我们使用 ctrl + c 已经不能终止该进程了:
 
你也可将  signal(2, signal_handle); 中的函数换成 SIG_IGN 这样就会忽略该信号。
现在我有个想法就是将全部信号都捕获,在写个死循环,是不是就没有人把我停下来了!!!我们能想到的,人家肯定也想到了,规定 9 号信号 SIGKILL 和 19 号信号 SIGSTOP 这两个信号不能被捕获、阻塞或忽略。保证系统的稳定性和管理员的控制权。
 
alarm 函数
 
 大家为了早起都设置过闹钟吧,闹钟的作用就是时间一到就提醒我们执行某件任务。在 Linux 中的闹钟 alarm 也是一样的,我们设置一个定时,当时间一到执行某项任务,unsigned int alarm(unsigned int seconds);:
seconds:定时器应该等待的秒数。如果seconds是 0,则任何当前设置的定时器都会被取消(你可以同时设置多个闹钟),但不会发送SIGALRM信号。- 返回值:如果之前已经设置了定时器,
alarm函数返回之前设置的剩余时间(秒),直到定时器到期。如果之前没有设置定时器,则返回 0。 
当 alarm 定时器到期时,会向进程发送 SIGALRM 信号,终止进程:
12 int main()13 {14 15     // 设置一个闹钟,执行默认操作16     alarm(2);17 18     while(true)19     {20         std::cout << "I am Running, my pid is " << getpid() << std::endl;21         sleep(1);22     }23     return 0;24 }
 
两秒之后,进程自动终止:
 但更多情况下,我们想要闹钟解释后执行我们的逻辑,而不是终止进程,那咋办呢? 捕获该信号,自定义处理信号,这就需要我们上面说的 signal 函数了:
   7 void signal_handle(int signum)8 {9     std::cout << "Your alarm clock is ringing." << std::endl;10 }11 12 int main()13 {14 15     // 设置一个闹钟,执行默认操作16     alarm(2);17     // 捕获闹钟信号18     signal(SIGALRM, signal_handle);19 20     while(true)21     {22         std::cout << "I am Running, my pid is " << getpid() << std::endl;23         sleep(1);24     }25     return 0;26 }
 
现在闹钟时间到了,就不会终止进程啦!但是,我还有一个疑问,你这个闹钟只能执行一次呀,之后就失效了,我想要一个一直生效的定时任务,怎么做到呢?当捕获并执行自定义函数时再设置一个闹钟不就好啦:
   7 void signal_handle(int signum)8 {9     std::cout << "Your alarm clock is ringing." << std::endl;10     alarm(2);11 }
 
这样就得到一个持续的定时任务啦:
 
 
 在这里的闹钟就是一个触发了软件条件(倒计时),从而产生信号发送给进程!
 
2.4 硬件异常
段错误
 在我们的程序中,很可能涉及到 段错误(非法内存访问),具体触发错误的细节如下:
- 现代计算机使用内存管理单元(
MMU)来管理内存。MMU负责将虚拟地址(程序使用的地址)映射到物理地址(实际内存地址)。 - 当我们尝试访问一个地址时,
MMU尝试将虚拟地址翻译为物理地址,并检查该虚拟地址对应的页表项,以确定是否有权限访问该地址,以及地址是否有效 - 当 
CPU发现该块地址是无效的,或者是不具有写权限的,或者是无权限访问的,将触发异常 - 操作系统向该进程发送 
SIGSEG的信号 
这就是简单的硬件异常触发流程。
3. 信号的保存
现在我们已经基本了解了信号是从哪里来的,那么信号被一个进程接受过后,是以什么形式存在于进程当中呢?
在介绍信号的保存之前,希望大家记住这几个概念:
- 实际执行信号的处理动作称为信号递达(
Delivery) - 信号从产生到递达之间的状态,称为信号未决(
Pending)。 
 信号的信息被保存在一个进程的 task_struct 中:
 
 我们来好好的介绍这 3 个结构:
3.1 block 位图
 Block 位图用于指示哪些信号当前被进程 阻塞。如果一个信号在 Block 位图中对应的位被设置(为 1 ),那么即使该信号已经到达,它也不会被立即处理,而是会 保持在未决状态,直到进程解除对该信号的阻塞。
3.2 pending 位图
 Pending 位图(通常不是直接暴露给用户的,而是作为进程控制块 task_struct 的一部分)用于 跟踪哪些信号已经到达进程但尚未被处理。每个位代表一个信号,如果该位被设置(通常为 1 ),则表示对应的信号已经到达且处于 未决状态。
3.3 handler 函数指针数组
 用户可以通过系统调用来设置特定信号的处理函数。当用户为某个信号注册了一个自定义的处理函数时,操作系统就会将该函数的地址存储在 handler 表中对应信号编号的位置。如果用户没有为某个信号设置自定义处理函数,那么当该信号发生时,操作系统就会 调用默认的处理函数。
所以,我们总结一下,当一个信号传递给进程时,操作系统会将
pending表中该信号对应的值置 1,如果block表中的对应的值 也是 1,代表该信号被阻塞,不会被立即处理,直至解除阻塞;当解除阻塞或者一开始就不是阻塞状态的话,就会执行handle
表中该信号对应的函数操作。
3.4 验证结论
现在我们准备验证我们的想法,我们先阻塞一个信号,然后发送该信号,查看是否执行相关操作,在解除对该信号的阻塞,再次观察现象:
在这里会涉及到对信号集的操作,大家可以简单理解为
对信号集进行的对某个信号的阻塞操作最终会保存到阻塞表中,在这里就不具体说明操作了,感兴趣的小伙伴,我找了一篇比较好的文章 👉信号集操作指南。
6 // 自定义函数7 void signal_handler(int signum)8 {9     std::cout << "Recived signal SIGINT!!!" << std::endl;10 }11 12 int main()13 {14     // 捕获信号15     signal(SIGINT, signal_handler);16 17     sigset_t sigset;18     // 初始化信号集19     sigemptyset(&sigset);20     // 添加指定信号到信号集21     sigaddset(&sigset, SIGINT);22 23     // 阻塞该信号24     if(sigprocmask(SIG_BLOCK, &sigset, NULL) == -1)25     {26         perror("sigprocmask");27     }28 29     std::cout << "SIGINT is blocked. Try pressing Ctrl+C after 5s!!!\n" << std::endl;30     sleep(5);31 32     // 解除阻塞33     if(sigprocmask(SIG_UNBLOCK, &sigset, NULL) == -1)34     {35         perror("sigprocmask");36     }37 38     std::cout << "SIGINT is unblocked. Try pressing Ctrl+C!!!\n" << std::endl;39 40     while(true)41     {42         sleep(1);43     }44 }
 

第一次我们按 ctrl + c 没什么反应,过了 5s 后,函数自动被执行,可以看出我们的结论是正确的。
4. 信号的处理
现在我们知道信号哪里来的了,也知道保存在哪里了,现在我们来看看信号的处理方式。
4.1 执行默认方式
 对于没有为其注册信号处理函数的信号,进程会执行该信号的默认操作。就比如,SIGTERM 信号的默认操作是请求进程终止,而 SIGSEGV(段错误)信号的默认操作是生成core文件并终止进程。
 
4.2 调用信号处理函数
 如果进程为某个信号注册了信号处理函数(也称为信号处理器,上面代码中的 signal_handler 函数),那么当该信号到达时,内核会暂停进程的正常执行流程,转而调用该处理函数。
 
4.3 忽略信号
  进程可以选择忽略某些信号。这意味着当这些信号到达时,进程不会执行任何特别的操作,而是继续执行其当前的代码路径。然而,需要注意的是,并非所有信号都可以被忽略。例如,SIGKILL和SIGSTOP等信号是不能被忽略的。
 
4.4 阻塞信号
 进程可以选择屏蔽某些信号,以 避免在关键操作期间接收到这些信号。通过调用sigprocmask 等系统调用,进程可以设置其信号屏蔽字,以决定哪些信号能够传递到进程中。被屏蔽的信号将保持在未决状态,直到屏蔽被解除后才会被处理。
 
4.5 阻塞和忽略的区别
这两个概念相当容易混淆,从定义上来说:
- 阻塞是指 
进程选择性地阻止某些信号的传递。当这些被阻塞的信号发生时,它们会被内核记录下来(处于未决状态),但不会立即执行信号的处理函数或执行默认操作。 - 忽略是指进程对收到的某些信号 
不执行任何操作,即不调用处理函数也不执行默认操作,而是简单地丢弃这些信号。 
大家可以这样理解:信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。 前者是处于未决的状态,后者是被递达后选择了忽略,不做其他处理。
总结
在这篇文章中介绍了信号的概念,也介绍了信号从哪里来,到哪里去,被接受处理的过程,希望大家有所收获😁。
相关文章:
Linux 操作系统 --- 信号
序言 在本篇内容中,将为大家介绍在操作系统中的一个重要的机制 — 信号。大家可能感到疑惑,好像我在使用 Linux 的过程中并没有接触过信号,这是啥呀?其实我们经常遇到过,当我们运行的进程当进程尝试访问非法内存地址时…...
黑马前端——days09_css
案例 1 页面框架文件 <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv"X-UA-Compati…...
【Python爬虫】技术深度探索与实践
目录 引言 第一部分:Python爬虫基础 1.1 网络基础 1.2 Python爬虫基本流程 第二部分:进阶技术 2.1 动态网页抓取 2.2 异步编程与并发 2.3 反爬虫机制与应对 第三部分:实践案例 第四部分:法律与道德考量 第五部分&#x…...
智启万象|挖掘广告变现潜力,保障支付安全便捷
谷歌致力于为开发者提供 先进的广告变现与支付解决方案 一起回顾 2024 Google 开发者大会 了解如何利用谷歌最新工具和功能 提高变现收入,优化用户体验,保障交易安全 让变现更上一层楼 广告检查器是谷歌 AdMob 平台最新推出的高级测试工具,开…...
函数递归,匿名、内置行数,模块和包,开发规范
一、递归与二分法 一)递归 1、递归调用的定义 递归调用:在调用一个函数的过程中,直接或间接地调用了函数本身 2、递归分为两类:直接与间接 #直接 def func():print(from func)func()func() # 间接 def foo():print(from foo)bar…...
Springboot3 整合swagger
一、pom.xml <dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-api</artifactId><version>2.1.0</version></dependency> 二、application.yml # SpringDoc配置 # springdoc:swa…...
查看同一网段内所有设备的ip
使用命令提示符(CMD)进行扫描 查看本机IP地址 首先通过 ipconfig /all 命令查看本机的IP地址,确定你的网段,例如 192.168.1.。 Ping网段内每个IP地址 接着使用循环命令: for /L %i IN (1,1,254) DO ping -w 1 -n …...
Spark MLlib 特征工程(上)
文章目录 Spark MLlib 特征工程(上)特征工程预处理 Encoding:StringIndexer特征构建:VectorAssembler特征选择:ChiSqSelector归一化:MinMaxScaler模型训练总结Spark MLlib 特征工程(上) 前面我们一起构建了一个简单的线性回归模型,来预测美国爱荷华州的房价。从模型效果来…...
《SPSS零基础入门教程》学习笔记——03.变量的统计描述
文章目录 3.1 连续变量(1)集中趋势(2)离散趋势(3)分布特征 3.2 分类变量(1)单个分类变量(2)多个分类变量 3.1 连续变量 (1)集中趋势 …...
2024年杭州市网络与信息安全管理员(网络安全管理员)职业技能竞赛的通知
2024年杭州市网络与信息安全管理员(网络安全管理员)职业技能竞赛的通知 一、组织机构 本次竞赛由杭州市总工会牵头,杭州市人力资源和社会保障局联合主办,杭州市萧山区总工会承办,浙江省北大信息技术高等研究院协办。…...
SpringBoot参数校验详解
前言 在web开发时,对于请求参数,一般上都需要进行参数合法性校验的,原先的写法时一个个字段一个个去判断,这种方式太不通用了,Hibernate Validator 是 Bean Validation 规范的参考实现,用于在 Java 应用中…...
安全基础学习-SHA-1(Secure Hash Algorithm 1)算法
SHA-1(Secure Hash Algorithm 1)是一种密码学哈希函数,用于将任意长度的输入数据(消息)转换成一个固定长度的输出(哈希值或摘要),长度为160位(20字节)。SHA-1的主要用途包括数据完整性验证、数字签名、密码存储等。 1、SHA-1 的特性 定长输出:无论输入数据长度是多…...
leetcode350. 两个数组的交集 II,哈希表
leetcode350. 两个数组的交集 II 给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可…...
基于YOLOv8的缺陷检测任务模型训练
文章目录 一、引言二、环境说明三、缺陷检测任务模型训练详解3.1 PCB数据集3.1.1 数据集简介3.1.2 数据集下载3.1.3 构建yolo格式的数据集 3.2 基于ultralytics训练YOLOv83.2.1 安装依赖包3.2.2 ultralytics的训练规范说明3.2.3 创建训练配置文件3.2.4 下载预训练模型3.2.5 训练…...
【upload]-ini-[SUCTF 2019]CheckIn-笔记
上传图片木马文件后看到,检查的文件内容,包含<? 一句话木马提示 检查的文件格式 用如下图片木马,加上GIF89a绕过图片和<?检查 GIF89a <script languagephp>eval($_POST[cmd])</script> .user.ini实际上就是一个可以由用…...
uniapp条件编译使用教学(#ifdef、#ifndef)
#ifdef //仅在xxx平台使用#ifndef //除了在xxx平台使用#endif // 结束 标识平台APP-PLUSAPPMP微信小程序/支付宝小程序/百度小程序/头条小程序/QQ小程序MP-WEIXIN微信小程序MP-ALIPAY支付宝小程序MP-BAIDU百度小程序MP-TOUTIAO头条小程序MP-QQQQ小程序H5H5APP-PLUS-NVUEApp nv…...
NXP i.MX8系列平台开发讲解 - 4.1.2 GNSS 篇(二) - 卫星导航定位原理
专栏文章目录传送门:返回专栏目录 Hi, 我是你们的老朋友,主要专注于嵌入式软件开发,有兴趣不要忘记点击关注【码思途远】 文章目录 关注星号公众号,不容错过精彩 作者:HywelStar Hi, 我是你们的老朋友HywelStar, 根…...
怎样在 SQL 中对一个包含销售数据的表按照销售额进行降序排序?
在当今数字化商业的浪潮中,数据就是企业的宝贵资产。对于销售数据的有效管理和分析,能够为企业的决策提供关键的支持。而在 SQL 中,对销售数据按照销售额进行降序排序,是一项基础但极其重要的操作。 想象一下,您面前有…...
DIAdem 与 LabVIEW
DIAdem 和 LabVIEW 都是 NI (National Instruments) 公司开发的产品,尽管它们有不同的核心功能和用途,但它们在工程、测试和测量领域中常常一起使用,以形成一个完整的数据采集、分析、处理和报告生成的解决方案。 1. 功能和用途 LabVIEW (Lab…...
UE虚幻引擎可以云渲染吗?应用趋势与挑战了解
虚幻云渲染技术是基于虚幻引擎的云端渲染技术,将虚幻引擎的渲染计算任务通过云计算的方式进行处理和渲染、并将渲染结果传输到终端设备上进行展示。虚幻引擎云渲染技术在近年来得到了迅猛的发展,并在各个领域得到了广泛的应用,包括游戏、电影…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...
【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...
c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...
【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。
1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj,再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...
《C++ 模板》
目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板,就像一个模具,里面可以将不同类型的材料做成一个形状,其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式:templa…...
PAN/FPN
import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...
如何更改默认 Crontab 编辑器 ?
在 Linux 领域中,crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用,用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益,允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...
