【Linux】 信号的保存 | 捕捉

对于信号,主要涉及到信号的产生、保存和捕获,之前谈到了信号的产生,这里主要介绍信号产生后如何进行保存和捕捉处理的原理。

一、信号的保存

1.阻塞信号

相关概念

  • 实际执行处理信号的动作称为信号递达Delivery
  • 信号从产生到递达的过程称为信号未决Pending
  • 进程可以阻塞、忽略某个信号。
  • 被阻塞就只有产生和未决,忽略是在递达后的处理。


2.内核结构

之前讲过OS发送信号给进行,会在进程PCB表上的位图标记 0-》1,并回调函数指针的方法。

实际上,内核会位PCB的信号维护三张表Block(阻塞表)、Pending(未决)、handler(函数指针数组)

说明:

  • Pending表就是标记我们之前谈到的位图,0/1标记某一位是否收到信号。
  • Block表也是位图 ,代表某一信号是否被阻塞,对特定的信号进行屏蔽。
  • Handler表是函数指针数组,内容 :0 表示默认 1是忽略,还有用户自定回调函数。

描述这一过程:
在信号没有创建之前,Block表中的某一位先会被设置位0和1,标记是否被阻塞。

信号产生时,会在进程控制块的Pengding表中将对应信号位的0-》1。

再校验Block表,如果表上的比特位是1 ,代表被阻塞,将不会递达。直到阻塞被解除。

总结:

进程task_struct中会维护三张表。三张表共同维护信号的识别。

信号的阻塞,不会影响产生。

一个信号没有被递达,并且接收到多次,pending表会默认最后一次发送的信号。


信号的捕捉

信号在什么时候被处理?

进程从内核态到用户态的时候,进行信号的检测和处理

用户态是一种受控的状态,能访问的资源是有限的。

内核态是OS的一种工作状态,能访问到大部分资源。

系统调用必定发生身份从用户态到内核态的转变,因为我们无法通过用户态进行系统调用,

系统调用是比较费时间的,要避免频繁的系统调用。

用户态和内核态

如何对用户态和内核态进行区分?

在CPU上有一个CR3寄存器,是一个2比特位的。00 01 10 11

1表示内核,3表示用户态

进程如何调用系统调用接口?

用户态只能访问自己的【0,3】GB的内存空间。

内核态能让用户以OS的身份访问【3,4】GB。

进程需要被加载到内存中,OS需要维护进程PCB。实际上我们平时说的页表是【0,3】GB的用户级别页表。每一份进程都需要维护一张

【3,4】GB有对应的内核级页表。因为内核级的内容不会被用户身份访问,所以只需要维护一张内核级页表,这个页表将给CS寄存器保存。

故如果进程需要调用系统调用,就像我们平常调用库函数一样,就是在进程地址空间中跳跃。

先在CR3寄存器中切换身份,然后通过寄存器CS找到内核级页表,就能找到对应的内核内容。


内核如何实现捕捉

进程的信号在合适的时候被处理,从内核转到用户级,先检测再处理。

描述这一过程

  • CPU执行用户代码时,会先以用户态执行,遇到系统调用接口时,会切换身份调用系统调用。执行完成后到进程的PCB内查看信号列表,如果pending表全为0,或者pending为 1 block为 1阻塞也直接返回。
  • 如果pending为1,block为0,且handler存在自定义的方法,则会将内核态切换为用户态,调用用户的方法。(这一切换是为了防止内内容被用户破坏)。执行完毕后会现在内核态检测信号的位置,通过特定系统调用返回。

注意:
在信号调用handler方法时,就会将pending表上的1-》0

如果处理完毕后,pending表上还有信号没被处理,则会执行handler方法。

抽象图帮助记忆

一共会经过四次身份切换,只有在第一次身份切换时,才进行信号的检测与处理!


信号操作函数

sigset_t

位图类型

它在Linux下的定义

#define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
typedef struct
{unsigned long int __val[_SIGSET_NWORDS];
} __sigset_t;typedef __sigset_t sigset_t;
  • 对于panding表,0表示没接收到信号,1表示接收到信号
  • 对于block表,0表示信号没有被阻塞,1表示信号被阻塞

sigset_t函数

因为sigset_t 是位图,它的每一位比特位可以表示pending表和block表的内容,因此我们可以通过逻辑关系的方法修改比特位的内容,但是这样过于繁琐。就由下面这些函数操作位图。

#include <signal.h>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函数:将位图表设置为全0,(清0)
  • sigfillset函数:将位图上的所有比特位置为有效信号(置位)
  • sigaddset函数:将signo的信号置为有效信号
  • sigdelset函数:将signo信号置零
  • sigisemember函数:判断signo信号是否在位图中。

注意:
sigset_t 创建后,需要初始化(置位/清零)

我们操作的表与进程中的表没有关系。需要继续调用系统调用写入。


sigprocmask

sigprocmask函数可以用于读取或更改进程的信号屏蔽字(阻塞信号集),该函数原型如下:

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);

一般而言,阻塞和未决表在进行修改时,都会保存上一张表

参数说明:

  • oset不为空时,读取当前进程的阻塞表输出到oset表中
  • set不为空时,依旧how和set更改当前的阻塞表
  • set和oset都不为空时,会保存旧表更改阻塞表

how参函数

想要如何操作信号屏蔽字,此参数有三个可选值:

  • 1.SIG_BLOCK:就是把对应信号的bit位改为1,即就是阻塞该信号
  • 2.SIG_UNBLOCK:就是把对应信号的bit位改为0,即就是使该信号不阻塞
  • 3.SIG_SETMASK:设置当前信号屏蔽字为set所指向的值

返回值:

  • 调用成功返回0,失败返回-1

sigpending函数

       #include <signal.h>int sigpending(sigset_t *set);

操作pending表,读取当前进程的pending表,通过set返回

成功返回0,失败返回-1


下面是举例运用

使用sigprocmask函数阻塞2号信号和40号信号

要求:阻塞2号信号和40号信号, 分别给进程发送5次2号信号和5次40号信号,观察结果

 1 #include <iostream>2 #include <sys/types.h>3 #include <unistd.h>4 #include <signal.h>5 6 7 void pendingPrin(sigset_t* s)8 {9   for(int i=31;i>0;i--)10   {11     if(sigismember(s,i))12     {13       std:: cout<<1;14     }15     else std::cout<<0;16   }17   std::cout<<std::endl;18   std::cout<<"----------------------------------------------"<<std::endl;19 }20 21 22 void handler(int signo)23 {24   std::cout<<"get a signo: "<<signo<<std::endl;25 }26 27 int main()28 {29   std::cout<<"my pid is: "<<getpid()<<std::endl;30   //自定义处理2信号和40信号31   signal(2,handler);32   signal(40,handler);33   sigset_t set,s;34   sigemptyset(&set);35   sigaddset(&set,2);36   sigaddset(&set,40);37   sigprocmask(SIG_BLOCK,&set,nullptr);38 39   while(true)40   {41     sigpending(&s);42     std::cout<<"pending: ";43     pendingPrin(&s);44     std::cout<<"  block: ";45     pendingPrin(&set);46     sleep(3);47   }48   return 0;49 }                                                                                                                 
~


sigaction

捕捉信号,除了之前谈到的signal之外,sigaction对特定信号捕捉。

#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

 参数说明:

  • signum:要捕捉的信号编号
  • act:不为空,则为用户自定行为。
  • oldact:导出原信号的处理行为。

act和oldact都是sigacgtion类型的结构体,定义如下

struct sigaction {void(*sa_handler)(int);void(*sa_sigaction)(int, siginfo_t *, void *);sigset_t   sa_mask;int        sa_flags;void(*sa_restorer)(void);
};
  • 第一个参数:sa_handler

是一个函数指针 SIG_IGN是,忽略处理, SIG_DEF是默认处理,handler自定义处理。

  • 第二个参数:sa_sigaction

实时信号的处理函数。

  • 第三个参数是:sa_mask

是阻塞信号集,就和之前谈到的阻塞位图一致‘

  • 选项sa_flags

通常设为0

举例使用

  1 #include<iostream>2 #include <signal.h>3 #include <sys/types.h>4 #include <unistd.h>5 6 void handler(int signo)7 {8   std::cout<<"get a signo: "<<signo<<std::endl;9 }10 11 12 int main()13 {14   std::cout<<"get a pid"<<getpid()<<std::endl;15   struct sigaction ac,oac;16   ac.sa_handler=handler;17   ac.sa_flags=0;18   sigemptyset(&ac.sa_mask);19   sigaction(2,&ac,&oac);20   while(true)21   {22     std::cout<<"main running ..."<<std::endl;23     sleep(2);                                                                                                     24   }25   return 0;26 }
~


相关知识

信号的主体部分已经介绍完毕,下面还有几个相关知识点:

volatile关键字

保持内存的可见性

程序提高优化级别(例如debug到relase的转变)会使当前只读变量放进寄存器,而如何后续的变量由信号触发变化,信号变化handler是在内存中,这时候就会对一个变量形成俩份,导致寄存器中保持的不受改变。

volatile关键字声明在类型前,告诉编译器不要做过度的优化,保持内存的可见性。


SIGCHLD信号

看上去不那么实用的信号

为了避免出现僵尸进程,子进程在结束后,父进程需要等待,回收资源。等待方式可以是阻塞等待,也可以是轮询等待。但是这俩种方式都有延迟。

GIGCHILD信号,在子进程结束后,立马会去父进程发送信号,让父进程捕捉信号。

但是这个方法也有缺点,假设有多个子进程同时发送信号,但是父进程,没来得急回收,就会导致信号被阻塞,实际最后子进程不能完全被父进程杀掉。

那就必须调用自己的方法,handler函数不断的调用waitpid。

实际上

父进程调用signal或sigaction函数将SIGCHLD信号的处理动作设置为SIG_IGN,子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。

系统默认的忽略动作和用户用signal或sigaction函数自定义的忽略通常是没有区别的,但这是一个特列。此方法对于Linux可用,但不保证在其他UNIX系统上都可用。故而SIGCHLD是比较不实用的信号。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/453562.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

golang并发安全-sync.Once

什么是sync.Once sync.Once 是 Go 语言中的一种同步原语&#xff0c;用于确保某个操作或函数在并发环境下只被执行一次。它只有一个导出的方法&#xff0c;即 Do&#xff0c;该方法接收一个函数参数。在 Do 方法被调用后&#xff0c;该函数将被执行&#xff0c;而且只会执行一…

计算机网络-差错控制(奇偶校验码 CRC循环冗余码)

文章目录 差错从何而来从传感器层面提高信道比来减少线路本身的随机噪声的一个例子热噪声和冲击噪声 数据链路层的差错控制检错编码-奇偶校验码检错编码-CRC循环冗余码例子注意 差错从何而来 噪声通常指的是任何未预期的、随机的信号干扰&#xff0c;这些干扰可能源自多种物理…

C++项目 -- 高并发内存池(二)Thread Cache

C项目 – 高并发内存池&#xff08;二&#xff09;Thread Cache 文章目录 C项目 -- 高并发内存池&#xff08;二&#xff09;Thread Cache一、高并发内存池整体框架设计二、thread cache设计1.整体设计2.thread cache哈希桶映射规则3.TLS无锁访问4.thread cache代码 一、高并发…

Log360,引入全新安全与风险管理功能,助力企业积极抵御网络威胁

ManageEngine在其SIEM解决方案中推出了安全与风险管理新功能&#xff0c;企业现在能够更主动地减轻内部攻击和防范入侵。 SIEM 这项新功能为Log360引入了安全与风险管理仪表板&#xff0c;Log360是ManageEngine的统一安全信息与事件管理&#xff08;SIEM&#xff09;解决方案…

51单片机之LED灯模块篇

御风以翔 破浪以飏 &#x1f3a5;个人主页 &#x1f525;个人专栏 目录 点亮一盏LED灯 LED的组成原理 LED的硬件模型 点亮一盏LED灯的程序设计 LED灯闪烁 LED流水灯 独立按键控制LED灯亮灭 独立按键的组成原理 独立按键的硬件模型 独立按键控制LED灯状态 按键的抖动 独立按键…

Unity中blendtree和state间的过渡

混合树状态之间的过渡 如果属于此过渡的当前状态或下一状态是混合树状态&#xff0c;则混合树参数将出现在 Inspector 中。通过调整这些值可预览在混合树值设置为不同配置时的过渡表现情况。 如果混合树包含不同长度的剪辑&#xff0c;您应该测试在显示短剪辑和长剪辑时的过渡表…

ubuntu22.04 经常死机,鼠标,键盘无响应

一、现象说明 1. 开机一小时后&#xff0c;突然之间网络掉线&#xff0c;鼠标、键盘无反应。 2.强制重启后&#xff0c;恢复正常。 3.多次重复出现该问题。 二、环境说明&#xff1a;内核、显卡 三、异常日志&#xff1a; /var/log/syslog: 四、问题解答&#xff1a; 1.…

ChatGPT Plus如何升级?信用卡付款失败怎么办?如何使用信用卡升级 ChatGPT Plus?

ChatGPT Plus是OpenAI提供的一种高级服务&#xff0c;它相较于标准版本&#xff0c;提供了更快的响应速度、更强大的功能&#xff0c;并且用户可以优先体验到新推出的功能。 尽管许多用户愿意支付 20 美元的月费来订阅 GPT-4&#xff0c;但在实际支付过程中&#xff0c;特别是…

【项目实践03】【布隆过滤器】

文章目录 一、前言二、项目背景三、实现方案1. 谷歌 布隆过滤器2. Redis 布隆过滤器 四、思路延伸1. 布隆过滤器的实现原理2. 布隆过滤器的一些扩展3. 布谷鸟过滤器 五、参考内容 一、前言 本系列用来记录一些在实际项目中的小东西&#xff0c;并记录在过程中想到一些小东西&a…

神经网络 | 基于 CNN 模型实现土壤湿度预测

Hi&#xff0c;大家好&#xff0c;我是半亩花海。在现代农业和环境监测中&#xff0c;了解土壤湿度的变化对于作物生长和水资源管理至关重要。通过深度学习技术&#xff0c;特别是卷积神经网络&#xff0c;我们可以利用过去的土壤湿度数据来预测未来的湿度趋势。本文将使用 Pad…

基于深度学习的SSVEP分类算法简介

基于深度学习的SSVEP分类算法简介 1、目标与范畴2、深度学习的算法介绍3、参考文献 1、目标与范畴 稳态视觉诱发电位&#xff08;SSVEP&#xff09;是指当受试者持续注视固定频率的闪光或翻转刺激时&#xff0c;在大脑枕-额叶区域诱发的与刺激频率相关的电生理信号。与P300、运…

从领域外到领域内:LLM在Text-to-SQL任务中的演进之路

导语 本文介绍了ODIS框架&#xff0c;这是一种新颖的Text-to-SQL方法&#xff0c;它结合了领域外示例和合成生成的领域内示例&#xff0c;以提升大型语言模型在In-context Learning中的性能。 标题&#xff1a;Selective Demonstrations for Cross-domain Text-to-SQL会议&am…