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

【Linux庖丁解牛】— 保存信号!

1. 信号其他相关常见概念

• 实际执行信号的处理动作称为信号递达(Delivery)

• 信号从产生到递达之间的状态,称为信号未决(Pending)。

• 进程可以选择阻塞 (Block )某个信号。 

• 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.

• 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的⼀种处理动 作。

我们光知道这些概念还不够,我们接下来需要知道这些概念在系统内核中的具体体现,接下来我们来看看进程PCB中的三张表:

我们首先来看到pending表,这张表实际上就是unsigned int pending,就是一张位图【比特位的位置表示信号的编号,内容表示是否收到信号】,这张表的本质作用就是记录进程收到的信号

我们再来看到第二张表block表,这张表实际上也是unsigned int block,也是一张位图【比特位的位置表示信号的编号,内容表示是信号是否阻塞】。pending表和block表就可以决定一个进程的某个信号是否收到,是否阻塞,是否未决,是否递达。

最后我们看到第三张表handler表,该表本质上是函数指针数组,数组的类型为sighandler_t*,还记得我们的自定义信号处理方法吗:

该表的下标记录信号的编号,内容就是信号所对应的处理方式。所以:自定义信号处理方式的本质就是修改handler表!!

> 我们再来认识两个宏:SIG_IGN【ignore】 & SIG_DEL【default】

很好理解,SIG_IGN就是忽略处理的意思,ING_DEL就是恢复默认处理的意思。

下面简单写个代码使用一下:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <functional>
#include <vector>void hander(int sig)
{std::cout<<"\nhello sig->"<<sig<<std::endl;signal(sig,SIG_DFL);std::cout<<"恢复默认处理动作\n";
}int main()
{signal(2,hander);//忽略处理// signal(2,SIG_IGN);while(true){printf("haha\n");sleep(1);}return 0;
}

2. 信号集及其操作函数

2.1 sigset_t

从上图来看,每个信号只有⼀个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型 可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号 是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽” 应该理解为阻塞而不是忽略。

2.2 信号集操作函数

sigset_t类型对于每种信号用⼀个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些 bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_t变量,而不应该对它的内部数据做任何解释,比如用printf直接打印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。

> sigprocmask & sigpending 

调用函数sigprocmask可以读取或更改进程的信号屏蔽字【阻塞信号集】。

 第一个参数可以传以下三个参数:

 第二个参数传我们设置的信号集即可,第三个参数是输出型参数,获取旧的信号集。

下面看一下sigpending

该函数的作用就是读取当前进程的未决信号集,set即输出型参数。

> demon代码

接下来我们来写一个demon代码来将上面的函数使用一下,先说一下带代码的具体逻辑:先把当前进程的2号信号屏蔽,然后把pending位图循环打印输出,我们不断发送信号就可以看到pending的变化。

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <functional>
#include <vector>void Print(sigset_t &pending)
{printf("我是一个进程(0x%0x),pending:", getpid());for (int i = 31; i >= 1; i--){if (sigismember(&pending, i)){std::cout << 1;}elsestd::cout << 0;}printf("\n");
}int main()
{// 屏蔽信号sigset_t block;sigset_t old_block;// 初始化sigemptyset(&block);sigemptyset(&old_block);// 将2号信号设置进block中sigaddset(&block, 2);// 完成屏蔽sigprocmask(SIG_SETMASK, &block, &old_block);while (true){sigset_t pending;sigpending(&pending);Print(pending);sleep(1);}return 0;
}

现在,如果我们将所有的信号屏蔽,那么所有信号不都是无法被递达了吗??那这个进程不就是无法被杀掉了吗??其实不然,9号信号既无法被捕捉,也无法被屏蔽!!

现在,我们基于以上代码10秒解除对2号信号的屏蔽,然后我们希望看到2号信号被递达:

 这里还有一个问题:解除对2号信号的屏蔽之后,一旦2号信号被递达,那么pending是被立即修改【在处理动作完成之前】,还是在处理动作完成之后呢??做个实验便可得出结论了

预期结果:如果在捕捉动作中打印的pending 全为0,则pending是被立即修改【在处理动作完成之前】,否则,pending在处理动作完成之后修改。

3. core VS term

进程收到信号退出的方式有core和term两种,但这两种方式有什么区别呢??

core是核心意思,一旦进程以core方式退出,那么系统就会在当前路径下形成一个文件。进程异常退出的时候,会将进程在内存中存储的核心数据拷贝到磁盘中形成一个文件!!之后再进程退出,我们将这种机制叫做核心转储用来支持调试debug。而term就是直接进程退出。

我们之所以之前没有看到这个形成文件的行为,是因为在我们云服务器上默认是关闭这个机制的【原因:……】。

现在我们打开:

不过,为什么要有核心转储机制呢?? 

主要是为了支持debug调试,当我们的程序崩溃时,可以使用gdb命令中的core-file core快速定位到崩溃的代码!!!

说到这里,不知道各位是否还记得这张图

0表示进程正常退出,那是因为我们的信号只有1~31,而没有0号信号!!低7位记录信号,第八位记录是否core dump。

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <functional>
#include <vector>
#include <wait.h>int main()
{pid_t id = fork();if (id == 0){printf("hahaha\n");printf("hahaha\n");printf("hahaha\n");printf("hahaha\n");int a = 10;a /= 0;printf("hahaha\n");printf("hahaha\n");printf("hahaha\n");}int status=0;int n=waitpid(id,&status,0);printf("signal->%d exit_code->%d core_dump->%d\n",status&0x7F,(status>>8)&0xFF,(status>>7)&0x1);return 0;
}

http://www.xdnf.cn/news/15617.html

相关文章:

  • SAP学习笔记 - 开发45 - RAP开发 Managed App New Service Definition,Metadata Extension
  • C++中list各种基本接口的模拟实现
  • 25、企业能源管理(Energy):锚定双碳目标,从分类管控到智能优化的数字化转型之路
  • npu-smi info命令参数解释
  • C++-linux系统编程 8.进程(三)孤儿进程、僵尸进程与进程回收
  • 数据结构之单链表
  • Java :List,LinkedList,ArrayList
  • sqli-labs靶场通关笔记:第17关 POST请求的密码重置
  • 连接new服务器注意事项
  • kiro, 新款 AI 编辑器, 简单了解一下
  • Java基础(八):封装、继承、多态与关键字this、super详解
  • 笔试——Day8
  • Scrapy扩展深度解析:构建可定制化爬虫生态系统的核心技术
  • 直播数据统计:如何让数据为我们所用?
  • CommunityToolkit.Mvvm IOC 示例
  • C++回顾 Day8
  • 一文深入:AI 智能体系统架构设计
  • 简单工厂设计模式
  • QT 中各种坑
  • 算法学习day16----Python数据结构--模拟队列
  • haproxy负载均衡
  • 【雅思播客016】New Year Resolution 新年决心
  • vue实现el-table-column中自定义label
  • 深入理解C++11 std::iota:从原理到实践
  • Oracle日期时间函数说明及与MySql区别说明
  • 028_分布式部署架构
  • lanch4j将jar转成exe
  • Mac IDEA启动报错:Error occurred during initialization of VM
  • WPF中的ListBox详解
  • 国内第一梯队终端安全产品解析:技术与场景实践