【Linux】 信号的保存 | 捕捉

news2024/11/26 2:29:02

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

一、信号的保存

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.coloradmin.cn/o/1433518.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机器学习笔记-EM+HMM

机器学习笔记-EMHMM EM算法-期望最大化算法HMM算法-马尔可夫状态 EM算法-期望最大化算法 计算期望和求极大值&#xff1a;数据缺失下的参数估计 HMM算法-马尔可夫状态 从一个状态到另外一个状态的随机过程。

PDF控件Spire.PDF for .NET【安全】演示:检测 PDF 文档是否受密码保护

Spire.PDF for .NET 是一款独立 PDF 控件&#xff0c;用于 .NET 程序中创建、编辑和操作 PDF 文档。使用 Spire.PDF 类库&#xff0c;开发人员可以新建一个 PDF 文档或者对现有的 PDF 文档进行处理&#xff0c;且无需安装 Adobe Acrobat。 E-iceblue 功能类库Spire 系列文档处…

支持多字体、静动态的.NET图片验证码的开源项目

上次分享过 SkiaSharp 这个开源图形项目&#xff0c;并举了一个生成验证码的例子&#xff0c;具体见文章&#xff1a;《SkiaSharp&#xff1a;.NET强大而灵活的跨平台图形库》。 但文中验证码比较简单&#xff0c;刚好看到一个非常不错的图片验证码&#xff0c;分享给大家。 …

Java 使用 ant.jar 执行 SQL 脚本文件

Java 使用 ant.jar 执行 SQL 脚本文件&#xff0c;很简单。 在 pom.xml 中导入 ant 依赖 <dependency><groupId>org.apache.ant</groupId><artifactId>ant</artifactId><version>1.10.11</version> </dependency>sql 脚本文件…

STM32TIM定时器(1)

文章目录 前言一、介绍部分TIM简介了解定时器类型基本定时器框图通用定时器框图高级定时器框图定时器级联关系 所需简化定时器中断流程图时序部分预分频器时序计数器时序无影子寄存器计数器时序有影子寄存器计数器时序 时钟树 二、实例部分使用定时器计数使用对射红外传感器来控…

清华系面壁MiniCPM:国产AI模型新突破,2B小钢炮成本效率双优

前言 在人工智能的快速发展中&#xff0c;模型的规模和性能成为衡量先进技术的关键指标。最近&#xff0c;清华系创业团队面壁智能发布的面壁MiniCPM模型&#xff0c;以其2B&#xff08;24亿&#xff09;参数的“小钢炮”身份&#xff0c;成功挑战了70亿参数的国际大模型Mistr…

开关电源学习之Buck电路

一、引言 观察上方的电路&#xff0c;当开关闭合到A点时&#xff0c;电流流过电感线圈&#xff0c;形成阻碍电流流过的磁场&#xff0c;即产生相反的电动势&#xff1b;电感L被充磁&#xff0c;流经电感的电流线性增加&#xff0c;在电感未饱和前&#xff0c;电流线性增加&…

k8s-常用工作负载控制器(更高级管理Pod)

一、工作负载控制器是什么&#xff1f; 二、Deploymennt控制器&#xff1a;介绍与部署应用 部署 三、Deployment控制器&#xff1a;滚动升级、零停机 方式一&#xff1a; 通个加入健康检查可以&#xff0c;看到&#xff0c;nginx容器逐个被替代&#xff0c;最终每个都升级完成&…

Dell服务器iDRAC9忘记密码, 通过RACADM工具不重启 重置密码

系列文章目录 文章目录 系列文章目录前言一、RACADM工具二、linux环境1.解压安装RACADM工具测试RACADM工具重置iDRAC密码 Windows环境 前言 一、RACADM工具 RACADM工具 官网参考信息 https://www.dell.com/support/kbdoc/zh-cn/000126703/%E5%A6%82%E4%BD%95-%E9%87%8D%E7%BD…

钉钉群机器人-发送群消息

1、钉钉群创建机器人 添加完成后&#xff0c;要记住 Webhook 路径&#xff1b; 2、机器人接入文档网址 自定义机器人接入 - 钉钉开放平台 3、JAVA代码 import com.dingtalk.api.DefaultDingTalkClient; import com.dingtalk.api.DingTalkClient; import com.dingtalk.api.re…

[SWPUCTF 2021 新生赛]easyupload1.0

发现是上传文件第一想到是文件木马 <?php eval ($_POST[123]);?>木马上传burp修改后缀发现flag里面这个是假的 我们猜想是在phpinfo我们上传<?php eval(phpinfo(););?>木马上传burp修改后缀里面 CtrlF 发现flag

Java技术栈 —— Hive与HBase

Java技术栈 —— Hive与HBase 一、 什么是Hive与HBase二、如何使用Hive与HBase&#xff1f;2.1 Hive2.1.1 安装2.1.2 使用2.1.2.1 使用前准备2.1.2.2 开始使用hive 2.2 HBase2.2.1 安装2.2.2 使用 三、Apache基金会 一、 什么是Hive与HBase 见参考文章。 一、参考文章或视频链…

2024.2.4 模拟实现 RabbitMQ —— 实现核心类

目录 引言 创建 Spring Boot 项目 编写 Exchange 实体类 编写 Queue 实体类 编写 Binding 实体类 编写 Message 实体类 引言 上图为模块设计图 此处实现核心类为了简便&#xff0c;我们引用 Lombok&#xff08;可点击下方链接了解 Lombok 的使用&#xff09; IDEA 配置 L…

滑动列表(scroll view)

使用scroll view实现单行滑动列表。 只需要横向滑动&#xff0c;取消Scroll Rect的Vertical&#xff0c;并取消掉Scrollbar。 为Content添加两个组件 Grid Layout Group设置子节点布局 Content Size Fitter自适应Content大小 Padding:子节点与边界的距离。 Cell Size:子节点…

NLP_语言模型的雏形N-Gram

文章目录 N-Gram 模型1.将给定的文本分割成连续的N个词的组合(N-Gram)2.统计每个N-Gram在文本中出现的次数&#xff0c;也就是词频3.为了得到一个词在给定上下文中出现的概率&#xff0c;我们可以利用条件概率公式计算。具体来讲&#xff0c;就是计算给定前N-1个词时&#xff0…

DFS——连通性和搜索顺序

dfs的搜索是基于栈&#xff0c;但一般可以用用递归实现&#xff0c;实际上用的是系统栈。有内部搜索和外部搜索两种&#xff0c;内部搜索是在图的内部&#xff0c;内部搜索一般基于连通性&#xff0c;从一个点转移到另一个点&#xff0c;或者判断是否连通之类的问题&#xff0c…

Github开源项目Excalidraw:简洁易用的手绘风格白板工具

Excalidraw是Github上的一个开源项目&#xff0c;它提供了一个简洁易用的手绘图形创建工具&#xff0c;用户可以通过它创建流程图、示意图、架构图和其他各种图形。本文将介绍Excalidraw的特点和功能&#xff0c;并探讨其在技术层面上的优势和扩展能力。 GitHub地址&#xff1a…

android inset 管理

目录 简介 Insets管理架构 Insets相关类图 app侧的类 WMS侧的类 inset show的流程 接口 流程 WMS侧确定InsetsSourceControl的流程 两个问题 窗口显示时不改变现有的inset状态 全屏窗口上的dialog 不显示statusbar问题 View 和 DecorView 设置insets信息 输入法显…

通讯基本概念

通信的方式有多种&#xff0c;按数据传输方式可分为串行通讯和并行通信&#xff1b;按通信数据同步方式可分为同步通信和异步通信&#xff1b;按数据通信的方向可分为 一、串行通信和并行通信 串行通信&#xff1a;设备之间通过少量的数据信号线&#xff08;一般是8根以下&am…

Windows自动化实现:系统通知和任务栏图标自定义

文章目录 Windows自动化的三个小工具系统通知任务栏图标使用pystray实现使用infi.systray实现 Windows自动化的三个小工具 系统通知 import win10toastwin10toast.ToastNotifier().show_toast("eee", "休息一下", icon_path"icon.ico", durati…