[Linux] 信号保存与处理

🪐🪐🪐欢迎来到程序员餐厅💫💫💫

          主厨:邪王真眼

主厨的主页:Chef‘s blog  

所属专栏:青果大战linux

总有光环在陨落,总有新星在闪烁


信号的保存

下面的概念务必记住

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

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

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

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

  5. 阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作

信号的内核描述

如图所示,我们的task_struct中保存了三张表,block表,pend表,handler表

pending表

它本质就是一个位图

 handler表

它本质就是一个函数指针数组

 typedef void (*sighandler_t)(int);

我们之前通过signal函数给指定信号设置自定义方法,本质就是把signal函数的sighandler参数写入handler表,所以我们signal调用一次,就可以永久修改一个进程的信号处理方法

 block表

它本质也是一个位图

被阻塞的信号,即是被写入pending表,也不会被处理,直到阻塞被解除才会去处理它

有了这三张表,我们就可以直到一个信号有没有被传进来,他有没有被阻塞,如果没被阻塞那他又该如何处理,如此一来我们的进程就可以识别信号了,而三张表的实现,当然是当初写OS的程序员通过代码实现的,因此我们说进程认识信号,是程序员内置的结果。


内核数据结构

我们需要先了解一些类型,如下所示,不懂的看注释

struct task_struct {
struct sighand_struct *sighand;//hand表
sigset_t blocked//block表
struct sigpending pending;//pend表
...
}

先看hand表的结构体类型

struct sighand_struct {
atomic_t count;
struct k_sigaction action[_NSIG]; //  _NSIG是个宏,为64
spinlock_t siglock;
};
//上面的结构体中包含了K_sigaction这个结构体,它的定义就在下面
struct k_sigaction {
struct __new_sigaction sa;
void __user *ka_restorer;
};
//上面的结构体中包含了__new_sigaction这个结构体,它的定义就在下面
struct __new_sigaction {
__sighandler_t sa_handler;
unsigned long sa_flags;
void (*sa_restorer)(void); /* Not used by Linux/SPARC */
__new_sigset_t sa_mask;
};
//上面的结构体中包含了__sighandler_t这个类型,它的定义就在下面
typedef void (*__sighandler_t)(int);//即函数指针

再看pend表

struct sigpending {
struct list_head list;//表示这是个链表结构
sigset_t signal;//它的重点是这个sigset_t类型
};

 显然,pend表的内容是sigset_t类型的变量存储的,block表也是,sigset_t被称为信号集,这个类型定义如下

typedef struct
{unsigned long int __val[_SIGSET_NWORDS];
} __sigset_t;//注意这前面有两个下划线typedef __sigset_t sigset_t;//这次typedef后,前面没有下划线了
这个sigset_t类型本质就是个unsigned long类型的数组,这个数组就是用来当位图用的
OS还很贴心的准备了一组接口用于操作该类型的变量
#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);
  1.  sigemptyset:使信号集set中的所有比特位变为0

  2. sigfillset:使信号集set中的所有比特位变为1

  3. sigaddset:使信号集set的第signum位变为1

  4. sigdelset:使信号集set的第signum位变为0

  5. sigismember:检测信号集set的第signum位是0还是1 

  • 在使用sigset_ t类型的变量前,⼀定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。
  • 这四个函数都是成功返回0,出错返回-1。
  • sigismember是⼀个布尔函数,用于判断⼀个信号集的有效信号中是否包含某种 信号,若包含则返回1,不包含则返回0,出错返回-1。

 sigprocmask

调⽤函数 sigprocmask 可以读取或更改进程的block表。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1
  • how参数
    • 这个参数决定了如何修改信号屏蔽字,它有以下几种取值:
      • SIG_BLOCK:将set参数所指向的信号集添加到当前信号屏蔽字中。
      • SIG_UNBLOCK:从当前信号屏蔽字中移除set参数所指向的信号集中的信号。
      • SIG_SETMASK:将当前信号屏蔽字设置为set参数所指向的信号集。这会完全替换当前的信号屏蔽状态。
  • set参数
    • 这是一个指向sigset_t类型的信号集的指针。
  • oldset参数
    • 这是一个指向sigset_t类型信号集的指针,用于保存本次修改前的信号屏蔽字状态。如果不需要保存旧状态,可以将此参数设置为NULL

9号信号(SIGKIILL和19号信号(SIGSTOP)不能block


sigpending

#include <signal.h>
int sigpending(sigset_t *set);
读取当前进程的未决信号集,通过set参数传出。
调⽤成功则返回0,出错则返回-1

为什么有可以修改block表的函数,但是没有修改pend表的函数

因为不需要,我们上节课学习的信号产生的方式都是在修改pend,有他们就够了


实操函数

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>int main(){//对二号信号进行屏蔽sigset_t ne;sigset_t old;sigemptyset(&ne);sigemptyset(&old);sigaddset(&ne,2);//此时还没有把2号加到内核的block中,只是放到了栈上的ne中了sigprocmask(SIG_BLOCK,&ne,&old);//现在加到内核了int n=7;while(true){n--;if(n==0)sigprocmask(SIG_SETMASK,&old,nullptr);sigset_t pe;sigemptyset(&pe);sigpending(&pe);for(int i=31;i;i--)std::cout<<sigismember(&pe,i);std::cout<<std::endl;sleep(1);}}

我们输入ctrl+c即二号命令,但是进程没有立即终止,是因为我们把二号信号写进block表了 

7秒之后,block表置零,二号信号不再被block于是被处理了,所以进程终止。


硬件中断

外设资源是否准备好,不能让OS去轮询检测,因为外设非常多,效率会很低,太浪费资源了,这里采用的解决方案就是硬件中断。

硬件中断是指由计算机硬件设备(如键盘、鼠标、硬盘、网卡等)发出的信号,用于通知 CPU暂停当前正在执行的程序,转而处理与该硬件设备相关的特定事件这使得 CPU 能够及时响应硬件设备的请求或状态变化,从而实现设备与 CPU 之间的高效交互。这些东西的具体实现是依靠硬件电路实现的,我们不用管。

  

但是外设实在是太多了,所以不能直接连接到cpu的针脚上,于是就设计了一个中断控制器来负责连接他们,每个设备都被编好了中断号,中断控制器通过中断号知晓是哪个设备发来的中断,然后会把该信息发给CPU。CPU会告知OS,OS会根据中断号去中断向量表执行对应的方法(例如去键盘获取信息)。

这里的通知CPU就是其实向着CPU的特定针脚发送高电平信号

程序员在写OS时,就提前给每一种设备准备好处理中断的方案,这些方案本质就是函数,他们汇聚在一起就是一个中断向量表,我们把它当作一个函数指针数组即可。访问该数组元素的下标就是中断号。

当然,如果硬件中断信息告知给CPU时,CPU要开始执行中断处理方法,就不能继续跑之前的进程,为了保证之后还能正常运行它,需要进行CPU现场保护

 CPU 现场保护是指在计算机系统发生中断或异常情况时,CPU 暂停当前正在执行的程序,为了能够在后续恢复该程序的执行,将当前程序执行的状态信息进行保存的过程。这些信息包括程序计数器(PC)的值、通用寄存器的内容、状态寄存器(也称为程序状态字 PSW)的内容等。这个和进程切换时的上下文数据保存有些像,但请注意这俩不是一个东西。 


 时钟中断

进程是在OS在管理控制下运行的,那么OS又是被谁指挥的呢?

时钟源是一个定期发送触发硬件中断信号的硬件,他的中断号对应的中断处理方式就是进程调度

所以OS才可以不断的调度进程,为了提高效率,这个时钟源已经被集成到CPU内部了,他的中断发送不依赖中断控制器,是直接发送给CPU的。我们计算机中有个参数叫做主频,表示时钟源一秒发送多少中断单位一般是GHZ,所以主频快的话,OS响应速度就比较快,效率就高了

于是OS就可以开摆了,OS所谓的调度进程,就是时钟源定时发送中断,然后OS去根据设置好的处理方法处理中断;OS要和外设IO,就是等硬件发送中断,然后它根据中断向量表去调用对应的函数。

无端联想,OS就像一个商店老板,他的店里什么都有,当用户需要一样东西时,OS虽然不知道这个东西是干嘛的,是怎么造出来的,但是他知道他的店里有,而且确切的直到放到哪里了,于是它只需要去把这个东西拿给用户就好。

因此操作系统再初始化完需要的资源后,就直接进入死循环,等待中断信号的到来,然后执行对应的方法即可。


时间片

时钟中断是定时发送的,我们假设它一纳米发送一次,

我们可以给进程task_struct设置一个变量——时间片

int time_piece=1000;

那么时间片的大小就是1000*1纳秒

每次时钟源发送信号,OS进行调度都会把当前进程的时间片减一

当减到零,就进行进程切换,否则继续执行该进程


软中断

有没有可能,上面这一套中断的流程,靠软件也可以实现呢

为了让操作系统支持进行系统调用,CPU也设计了对应的汇编指令(int 或者 syscall),可以让CPU内

部触发上面的中断逻辑。

把这些汇编写在系统接口函数里,不就可以让OS支持系统调用了吗

这里我们以int指令为例,他的中断号是ox80

  

它对应的中断向量表中的元素也是一个函数我们暂时称他为func(),我们的OS要是实现很多系统接口,这些接口会放到一个数组中,被称为系统调用表,所以要使用某个系统调用,只要使用他在表中下标即可。现在我们要使用系统调用,只需要把系统调用号传到func()中即可。 

第一个问题

用户层怎么把系统调用号给操作系统?

把系统调用号写进CPU寄存器,之后查看寄存器即可,只要提前设计好OS去哪个寄存器查看即可,系统接口的蚕食也是依靠寄存器传给OS的。

第二个问题

操作系统怎么把返回值给用户?

把最后return的值放到一个寄存器中,再把该值move到用户用于接受返回值的变量中。

系统调用的过程,其实就是先int 0x80、syscall触发软中断,并且把系统调用号(以及形参)写到一个寄存器里,然后CPU告知OS,OS根据寄存器的值去系统调用表执行对应的函数。

现在我们知道了使用系统接口,不一定就要用系统函数,你可以先通过int汇编代码触发ox80号中断,然后把要用的系统调用接口的编号和参数通过汇编move到指定的寄存器,最后OS会找到要执行的函数,并且运行它。

事实上,这才是OS提供的真正的系统接口,而不是我们用的那些c语言函数(write、read、fork这些)。我们所用的fork这些接口,是c语言封装过的函数,毕竟让用户用真的系统实在是太难了。

这样一来既方便了用户,也保护了OS,毕竟如果你用int(0x80),万一传入的系统调用号非法呢,或者你用了一些别的奇葩操作,都会危害到OS,现在只让你用c语言封装的接口,你就害不了他了

左64位,右32位真系统接口

c语言才是世界上最好的语言!

缺页中断、除零错误、野指针,他们本质都是被转化为软中断,OS会提前给每种情况设置处理方法,也都有自己的中断号。

  • CPU内部的软中断,比如int 0x80或者syscall,他们不是说出现的什么错误,知识单纯让进程陷入内核去进行系统调用的,我们把这种操作叫做陷阱(这个名字听起来有点怪)

  • CPU内部的软中断,比如除零/野指针等,我们叫做异常。

所以,能理解“缺页异常” 为什么这么叫了吗?

OS是什么

它就是躺在中断处理例程上的代码块

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

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

相关文章

【Axure高保真原型】伸缩表单

今天和大家分享伸缩表单的原型模板&#xff0c;效果包括在需要填写内容较多时&#xff0c;可以对填写内容进行分类&#xff0c;然后通过点击上下箭头&#xff0c;收起或展开对应的信息。这个模版里面包含了输入框、下拉列表、选择器、上次图片共多种种常用的元件&#xff0c;后…

InternVL简读

InternVL: Scaling up Vision Foundation Models and Aligning for Generic Visual-Linguistic Tasks 1. Introduction 需要解决的问题&#xff1a; existing VLLMs [5, 81, 131, 177, 187] commonly employ lightweight “glue” layers, such as QFormer [81] or linear pr…

从源码分析swift GCD_DispatchGroup

前言&#xff1a; 最近在写需求的时候用到了DispatchGroup&#xff0c;一直没有深入去学习&#xff0c;既然遇到了那么就总结下吧。。。。 基本介绍&#xff1a; 任务组&#xff08;DispatchGroup&#xff09; DispatchGroup 可以将多个任务组合在一起并且监听它们的完成状态。…

AFL-Fuzz 的使用

AFL-Fuzz 的使用 一、工具二、有源码测试三、无源码测试 一、工具 建议安装LLVM并使用afl-clang-fast或afl-clang-lto进行编译&#xff0c;这些工具提供了更现代和高效的插桩技术。您可以按照以下步骤安装LLVM和afl-clang-fast&#xff1a; sudo apt update sudo apt install…

ONES 功能上新|ONES Copilot、ONES Wiki 新功能一览

ONES Copilot 可基于工作项的标题、描述、属性信息&#xff0c;对工作项产生的动态和评论生成总结。 针对不同类型的工作项&#xff0c;总结输出的内容有对应的侧重点。 应用场景&#xff1a; 在一些流程步骤复杂、上下游参与成员角色丰富的场景中&#xff0c;工作项动态往往会…

EasyGBS国标GB28181平台P2P远程访问故障排查指南:客户端角度的排查思路

在现代视频监控系统中&#xff0c;P2P&#xff08;点对点&#xff09;技术因其便捷性和高效性而被广泛应用。然而&#xff0c;当用户在使用P2P远程访问时遇到设备不在线或无法访问的问题时&#xff0c;有效的排查方法显得尤为重要。本文将从客户端的角度出发&#xff0c;详细探…

Soul Android端稳定性背后的那些事

前言&#xff1a;移动应用的稳定性对于用户体验和产品商业价值都有着至关重要的作用。应用崩溃会导致关键业务中断、用户留存率下降、品牌口碑变差、生命周期价值下降等影响&#xff0c;甚至会导致用户流失。因此&#xff0c;稳定性是APP质量构建体系中最基本和最关键的一环。当…

mfc140u.dll是什么文件?如何解决mfc140u.dll丢失的相关问题

遇到“mfc140u.dll文件丢失”的错误通常影响应用程序的运行&#xff0c;这个问题主要出现在使用Microsoft Visual C环境开发的软件中。mfc140u.dll是一个重要的系统文件&#xff0c;如果它丢失或损坏&#xff0c;会导致相关程序无法启动。本文将简要介绍几种快速有效的方法来恢…

mybatis分页插件的使用

1. 引入依赖包 <dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>6.1.0</version> </dependency> 2 添加分页插件配置 2.1 使用配置类的方式&#xff08;推荐&#xff09…

手机便签哪个好用?手机桌面便签app下载推荐

在快节奏的现代生活中&#xff0c;我们常常需要记录一些重要的信息和灵感&#xff0c;以便于日后查阅和回顾。手机便签软件因其便携性和易用性&#xff0c;成为了我们日常生活中不可或缺的工具。无论是购物清单、待办事项、灵感记录还是重要笔记&#xff0c;手机便签都能帮助我…

Zabbix6.0升级为6.4

为了体验一些新的功能&#xff0c;比如 Webhook 和问题抑制等&#xff0c;升级个小版本。 一、环境信息 1. 版本要求 一定要事先查看官方文档&#xff0c;确认组件要求的版本&#xff0c;否则版本过高或者过低都会出现问题。 2. 升级前后信息 环境升级前升级后操作系统CentOS…

怎么将pdf中的某一个提取出来?介绍几种提取PDF中页面的方法

怎么将pdf中的某一个提取出来&#xff1f;传统上&#xff0c;我们可能通过手动截取屏幕或使用PDF阅读器的复制功能来提取信息&#xff0c;但这种方法往往不够精确&#xff0c;且无法保留原文档的排版和格式。此外&#xff0c;很多时候我们需要提取的内容可能涉及多个页面、多个…

LeetCode:101. 对称二叉树

跟着carl学算法&#xff0c;本系列博客仅做个人记录&#xff0c;建议大家都去看carl本人的博客&#xff0c;写的真的很好的&#xff01; 代码随想录 LeetCode&#xff1a;101. 对称二叉树 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输…

力扣-图论-18【算法学习day.68】

前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向和记录学习过程&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;&#xff09;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非…

深度学习之目标检测——RCNN

Selective Search 背景:事先不知道需要检测哪个类别,且候选目标存在层级关系与尺度关系 常规解决方法&#xff1a;穷举法&#xff0c;在原始图片上进行不同尺度不同大小的滑窗&#xff0c;获取每个可能的位置 弊端&#xff1a;计算量大&#xff0c;且尺度不能兼顾 Selective …

【计算机视觉基础CV】03-深度学习图像分类实战:鲜花数据集加载与预处理详解

本文将深入介绍鲜花分类数据集的加载与处理方式&#xff0c;同时详细解释代码的每一步骤并给出更丰富的实践建议和拓展思路。以实用为导向&#xff0c;为读者提供从数据组织、预处理、加载到可视化展示的完整过程&#xff0c;并为后续模型训练打下基础。 前言 在计算机视觉的深…

Linux 中的 mkdir 命令:深入解析

在 Linux 系统中&#xff0c;mkdir 命令用于创建目录。它是文件系统管理中最基础的命令之一&#xff0c;广泛应用于日常操作和系统管理中。本文将深入探讨 mkdir 命令的功能、使用场景、高级技巧&#xff0c;并结合 GNU Coreutils 的源码进行详细分析。 1. mkdir 命令的基本用法…

电商数据采集电商,行业数据分析,平台数据获取|稳定的API接口数据

电商数据采集可以通过多种方式完成&#xff0c;其中包括人工采集、使用电商平台提供的API接口、以及利用爬虫技术等自动化工具。以下是一些常用的电商数据采集方法&#xff1a; 人工采集&#xff1a;人工采集主要是通过基本的“复制粘贴”的方式在电商平台上进行数据的收集&am…

排序算法(3)——归并排序、计数排序

目录 1. 归并排序 1.1 递归实现 1.2 非递归实现 1.3 归并排序特性总结 2. 计数排序 代码实现 3. 总结 1. 归并排序 基本思想&#xff1a; 归并排序&#xff08;merge sort&#xff09;是建立在归并操作上的一种有效的排序算法&#xff0c;该算法是采用分治法&#xff0…

android RadioButton + ViewPager+fragment

RadioGroup viewpage fragment 组合显示导航栏 1、首先主界面的布局控件就是RadioGroup viewpage <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:tools…