Linux | 探索 Linux 信号机制:信号的产生和自定义捕捉
信号是 Linux 操作系统中非常重要的进程控制机制,用来异步通知进程发生某种事件。理解信号的产生、阻塞、递达、捕捉等概念,可以帮助开发者更好地编写健壮的应用程序,避免由于未处理的信号导致程序异常退出。本文将带你从基础概念开始,深入探讨信号处理的常见方式。
1. 信号的基本概念
在 Linux 系统中,信号是一种软中断机制,主要用于进程之间的异步通信。信号的产生和递达并不会按照进程的执行顺序发生,而是通过操作系统将某种事件(如用户输入、硬件异常等)通知进程。每个信号都有唯一的编号和宏定义名称。例如,SIGINT
(编号2)是一个常见的信号,通常由按下 Ctrl+C
产生。
信号的产生的四种方式:
- 终端输入产生信号:例如,当用户在终端中按下
Ctrl+C
时,系统会发送SIGINT
信号给当前前台进程,进而终止进程。- 系统调用产生信号:开发者可以通过调用
kill
函数向指定进程发送信号,例如kill -SIGKILL <PID>
。- 软件条件产生信号:某些软件事件会自动触发信号。例如,
SIGPIPE
信号在管道破裂时触发。- 硬件异常产生信号:例如,执行非法内存访问会触发
SIGSEGV
信号。
信号处理一般有三种方式:
- 忽略信号:进程忽略该信号。
- 执行默认动作:进程按照信号的默认处理方式处理,例如
SIGKILL
会导致进程直接退出。- 捕捉信号:进程通过自定义函数捕捉信号并进行处理。
2. 信号的自定义捕捉
Linux 提供了 signal
和 sigaction
系统调用,允许开发者自定义信号的处理函数,即捕捉信号。
a. 自定义捕捉终端产生的 SIGINT 2号信号:
#include <stdio.h>
#include <signal.h>void handler(int sig) {printf("Received signal: %d\n", sig);
}int main() {signal(SIGINT, handler); // 捕捉SIGINT信号while (1) {printf("Waiting for signal...\n");sleep(1);}return 0;
}
程序解释
handler
函数捕捉到 SIGINT
信号并打印信号编号。当用户在终端中按下 Ctrl+C
,进程不会立即终止,而是调用自定义的 handler
函数。
b. 自定义捕捉由软件条件产生的 SIGALRM 信号
SIGALRM
信号通常由 alarm()
函数产生,用于在设定的时间后通知进程。
alarm()
函数与 SIGALRM
信号
alarm()
函数的作用是设置一个闹钟,指定经过若干秒后系统向进程发送 SIGALRM
信号。此信号的默认处理行为是终止进程,但我们可以通过自定义信号处理函数来捕捉并处理 SIGALRM
信号。
alarm()
函数的定义如下:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
- 参数
seconds
:指定在多少秒后发送SIGALRM
信号。如果参数为 0,则取消先前设置的闹钟。 - 返回值:返回先前设置的闹钟还剩余的时间。如果没有设置过闹钟,返回值为 0。例如,设定闹钟为 30 秒,闹钟执行了 20 秒后取消并重设为 15 秒,之前的闹钟还剩下 10 秒,这个时间会作为
alarm()
函数的返回值。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>// 自定义的SIGALRM信号处理函数
void handle_alarm(int sig) {printf("Alarm signal received: %d. Time's up!\n", sig);
}int main() {// 注册SIGALRM信号的处理函数signal(SIGALRM, handle_alarm);// 设置闹钟为1秒alarm(1);// 计数器,在闹钟响起之前一直计数int count = 0;while (1) {printf("Counting: %d\n", count++);sleep(1); // 每秒计数一次}return 0;
}
程序解释
-
信号处理函数
handle_alarm
:
当SIGALRM
信号产生时,操作系统会调用此函数。此函数接收信号编号作为参数,并打印出提示信息。 -
设置闹钟:
程序通过调用alarm(1)
设置了一个 1 秒的闹钟。即 1 秒后,系统会发送SIGALRM
信号,触发信号处理函数handle_alarm
。 -
计数器:
程序进入一个无限循环,每秒钟打印一次计数。在 1 秒内,计数器会输出几次,直到接收到SIGALRM
信号并终止循环。
运行该程序时,输出:
Counting: 0
Alarm signal received: 14. Time's up!
程序开始计数,并且在 1 秒后接收到 SIGALRM
信号,调用信号处理函数,输出“Alarm signal received”信息,随后程序被信号终止。
拓展
-
重新设置闹钟:如果在
SIGALRM
之前再次调用alarm()
,会取消之前的闹钟并重新设置。例如,假如alarm(5)
在alarm(1)
之后被调用,系统将在 5 秒而非 1 秒后发送SIGALRM
信号。 -
取消闹钟:
alarm(0)
可以取消先前设置的闹钟,程序将不会再收到SIGALRM
信号。
3. 信号阻塞与未决信号
信号的三种状态:阻塞、未决和递达
阻塞(Block):
阻塞是指进程可以暂时不处理某些信号。当信号被阻塞时,即使信号产生了,也不会立即处理。信号会保持在阻塞状态,直到解除阻塞。
未决(Pending):
未决是指信号已经产生,但由于被阻塞,无法递达。信号会处于未决状态,等待解除阻塞。当信号解除阻塞后,未决信号会递达给进程。
递达(Delivery):
递达是指信号从产生到被进程处理的过程。当信号未被阻塞或解除阻塞后,信号会递达给进程,触发默认处理动作或自定义的信号处理函数。
进程可以选择阻塞(Block)某个信号。被阻塞的信号在产生时会处于未决状态,直到进程解除对该信号的阻塞后,才会执行相关的处理动作。
注意:阻塞和忽略是不同的概念。阻塞信号意味着信号不会被递达,直到解除阻塞。而忽略则是在信号递达后选择不进行处理的一种方式。
C语言中的信号相关函数
sigset_t
是一个用于表示信号集的数据类型,每个信号用一个位来表示它的状态。这个类型可以用来存储和操作进程的信号屏蔽字(阻塞信号集),以及未决信号集。
-
sigemptyset(sigset_t *set)
: 初始化一个信号集,将其中的所有信号位清零,即该信号集不包含任何信号。 -
sigfillset(sigset_t *set)
: 初始化一个信号集,将其中的所有信号位设置为1,即该信号集包含所有信号。 -
sigaddset(sigset_t *set, int signo)
: 在信号集中添加一个信号,使其对应的位被设置为1。signo
是要添加的信号的编号。 -
sigdelset(sigset_t *set, int signo)
: 从信号集中删除一个信号,使其对应的位被设置为0。signo
是要删除的信号的编号。 -
sigismember(const sigset_t *set, int signo)
: 检查信号集中的某个信号是否被设置为1。返回值为非零表示信号被设置(即有效),为0表示信号未被设置(即无效)。 -
sigprocmask(int how, const sigset_t *set, sigset_t *oset)
: 读取或更改进程的信号屏蔽字(阻塞信号集)。 -
sigpending(sigset_t *set)
: 读取当前进程的未决信号集。
使用 sigprocmask
添加block阻塞集
在某些场景下,进程可能不希望立即处理某个信号,这时可以选择阻塞该信号。当信号被阻塞时,信号会进入未决状态,直到解除阻塞后才会递达。可以使用 sigprocmask
函数来设置信号的阻塞状态。
#include <stdio.h>
#include <signal.h>int main() {sigset_t set;sigemptyset(&set);sigaddset(&set, SIGINT); // 阻塞SIGINT信号sigprocmask(SIG_BLOCK, &set, NULL);printf("SIGINT is blocked, press Ctrl+C...\n");sleep(10); // 期间按下Ctrl+C不会终止程序return 0;
}
SIGINT
信号被阻塞,按下 Ctrl+C
时,进程不会立刻退出,而是被阻塞,不能被递达,需要等到信号被解除阻塞后才能处理信号。
如果将所有信号都使用sigprocmask函数添加到block阻塞位图当中,是不是就能产生一个无法被退出的无敌进程?
答:SIGKILL (9号信号)和 SIGSTOP(19号信号)是特殊的,无法被阻塞、忽略或捕获。我们依然可以通过这些信号杀死进程,所以这样操作并不能使进程成为“无敌”的进程。(操作系统设计者早就想到了~~)
4. 常见信号的递达过程和处理方式
信号的递达过程
-
信号抵达的检查: 当系统从内核态返回用户态时,会首先检查当前进程的未决信号(pending signals)。这时,系统处于内核态,有权限检查进程的信号状态。
-
处理未决信号:
- 如果发现有未决信号,并且该信号没有被阻塞,系统会决定如何处理这些信号。
- 对于默认处理动作或忽略的信号,系统会执行默认动作或忽略信号,然后清除对应的未决标志位。
-
指定信号动作:
- 如果信号的处理动作是用户自定义的,系统会返回用户态,执行用户定义的处理函数。执行完自定义处理函数后,用户态的处理程序会通过
sigreturn
系统调用返回内核态,清除对应的未决标志位。如果没有新的信号要处理,系统会直接返回用户态,从主控制流程中上次被中断的地方继续执行。
- 如果信号的处理动作是用户自定义的,系统会返回用户态,执行用户定义的处理函数。执行完自定义处理函数后,用户态的处理程序会通过
-
为什么执行自定义函数是需要由内核态切换到内核态:
- 尽管内核态具有高权限,但操作系统设计中不允许直接在内核态执行用户代码。原因是用户代码可能包含非法操作,如清空数据库等,这在用户态时权限不足,但在内核态时可能会造成严重后果。
- 操作系统必须确保用户代码的合法性,以防止安全风险。因此,操作系统会严格控制用户代码的执行,确保系统安全和稳定。
信号的处理方式
信号编号 | 宏定义名称 | 默认动作 | 说明 |
---|---|---|---|
1 | SIGHUP | 终止进程 | 终端挂起时发送此信号 |
2 | SIGINT | 终止进程 | 用户按下 Ctrl+C |
9 | SIGKILL | 终止进程(不可捕捉) | 直接终止进程 |
11 | SIGSEGV | 终止进程并生成 core | 段错误,非法内存访问 |
15 | SIGTERM | 终止进程 | 请求进程终止 |
相关文章:

Linux | 探索 Linux 信号机制:信号的产生和自定义捕捉
信号是 Linux 操作系统中非常重要的进程控制机制,用来异步通知进程发生某种事件。理解信号的产生、阻塞、递达、捕捉等概念,可以帮助开发者更好地编写健壮的应用程序,避免由于未处理的信号导致程序异常退出。本文将带你从基础概念开始&#x…...

递归的时间复杂度分析
确定回溯算法的时间复杂度通常比较复杂,因为它取决于搜索空间的大小以及你的剪枝效率。对于生成从1到n的所有长度为k的组合。分析这类算法的时间复杂度时,我们通常需要考虑递归树的所有可能路径。 组合数 生成的组合数量是从n个元素中选择k个的组合数&…...

C++: 二叉树进阶面试题
做每件事之前都心存诚意, 就会事半功倍. 目录 前言1. 根据二叉树创建字符串2. 二叉树的层序遍历Ⅰ3. 二叉树的层序遍历Ⅱ4. 二叉树的最近公共祖先5. 二叉搜索树与双向链表6. 根据一棵树的前序遍历与中序遍历构造二叉树7. 根据一棵树的中序遍历与后序遍历构造二叉树8. 二叉树的…...

【HarmonyOS NEXT】实现网络图片保存到手机相册
【问题描述】 给定一个网络图片的地址,实现将图片保存到手机相册 【API】 phAccessHelper.showAssetsCreationDialog【官方文档】 https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-photoaccesshelper-V5#showassetscreationdialog…...

Pytorch详解-数据模块
Pytorch详解-数据模块 torch.utils.data.Dataset数据交互模块—Dataset的功能示例系列APIsconcatSubsetrandom_splitsampler unsqueeze DataLoaderDataLoader功能支持两种形式数据集读取自定义采样策略自动组装成批数据多进程数据加载自动实现锁页内存(Pinning Memo…...

浅谈openresty
熟悉了nginx后再来看openresty,不得不说openresty是比较优秀的。 对nginx和openresty的历史等在这此就不介绍了。 首先对标nginx,自然有优劣 一、开发难度 nginx: 毫无疑问nginx的开发难度比较高,需要扎实的c/c基础ÿ…...

【学习笔记】2024最新版SpringCloud教程
2024最新版SpringCloud教程 0 前言闲聊开篇简介 1 SpringBoot和SpringCloud版本选型 2 SpringCloud是什么能干吗 3 SpringCloud各组件的停更升级替换说明 4 项目实战之需求说明 5 项目实战之Maven父工程聚合说明和mysql驱动选择 6 项目实战之Mapper4一键生成Dao层代码 …...

Proxyless Service Mesh:下一代微服务架构体系
一、项目背景及意义 在当今的微服务架构中,应用程序通常被拆分成多个独立的服务,这些服务通过网络进行通信。这种架构的优势在于可以提高系统的可扩展性和灵活性,但也带来了新的挑战,比如: 服务间通信的复杂性&#…...

大数据Flink(一百一十八):SQL水印操作(Watermark)
文章目录 SQL水印操作(Watermark) 一、为什么要有WaterMark 二、Watermark解决的问题 三、代码演示 SQL水印操作(Watermark) 一、为什么要…...

【QGC】把QGroundControl地面站添加到Ubuntu侧边菜单栏启动
把QGroundControl地面站添加到Ubuntu侧边菜单栏启动 简介准备工作步骤 1: 创建 Desktop Entry 文件步骤 2: 编辑 Desktop Entry 文件步骤 3: 刷新应用程序菜单步骤 4: 将 QGroundControl 固定到侧边栏 环境: Ubuntu :20.04 LTS 简介 QGroundControl 是…...

PostgreSQL配置主从同步
PostgreSQL配置主从同步 1 主、备库安装postgresql软件 su - pg12 cd /home/pg12/resource tar -zxvf postgresql-12.9.tar.gz cd postgresql-12.9/ ./configure --prefix/home/pg12/soft/ make -j 16 && make install2 主、备库配置环境变量 vi ~/.bash_profile…...

基于python+django+vue的鲜花商城系统
作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于pythondjangovueMySQL的线…...

李飞飞任CEO,空间智能公司World Labs亮相,全明星阵容曝光
人工智能的下个大方向已经出现,标志性学者决定下场创业。 本周五,一个重磅消息引爆了 AI 圈:斯坦福大学计算机科学家李飞飞正式宣布创办 AI 初创公司 ——World Labs,旨在向人工智能系统传授有关物理现实的深入知识。 李飞飞说道&…...

PyTorch详解-可视化模块
PyTorch详解-可视化模块 Tensorboard 基础与使用启动 TensorBoard访问 TensorBoard使用 TensorBoardSummaryWriter类介绍参数说明常用方法 CNN卷积核与特征图可视化参数说明返回值 混淆矩阵与训练曲线可视化混淆矩阵可视化训练曲线绘制 模型参数打印参数说明输出解释 Tensorboa…...

Bootstrap 警告信息(Alerts)使用介绍
本章将讲解警告(Alerts)以及 Bootstrap 所提供的用于警告的 class。警告(Alerts)向用户提供了一种定义消息样式的方式。它们为典型的用户操作提供了上下文信息反馈。 您可以为警告框添加一个可选的关闭按钮。为了创建一个内联的可…...

uniapp(H5)设置反向代理,设置成功后页面报错
设置反向代理后,页面报错图: 反向代理代码:devServer下面就是配置对应的代理,一般这样就没问题了 "h5": {"router": {"mode": "hash"},"devServer": {"port": 517…...

define、typedef和using的使用
define、typedef 和 using 是 C(以及 C 语言中的 define)中用于定义别名或简化复杂类型的三个关键字,但它们各自有着不同的用途和行为。下面将分别对比这三个关键字: 1. #define 定义方式:#define 是预处理指令&…...

vue element时间选择不能超过今天 时间选中长度不能超过7天
背景: 使用elenmet plus 组件实现时间选择;且日期时间选择不能超过今天;连续选中时间的长度范围不能超过7天 效果展示: 实现思路: 一、使用element组件自带的属性和方法; :disabled-date"disabledDate…...

如何 吧一个 一维数组 切分成相同等分,一维数组作为lstm的输入(三维数据)的数据预处理 collate_fn的应用
要将一个一维数组切分成相同等分,你可以使用 Python 的内置功能或者 NumPy 库(如果你处理的是数值数据)。以下是几种不同的方法: 方法3 pad_sequence 结合dataloader 应该是最佳方案 ### 方法 1: 使用 Python 的内置切片功能 如果…...

Remix 学习 - @remix-run/react 中主要的 hooks
在 remix-run/react 中,有几个常用的 hooks,它们帮助你在 Remix 应用中处理路由、数据加载和其他功能。以下是一些主要的 hooks: useLoaderData: 用于获取从 loader 函数中返回的数据。 通常在组件中调用,以便访问路由加载的数据…...

STL之stack
stack容器 - 先进后出” - stack是堆栈容器,是一种的容器。 - 头文件:#include <stack> stack的push()与pop()方法 stack.push(elem);//往栈头添加元素 stack.pop();//从栈头移除第一个元素 stack<int> stkInt; stkInt.push(1);stkInt…...

如何用3个月零基础入门网络安全?_网络安全零基础怎么学习
前 言 写这篇教程的初衷是很多朋友都想了解如何入门/转行网络安全,实现自己的“黑客梦”。文章的宗旨是: 1.指出一些自学的误区 2.提供客观可行的学习表 3.推荐我认为适合小白学习的资源.大佬绕道哈! →点击获取网络安全资料攻略← 一、自学…...

适合学生党开学买的蓝牙耳机?分享开放式耳机排行榜前十名
学生党开学想买耳机的话,我觉得比较适合入手开放式耳机,因为这类耳机佩戴舒适度高,长时间使用也不会感到不适或疲劳,同时保持耳道干爽透气,更加健康卫生,还能提供自然、开阔的音场,音质表现优秀…...

汽车租赁系统1.0版本
汽车租赁系统1.0版本比较简陋,以后还会有2.0、3.0……就像《我爱发明》里面的一代机器二代机器,三代机器一样,是一个迭代更新的过程(最近比较忙,可能会很久),这个1.0版本很简陋,也请…...

DockerDocker Compose安装(离线+在线)
Docker&Docker Compose安装(离线在线) Docker离线安装 下载想要安装的docker软件版本:https://download.docker.com/linux/static/stable/x86_64/ 如目标机无法从链接下载,可以在本机下载后 scp docker版本压缩包[如docker-20.10.9.tgz] usernameh…...

【泰克生物】酵母展示建库技术解析:构建高质量抗体文库的实用指南
酵母展示库是抗体酵母展示服务的核心组成部分。酵母展示技术利用酵母细胞表面的展示系统,将目标蛋白质(如抗体)展示在细胞膜上。这一过程首先涉及到将抗体基因克隆到酵母表达载体中。随后,表达载体被转化到酵母细胞中,…...

QT Mode/View之View
目录 概念 使用已存在的视图 使用模型 使用模型的多个视图 处理元素的选择 视图间共享选择 概念 在模型/视图架构中,视图从模型中获取数据项并将它们呈现给用户。数据的表示方式不必与模型提供的数据表示形式相似,而且可能与用于存储数据项的底层数…...

URP 线性空间 ui资源制作规范
前言: 关于颜色空间的介绍,可参阅 unity 文档 Color space URP实现了基于物理的渲染,为了保证光照计算的准确,需要使用线性空间; 使用线性空间会带来一个问题,ui资源在unity中进行透明度混合时ÿ…...

如何精确统计Pytorch模型推理时间
文章目录 0 背景1 精确统计方法2 手动synchronize和Event适用场景 0 背景 在分析模型性能时需要精确地统计出模型的推理时间,但仅仅通过在模型推理前后打时间戳然后相减得到的时间其实是Host侧向Device侧下发指令的时间。如下图所示,Host侧下发指令与De…...

Mybatis-plus-Generator 3.5.5 自定义模板支持 (DTO/VO 等) 配置
随着项目节奏越来越快,为了减少把时间浪费在新建DTO 、VO 等地方,直接直接基于Mybatis-plus 这颗大树稍微扩展一下,在原来生成PO、 DAO、Service、ServiceImpl、Controller 基础新增。为了解决这个问题,网上找了一堆资料ÿ…...