[linux仓库]信号保存[进程信号·肆]

[linux仓库]信号保存[进程信号·肆]


🌟 各位看官好,我是!

🌍 Linux == Linux is not Unix !


🚀 今天来学习Linux的信号保存,明白进程是如何识别信号及相关函数。

👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享更多人哦!

信号保存

对信号产生有了一定的理解后,就可以从时间维度上讲解信号保存的话题

信号相关概念

  • 实际执⾏信号的处理动作称为信号递达(Delivery)
  • 信号从产⽣到递达之间的状态,称为信号未决(Pending)。
  • 进程可以选择阻塞 (Block )某个信号。(屏蔽某个信号 --> 既然能屏蔽就能解除)
  • 被阻塞的信号产⽣时将保持在未决状态,直到进程解除对此信号的阻塞,才执⾏递达的动作.
  • 注意 : 阻塞和忽略是不同的,只要信号被阻塞就不会递达,⽽忽略是在递达之后可选的⼀种处理动作。

 

 

信号是否被阻塞在内核中是用位图进行表示:pending位图表示是否收到信号,block位图表示信号是否被屏蔽.

 

 

进程如何识别信号

信号在内核中的表示示意图:

  • 每个信号都有两个标志位分别表⽰阻塞(block)和未决(pending),还有⼀个函数指针表⽰处理动作。信号产⽣时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例⼦中,SIGHUP信号未阻塞也未产⽣过,当它递达时执⾏默认处理动作。
  • SIGINT信号产⽣过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
  • SIGQUIT信号未产⽣过,⼀旦产⽣SIGQUIT信号将被阻塞,它的处理动作是用户⾃定义函数sighandler。

 

 

这表明什么呢?信号是否收到信号,是否阻塞,用什么方法进行处理.都是围绕这张表进行的啊!

结论:围绕这信号,进程能识别信号,本质是三张表:block表pending表handler表 --> 由理论转实操: 都是对这三张表的操作.

struct task_struct { ... /* signal handlers */ sigset_t blocked struct sigpending pending; ... } struct sigpending { struct list_head list; sigset_t signal; } typedef struct { unsigned long sig[_NSIG_WORDS]; } sigset_t; struct sighand_struct { atomic_t count; struct k_sigaction action[_NSIG]; // #define _NSIG 64 spinlock_t siglock; };

sigset_t

从上图来看,每个信号只有⼀个bit的未决标志, ⾮0即1, 不记录该信号产生了多少次,阻塞标志也是这样表示的。因此, 未决和阻塞标志可以⽤相同的数据类型sigset_t来存储, , 这个类型可以表⽰每个信号的“有效”或“无效”状态, 在阻塞信号集中“有效”和“⽆效”的含义是该信号是否被阻塞, ⽽在未决信号集中“有 效”和“⽆效”的含义是该信号是否处于未决状态。阻塞信号集也叫做当前进程的“屏蔽”。

 // 用户层面设置位图 sigset_t block, oblock;

sigprocmask

int sigprocmask(int how, const sigset_t *_Nullable restrict set, sigset_t *_Nullable restrict oldset);

该函数主要用来检查和修改block位图how:指示如何修改set : 输入型参数 --> 若为非空指针,更该进程的信号屏蔽字oldset : 输出型参数 --> 若为非空指针,返回老图,方便后悔操作

如果oset和set都是非空指针,则先将原来的信号 屏蔽字备份到oset⾥,然后 根据set和how参数更改信号屏蔽字。返回值:若成功则为0,若出错则为-1

假设当前信号屏蔽字为mask,下面为how参数可选值

  

如果调用sigprocmask解除了对当前若⼲个未决信号的阻塞,则在sigprocmask返回前,⾄少将其中⼀个信号递达。

因此,对这三张表的操作,可以通过sigprocmask对block位图进行修改,对pending可以通过进程对pending位图进行修改,sigpending函数可以修改pending位图(如下介绍),signal函数可以设置三种处理行为.

sigpending

 

 

信号集操作函数

sigset_t类型对于每种信号⽤⼀个bit表⽰“有效”或“⽆效”状态, ⾄于这个类型内部如何存储这些bit则依赖于系统实现, 从使⽤者的⻆度是不必关⼼的, 使⽤者只能调⽤以下函数来操作sigset_ t变量.

int sigemptyset(sigset_t *set);

int sigfillset(sigset_t *set);

int sigaddset(sigset_t *set, int signo);

int sigdelset(sigset_t *set, int signo);

int sigismember(const sigset_t *set, int signo);  函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表⽰该信号集不包含任何有效信号。 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表⽰ 该信号集的有效信号包括系统⽀持的所有信号。注意,在使⽤sigset_ t类型的变量之前,⼀定要调 ⽤sigemptyset或sigfillset做初始化,使信号集处 于确定的 状态。初始化sigset_t变量之后就可以在调⽤sigaddset和sigdelset在该信号集中添加或删除某种有效信号。

这四个函数都是成功返回0,出错返回-1。sigismember是⼀个布尔函数,⽤于判断⼀个信号集的有效信号中是否包含 某种 信号,若包含则返回1,不包含则返回0,出错返回-1.

这里提出几层理解:

  • 问题1:屏蔽所有信号 ? 9号信号不可被捕捉,不可被屏蔽 -- done
  • 问题2:如果解除对2号的屏蔽,我也要看到pending位图 1 -> 0 -- done
  • 问题3:2号信号被递达,pending 位图由1置为0,是在递达前还是递达后置为0

为了验证问题2,这里写了一段程序:先对2号信号进行屏蔽,接着我通过kill指令向该进程发送2号信号,应该观察到pending位图由0置1,等待一段时间让该进程对2号信号解除屏蔽,此时信号会被递达,应该观察到pending位图的2号信号由1重新置为0,必要的时候可以加上自定义捕捉查看是否捕捉到2号信号.

为了验证问题3,可以这样处理,如果是递达后处理2号信号时,在自定义捕捉方法中应一直为1,如果为0则证明是递达前.

void PrintPending(sigset_t &pending) { std::cout << "[pid: " << getpid() << "] " << "sigpending list: "; // 右->左, 低->高 , 0000 0000 for (int signo = 31; signo > 0; signo--) { if (sigismember(&pending, signo)) { std::cout << "1"; } else { std::cout << "0"; } } std::cout << "\r\n"; } void handler(int signo) { std::cout << "我获取到了: " << signo << " 信号" << std::endl; // 不要让进程终止 // 在对2号信号捕捉的代码中,获取pending && 打印?? sigset_t pending; sigemptyset(&pending); // 2.1 获取当前进程的pending信号集 sigpending(&pending); // 2.2 不断打印所有的pending信号集中的信号 std::cout << "###########################" << std::endl; PrintPending(pending); std::cout << "###########################" << std::endl; } int main() { // 0.设置2号信号的处理动作,不要让他终止 signal(2, handler); // 1.屏蔽2号信号 // 1.1 用户层面设置位图 sigset_t block, oblock; sigemptyset(&block); // 进行初始化 sigemptyset(&oblock); // 是输出型,也可以不初始化 sigaddset(&block, SIGINT); // 这里的时候,我们有没有设置当前进程的信号屏蔽字?没有! // 1.2将栈上位图设置到内核的信号屏蔽字-->block图 sigprocmask(SIG_SETMASK, &block, &oblock); int cnt = 10; while (true) { sigset_t pending; sigemptyset(&pending); // 2.1获取当前进程的pending信号集 sigpending(&pending); // 2.2不断打印所有pending信号集中的信号 PrintPending(pending); // 验证取消block,看信号是否递达 cnt--; if (cnt == 0) { // 解除对2号的屏蔽 std::cout << "解除对2号的屏蔽啦!" << std::endl; // 取消block sigprocmask(SIG_SETMASK, &oblock, nullptr); } sleep(1); } return 0; } 

总结

本文介绍了Linux系统中信号保存的相关概念和操作。信号从产生到递达之间存在未决状态,进程可通过阻塞位图屏蔽信号。内核使用三张表(block、pending、handler)管理信号状态。文章详细讲解了如何通过sigprocmask函数修改block位图,以及使用sigpending、signal等函数操作信号集。通过实验程序验证了信号屏蔽、解除和递达过程中pending位图的变化,说明信号处理的核心是对这三张表的操作。关键点包括:信号递达前后pending位图的变化、9号信号的特殊性,以及如何通过编程验证信号处理流程。

Read more

从0到1搞懂Linux动静态库制作与底层原理|开发者必备指南

从0到1搞懂Linux动静态库制作与底层原理|开发者必备指南

🔥个人主页:Cx330🌸 ❄️个人专栏:《C语言》《LeetCode刷题集》《数据结构-初阶》《C++知识分享》 《优选算法指南-必刷经典100题》《Linux操作系统》:从入门到入魔 《Git深度解析》:版本管理实战全解 🌟心向往之行必能至 🎥Cx330🌸的简介: 目录 前言: 一、先搞懂:Linux下的库是什么?二进制的“代码积木” 1.1 库的本质 1.2 库的分类与系统位置 1.3 预备工作:自定义库源码 二. 静态库:编译时链接,独立运行 2.1 整体图示:理清思路 2.2 静态库制作流程(Makefile 自动化 ,更简便) 2.3 静态库使用场景与命令

By Ne0inhk
Flutter for OpenHarmony: Flutter 三方库 glob 像在 Linux 终端一样灵活匹配鸿蒙应用文件路径(大规模文件管理神器)

Flutter for OpenHarmony: Flutter 三方库 glob 像在 Linux 终端一样灵活匹配鸿蒙应用文件路径(大规模文件管理神器)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 在 OpenHarmony 应用开发中,处理大规模的文件操作是常见的需求。例如: 1. 清理缓存:需要删除 cache 目录下所有的 .tmp 文件。 2. 多媒体扫描:需要找出 DCIM 目录及其所有子目录下包含 2026-02 的 .jpg 片。 3. 打包工具:需要排除所有 .dart 源文件但保留 .js 产物。 如果使用原生的 Directory.list 配合手写正则匹配,代码不仅晦涩难懂,且效率低下。glob 系统通过标准的通配符(Wildcard)语法(如 **/*.png),为你提供了一套极其直观、强大的跨平台文件定位方案。 一、通配符逻辑解析

By Ne0inhk
手搓简易 Linux 进程池:从 0 到 1 实现基于管道的任务分发系统

手搓简易 Linux 进程池:从 0 到 1 实现基于管道的任务分发系统

🔥草莓熊Lotso:个人主页 ❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》 ✨生活是默默的坚持,毅力是永久的享受! 🎬 博主简介: 文章目录 * 前言: * 一. 核心设计思路 * 二. 代码模块拆解 * 2.1 任务定义与随机任务生成 * 2.2 子进程任务处理逻辑 * 2.3 通道(Channel)类:封装父子进程通信 * 2.4 进程池(ProcesspPool)类:核心管理逻辑 * 2.5 主函数:进程池使用示例 * 三. 关键知识点解析 * 3.1 管道通信原理 * 3.2 轮询负载均衡 * 3.3 进程回收的坑

By Ne0inhk