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

Linux进程信号——信号保存

目录

1、再次认识信号

1.1、概念

1.2、感性理解

1.3、在内核中的表示

1.4、sigset_t 信号集

2、信号集操作函数

2.1、增删改查

2.2、sigprocmask 函数

2.3、sigpending 函数

1、再次认识信号

在这篇文章中,我们将进一步补充和理解 信号传递 过程中的一些关键概念。

1.1、概念

信号传递过程:信号从产生到执行的过程可以分为三部分:

  1. 信号产生(Produce):信号的发出有四种主要方式:键盘输入、系统调用、软件条件和硬件异常。

  2. 信号未决(Pending):信号从产生到执行的中间状态,指信号已产生,但未被立即处理。

  3. 信号递达(Delivery):信号递达后,进程会根据信号的处理方式来执行相应的动作。

信号阻塞(Block):指信号在任何阶段的传递过程中,信号的处理被暂停。阻塞的信号无法继续处理,直到解除阻塞。

1.2、感性理解

可以通过网购的过程来比喻信号的传递过程:

  • 信号产生:在购物平台上完成订单(信号的产生)。

  • 信号未决:订单在运输途中(信号的未决状态)。

  • 信号递达:快递送达后,用户根据自己的情况进行处理(信号递达后的处理动作)。

  • 信号阻塞:假设快递运输中发生堵车(信号传递被阻塞)。

在购物中,我们下单后,物流信息(未决信息)会记录,但快递可能因堵车导致延迟(信号的阻塞)。而信号的递达过程就类似快递送达后用户进行处理的动作,比如默认拆快递、忽略或退货等。

1.3、在内核中的表示

对于传递中的信号来说,需要存在三种状态表达:

  1. 信号是否阻塞
  2. 信号是否未决
  3. 信号递达时的执行动作

在内核中,每个进程都需要维护这三张与信号状态有关的表:block 表、pending 表、handler 表

所谓的 block 表 和 pending 表 其实就是 位图结构

一个 整型 int 就可以表示 31 个普通信号(实时信号这里不讨论)

  • 比如 1 号信号就是位图中的 0 位置处,0 表示 未被阻塞/未产生未决,1 则表示 阻塞/未决
  • 对于信号的状态修改,其实就是修改 位图 中对应位置的值(0/1
  • 对于多次产生的信号,只会记录一次信息(实时信号则会将冗余的信号通过队列组织)

如何记录信号已产生 -> 未决表中对应比特位置置为 1 ?

1.假设已经获取到了信号的 pending 表
2.只需要进行位运算即可:pending |= (1 << (signo - 1))
3.其中的 signo 表示信号编号,-1 是因为信号编号从 1 开始,需要进行偏移


如果想要取消 未决 状态也很简单:pending &= (~(1 << (signo - 1)))
至于 阻塞 block 表,与 pending 表 一模一样

 对于上图的解读:

1.SIGHUP 信号未被阻塞,未产生,一旦产生了该信号,pending 表对应的位置置为 1,当信号递达后,执行动作为默认
2.SIGINT 信号被阻塞,已产生,pending 表中有记录,此时信号处于阻塞状态,无法递达,一旦解除阻塞状态,信号递达后,执行动作为忽略该信号
3.SIGQUIT 信号被阻塞,未产生,即使产生了,也无法递达,除非解除阻塞状态,执行动作为自定义
阻塞 block 与 未决 pending 之间并没很强的关联性,阻塞不过是信号未决的延缓剂

  • 信号在 产生 之前,可以将其 阻塞,信号在 产生 之后(未决),依然可以将其 阻塞

于 handler 表是一个 函数指针表,格式为:返回值为空,参数为 int 的函数

可以看看 默认动作 SIG_DEL 和 忽略动作 SIG_IGN 的定义

/* Type of a signal handler.  */
typedef void (*__sighandler_t) (int);/* Fake signal functions.  */
#define SIG_ERR	((__sighandler_t) -1)		/* Error return.  */
#define SIG_DFL	((__sighandler_t) 0)		/* Default action.  */
#define SIG_IGN	((__sighandler_t) 1)		/* Ignore signal.  */

 默认动作就是将 0 强转为函数指针类型,忽略动作则是将 1 强转为函数指针类型,分别对应 handler 函数指针数组表中的 01 下标位置;除此之外,还有一个 错误 SIG_ERR 表示执行动作为 出错。

简单对这三张表作一个总结,task_struct 中存在:

1.block 表(位图结构)比特位的位置,表示哪一个信号;比特位的内容代表 是否 对应信号被阻塞
2.pending 表(位图结构)比特位的位置,表示哪一个信号;比特位的内容代表 是否 收到该信号
3.handler 表(函数指针数组)该数组的下标,表示信号编号;数组的特定下标的内容,表示该信号递达后的执行动作

1.4、sigset_t 信号集

无论是 block 表 还是 pending 表,都是一个位图结构,依靠 除、余 完成操作,为了确保不同平台中位图操作的兼容性,将信号操作所需要的 位图 结构封装成了一个结构体类型,其中是一个 无符号长整型数组

/* A `sigset_t' has a bit for each signal.  */# define _SIGSET_NWORDS	(1024 / (8 * sizeof (unsigned long int)))
typedef struct{unsigned long int __val[_SIGSET_NWORDS];} __sigset_t;#endif

 注:_SIGSET_NWORDS 大小为 32,所以这是一个可以包含 32 个 无符号长整型 的数组,而每个 无符号长整型 大小为 4 字节,即 32 比特,至多可以使用 1024 个比特位

sigset_t 是信号集,其中既可以表示 block 表信息,也可以表示 pending 表信息,可以通过信号集操作函数进行获取对应的信号集信息;信号集 的主要功能是表示每个信号的 “有效” 或 “无效” 状态 

block 表 通过信号集称为 阻塞信号集或信号屏蔽字(屏蔽表示阻塞),pending 表 通过信号集中称为 未决信号集 

如何根据 sigset_t 位图结构进行比特位的操作? 

  • 假设现在要获取第 127 个比特位
  • 首先定位数组下标(对哪个数组操作)127 / (8 * sizeof (unsigned long int)) = 3
  • 求余获取比特位(对哪个比特位操作):127 % (8 * sizeof (unsigned long int)) = 31 
  • 对比特位进行操作即可
    • 假设待操作对象为 XXX
    • 置 1XXX._val[3] |= (1 << 31)
    • 置 0XXX._val[3] &= (~(1 << 31))

所以可以仅凭 sigset_t 信号集,对 1024 个比特位进行任意操作,关于 位图 结构的实现后续介绍

2、信号集操作函数

对于信号的产生或阻塞,实质上就是对 blockpending 两个表的增、删、改、查操作。

2.1、增删改查

对于位图(bit map)的增删改查操作如下:

  • | 操作,将比特位置为 1。

  • & 操作,将比特位置为 0。

  • :灵活使用 |& 操作。

  • :判断指定比特位是否为 1。

直接操作位图并不推荐,操作系统提供了一些系统接口来对信号集进行操作。

信号集操作函数

#include <signal.h>int sigemptyset(sigset_t *set);	// 初始化信号集
int sigfillset(sigset_t *set);	// 填充信号集
int sigaddset(sigset_t *set, int signum);	// 增加信号
int sigdelset(sigset_t *set, int signum);	// 删除信号
int sigismember(const sigset_t *set, int signum);	// 查找信号是否在信号集中

这些函数返回值为 0 表示成功,-1 表示失败,并设置错误码。

  • sigemptyset:初始化信号集,使信号集为空。

  • sigfillset:将信号集填满,即将所有信号都添加到集里。

  • sigaddset:将指定的信号加入信号集。

  • sigdelset:将指定的信号从信号集中删除。

  • sigismember:检查指定的信号是否在信号集内。

2.2、sigprocmask 函数

sigprocmask 函数用于对 block 表 进行操作,即控制信号的屏蔽状态。

sigprocmask 函数原型

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • how:表示操作的类型,支持三种操作:

    • SIG_BLOCK:将 set 信号集中的信号添加到当前进程的阻塞表(屏蔽信号)。

    • SIG_UNBLOCK:解除 set 信号集中的信号的阻塞状态。

    • SIG_SETMASK:将当前进程的阻塞信号表设置为 set 信号集。

  • set:待操作的信号集,包含待操作的信号。

  • oldset:保存当前进程的屏蔽信号集,操作完成后,可以用来恢复之前的状态。

示例程序 1:将 SIGINT(2号信号)信号阻塞,尝试通过键盘输入 Ctrl+C 发出信号。

#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;int main() {sigset_t set, oset;sigemptyset(&set);sigemptyset(&oset);sigaddset(&set, SIGINT);	// 阻塞 SIGINT 信号sigprocmask(SIG_BLOCK, &set, &oset);  // 设置当前进程的屏蔽信号集while (true) {cout << "我是一个进程,我正在运行" << endl;sleep(1);}return 0;
}

现象

  • SIGINT 信号被阻塞后,进程无法接收到 Ctrl + C 产生的信号,进程将继续运行。

示例程序 2:在程序运行五秒后,解除阻塞状态。

#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;int main() {sigset_t set, oset;sigemptyset(&set);sigemptyset(&oset);sigaddset(&set, SIGINT);	// 阻塞 SIGINT 信号sigprocmask(SIG_BLOCK, &set, &oset);  // 设置当前进程的屏蔽信号集int n = 0;while (true) {if (n == 5) {// 解除阻塞,恢复原来的屏蔽信号集sigprocmask(SIG_SETMASK, &oset, nullptr);}cout << "我是一个进程,我正在运行" << endl;n++;sleep(1);}return 0;
}

现象

  • SIGINT 信号被阻塞五秒钟后,解除阻塞,信号递达,程序终止。

2.3、sigpending 函数

sigpending 函数用于获取当前进程的 未决信号集,即当前被产生但尚未递达的信号。

sigpending 函数原型

int sigpending(sigset_t *set);
  • 返回值:成功返回 0,失败返回 -1 并设置错误码。

示例程序:展示进程的未决信号集。

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cassert>
using namespace std;static void DisplayPending(const sigset_t pending) {cout << "当前进程的 pending 表为: ";for (int i = 1; i < 32; i++) {if (sigismember(&pending, i))cout << "1";elsecout << "0";}cout << endl;
}int main() {sigset_t set, oset;sigemptyset(&set);sigemptyset(&oset);sigaddset(&set, SIGINT);	// 阻塞 SIGINT 信号sigprocmask(SIG_BLOCK, &set, &oset);  // 设置当前进程的屏蔽信号集int n = 0;while (true) {if (n == 5) {sigprocmask(SIG_SETMASK, &oset, nullptr);  // 恢复原来的屏蔽信号集}sigset_t pending;sigemptyset(&pending);int ret = sigpending(&pending);assert(ret == 0);DisplayPending(pending);n++;sleep(1);}return 0;
}

现象

  • 当信号发出并被阻塞时,信号会进入 未决状态。在解除阻塞后,信号会递达并处理。

  • sigpending 函数显示当前进程中哪些信号处于未决状态。

总结: 

  • 信号集操作:通过 sigprocmasksigpending 等函数,可以方便地对进程的信号屏蔽(阻塞)表和未决信号集进行操作。

  • 阻塞和未决状态:信号在阻塞期间无法递达,直到解除阻塞后,信号才会递达并处理。

  • sigpending 函数:提供了获取未决信号集的功能,帮助开发者查看信号是否已被阻塞并未递达。

http://www.lryc.cn/news/599458.html

相关文章:

  • RWA项目面临的主要风险有哪些?例如市场风险、技术风险和法律风险。
  • 比特币技术简史 第九章:比特币的未来 - 发展趋势、挑战和机遇
  • Linux: network: wireshark: esp attempt to detec null-encrypted esp payloads
  • Linux: network: wireshark: tcp的segment重组是怎么判断出来的
  • Comfyui中Upscale Image By 几种放大方法的区别
  • Oracle转Mysql建表脚本
  • mysql 和oracle的选择
  • 【矩阵专题】Leetcode48.旋转图像(Hot100)
  • 【源力觉醒 创作者计划】ERNIE-4.5-VL-28B-A3B 模型详解:部署、测试与 Qwen3 深度对比测评
  • leetcode_122 买卖股票的最佳时机II
  • Axios基本使用
  • 分别使用 Java 8 和 Python 调用 Elasticsearch 接口简单获取数据
  • Web前端:JavaScript 随机点名系统案例详解
  • 常用设计模式系列(十二)—享元模式
  • OpenTelemetry学习笔记(十二):在APM系统中,属性的命名空间处理遵循规则
  • 基于讯飞星火AI的文学作品赏析系统开发实战:从通用聊天到专业文学分析的完整技术方案
  • 新房装修是中央空调还是壁挂空调好?
  • 滑动窗口---6(稍难)
  • GDB调试命令学习
  • 【开源软件】SimpleAI一款轻量级的桌面随身AI助手
  • 航段导航计算机 (Segment_Navigator) 设计与实现
  • OSPF 协议(多区域)
  • Python智能优化算法实战指南
  • 汪小菲食通达公司成立新零售公司,布局餐饮零售新赛道
  • 轻量级音乐元数据编辑器Metadata Remote
  • SpringBoot整合Liquibase提升数据库变更的可控性、安全性、自动化程度(最详细)
  • 自动化UI测试工具TestComplete的AI双引擎:即时数据集 + 自愈测试
  • SpringBoot学习路径二--Spring Boot自动配置原理深度解析
  • Qt 多媒体开发:音频与视频处理
  • 剪映将绿幕视频扣成透明背景视频转webm格式可以在网页上透明播放