【linux】信号的保存和递达处理

news2024/11/28 8:41:31

        上节我们了解到了预备(信号是什么,信号的基础知识)再到信号的产生(四种方式)。今天我们了解信号的保存。信号产生,进程不一定立马就去处理,而是等合适的时间去处理,那么在这段时间内,进程就需要保存信号,到了合适时间再去执行!

目录

一、递达,阻塞,未决

二、信号的保存

2.1 信号在内核中的数据结构构成

2.3 用户态和内核态

2.3 信号的捕捉流程

三、sigset_t 信号集        

四、信号的处理细节

4.1 对于同类型信号的处理

4.2 可重入函数和不可重入函数

4.3 volatile关键字


一、递达,阻塞,未决

        我们知道,信号是发送给进程的,而进程又是被操作系统创建pcb(信号的相关信息被保存到进程pcb中)而进行管理的,所以修改或者访问进程pcb都需要操作系统来进行,那么信号发送的本质就是:操作系统在向进程发送信号。

       信号产生,进程不一定立马就去处理,而是等合适的时间去处理,那么在这段时间内,进程就需要保存信号,到了合适时间再去执行!那么实际执行信号的处理动作称为信号递达;信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞 (Block )某个信号。

        被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。我们之前知道,进程递达之后的动作有三种:默认动作、自定义动作、忽略动作(执行动作,只不过这个动作就是什么都不做)。注意:阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。


二、信号的保存

        我们知道信号是保存到进程pcb中的,信号产生、信号递达、信号阻塞、信号未决这些到底怎么实现的呢?我们来看:

2.1 信号在内核中的数据结构构成

        上图就是信号在内核中的数据结构构成,我们来慢慢了解。首先信号的相关信息都在进程pcb中存储,判断信号发送给进程后的状态都是位图来实现的。

        unsigned int pending = 0;这是信号未决的位图结构,一共有32个比特位,分别代表32个进程信号的编号,当然比特位的内容(0/1)也代表进程是否收到了对应的信号,收到信号但未递达,对应编号的比特位就会由0改为1。

        unsign int block =0 ;这是信号阻塞的位图结构,一共有32个比特位,分别代表32个进程信号的编号,当然比特位的内容(0/1)也代表进程是否阻塞了对应的信号,收到信号被阻塞,对应编号的比特位就会由0改为1。

        如果某个信号被阻塞,那么阻塞位图结构中对应的比特位(信号编号)就会置为1,那么在此信号阻塞未被解除之前,会一直处于信号未决(信号产生但未被处理)非阻塞被解除。

        handler_t handler[32] :信号递达后要处理动作,那么handler这个数组中一定存放着信号编号所对应的处理动作。handler_t 其实是函数指针类型,typedef void(*handler)(int signo); 参数是信号编号,返回值是void的函数指针。数组的下标就是对应的信号编号,数组下标中的内容就是对应信号的处理方法(函数指针)。

        当调用signal(signo,handler); ,就会把信号对应的处理方法设置为自定义方法,内核中就是将数组下标(信号编号)中的内容(处理方法)设置为自定义方法的函数指针。从而在递达后执行处理方法。

        所以我们知道,为什么进程可以识别信号呢?原来是因为程序员在设计进程的时候,已经为进程设计好了这三种结构,从而去识别信号!


2.3 用户态和内核态

        信号产生时,进程可能不会立马去处理,而是等待合适的时机,那么这个合适的时机是什么时候呢?是从内核态返回到用户态!哦吼,那什么是用户态和内核态呢?我们来看:

        我们编写的代码一般都是用户层级的代码,那当我们去调用接口去访问os自身的资源(getpid等等),去printf(访问硬件资源)的时候,这就需要我们切换身份为内核态去执行这些操作!访问不同的资源始终是进程,但是当他的身份不同的时候,那么可以访问的资源就是不同的!

        用户为了访问内核或者硬件资源,必须通过系统接口完成访问。那么系统调用肯定是比进程互相调用用户态层级的代码慢得多,因为他需要身份的切换等等,所以我们尽量避免频繁的调用系统接口。(这就是为什么vector中的扩容他需要一次性去扩充1.5/2倍的空间,因为这样就可以避免频繁的扩容,导致频繁的去调用系统接口,导致速度和效率大大下降)

        那么我们就会想,那到底是怎么操作这个身份的呢?如何就知道它是内核态或者用户态的呢?我们都知道进程在执行时,会将此进程的上下文投递到cpu的寄存器中,那么此时cpu中还有很多寄存器存放着不同的信息:

        cpu内部的寄存器分为:1.可见寄存器 2.不可见寄存器。其中,有存放着进程pcb的起始地址的寄存器(这样就可以访问进程的所有信息),有存放页表起始地址的寄存器,也有存放着当前进程的运行级别的寄存器(利用位图结构,来表示不同的级别),所以当进程去访问内核的资源的时候,os就会到cpu的CR3去看进程的运行级别,如果处于内核态,那可以访问,反之

        我们了解了访问的条件,但是他到底是如何到os中访问资源呢?来看:

        每一个进程都有[3,4]G的内核空间,[1,3]G的用户空间,且都享有同一个内核级页表。 

        之前我们知道,当动态库加载到物理内存时,是可以通过页表映射到进程空间的共享区,之后在执行代码若执行到共享区的代码时,就会在当前地址空间(起始地址+偏移量的方式)去跳转到共享区去执行代码,执行完毕后,再回到对应执行的代码。每一个进程他都有自己的一套内核结构(进程的独立性),且都有不同的用户级页表。

        但若去访问操作系统的资源,因为操作系统只有一个,当开机时,操作系统的资源会被加载到物理内存,进程访问时,通过同一个内核级页表。所以无论进程怎么切换,都不会更改3-4G的内核空间。

        那什么时候从用户态切换到内核态呢?系统调用的最开始。(根据 Int 80(汇编代码),会把寄存器中的进程运行级别状态修改。(系统调用最开始就设计了这样))


2.3 信号的捕捉流程

        我么们了解了内核态和用户态以后,就可以了解到,原来信号产生,不会立即被进程所处理动作,而是等到合适的时机去处理,这个合适的时机就是内核态切到用户态的时候。那我们一定之前就进入了内核态,我们来看:

         当进程需要访问内核资源的时,就会通过系统调用来切换身份,由用户态切换到内核态,之后进行系统调用(cpu中改变身份,通过内核级页表去访问内核资源),到这里本应该就是切换到用户态返回的,但是来都来了,而且切换到内核态确实不容易。所以就会通过进程中的pending,block,headler进行信号的检测过程(先在pending中查看信号是否存在,再到block中查看是否被阻塞,如果阻塞则该信号处于未决,继续查看pending中的下一个信号,如果没有被阻塞,那就信号递达,通过handler去处理动作(默认、自定义、忽略)。当然在信号递达前,会将pending中该信号对应的比特位由1变为0,再去执行。

        忽略其实最容易执行,只需要将pending中1改为0以后,啥都不做;而自定义就需要再将身份切换为用户态,然后去执行handler中的方法。那为什么不直接在内核态中去执行用户态中的方法呢?是因为操作系统不信任任何人,如果用户态的代码是问题代码,那么就会导致操作系统出现严重问题,所以会先切换用户态,再去执行handler中对应的方法(用户态执行一些代码会受到限制)。递达后为什么不直接回到进程中呢?是因为我们没办法直接回到当前进程执行的位置,这个过程需要操作系统的操作。所以只能再回到内核态,再由内核态切到用户态回到进程执行的位置。

        我们直接抽象看本质:

        四个交点(四次身份切换)

        在用户态中因为一些原因陷入内核,执行系统调用后,在内核态中再进行信号的检测过程,再由内核态切换到用户态执行方法,完毕后再切换身份回到内核态,通过信号检测结束后,再身份切换,回到进程执行流中上次中断的地方。


三、sigset_t 信号集        

        我们知道信号是在进程的pcb中,即内核中。所以用户级操作难免会困难一些。所以sigset_t 信号集就是为了更好的在用户级操作信号所产生的类型。sigset_t 信号集包括 pending信号集、信号屏蔽字(block信号集)。sigset_t 底层就是一个大数组实现的位图结构。

信号集操作函数:

#include <signal.h>
int sigemptyset(sigset_t *set);                  //初始化set信号集
int sigfillset(sigset_t *set);                        //将信号集set全部设置为1
int sigaddset (sigset_t *set, int signo);     //往set信号集添加信号
int sigdelset(sigset_t *set, int signo);       //删除set信号集中的信号
这四个函数都是成功返回0,出错返回-1
int sigismember(const sigset_t *set, int signo);        //判断信号是否在set中
sigismember 是一个布尔函数 , 用于判断一个信号集的有效信号中是否包含某种信号, 若包含则返回 1, 不包含则返回 0, 出错返回 -1
sigprocmask
调用函数 sigprocmask 可以读取或更改进程的信号屏蔽字 ( 阻塞信号集)
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
how就是下面的几种方式:
返回值 : 若成功则为 0, 若出错则为 -1
sigpending
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值 : 若成功则为 0, 若出错则为 -1
读取当前进程的未决信号集 , 通过 set 参数传出。调用成功则返回 0, 出错则返回 -1。

下面我们利用上面所学,来实现一个观察pending信号集,通过信号屏蔽子来观察pending信号集的变化:

#include <iostream>
#include <vector>
#include <signal.h>
#include <unistd.h>

// #define BLOCK_SIGNAL 2
#define MAX_SIGNUM 31

using namespace std;


static vector<int> sigarr = {2}; 

//输出pending信号集
static void show_pending(const sigset_t &pending)
{
    for(int signo = MAX_SIGNUM; signo >= 1; signo--)
    {
        if(sigismember(&pending, signo))
        {
            cout << "1";
        }
        else cout << "0";
    }
    cout << "\n";
}

//递达自定义动作
static void myhandler(int signo)
{
    cout << signo << " 号信号已经被递达!!" << endl;
}

int main()
{
    for(const auto &sig : sigarr) signal(sig, myhandler);

    // 1. 先尝试屏蔽指定的信号
    sigset_t block, oblock, pending;

    // 1.1 初始化
    sigemptyset(&block);
    sigemptyset(&oblock);
    sigemptyset(&pending);

    // 1.2 添加要屏蔽的信号
    for(const auto &sig : sigarr) sigaddset(&block, sig);  //批量化屏蔽

    // 1.3 开始屏蔽,设置进内核(进程)
    sigprocmask(SIG_SETMASK, &block, &oblock);

    // 2. 遍历打印pengding信号集
    int cnt = 10;
    while(true)
    {
        // 2.1 初始化
        sigemptyset(&pending);
        // 2.2 获取它
        sigpending(&pending);
        // 2.3 打印它
        show_pending(pending);
        // 3. 慢一点
        sleep(1);
        if(cnt-- == 0)
        {
            sigprocmask(SIG_SETMASK, &oblock, &block); // 一旦对特定信号进行解除屏蔽,一般OS要至少立马递达一个信号!
            cout << "恢复对信号的屏蔽,不屏蔽任何信号\n";
        }
    }
}

四、信号的处理细节

4.1 对于同类型信号的处理

        当我们正在递达一个信号期间,同类型的信号无法被递达!(信号的处理细节)

        当信号正在被递达中,又来了同类型的信号,此时当前信号会被加入到进程的信号屏蔽字,且会将pending中该信号对应的那一位由0变为1。(因为该信号被递达前,会将pending中对应的那一位由1改为0),若结束递达后,同类型仍发送,则会继续重复上面的动作。但若结束递达后,同类型的信号没有发送了,进程就只会再捕捉一次,将pending中的1改为0。递达后则继续检其他信号进行递达。

        进程处理信号的原则是穿行的处理同类型的信号,不允许递归处理!


4.2 可重入函数和不可重入函数

        举例说明:

         在main执行流中,没有头结点的单链表进行头插,如上图所示:在执行到第一步时,此时被信号中断,结果导致main中还没有执行完又进入insert()中,最后回到main执行流中,再执行完剩下的代码结果导致内存泄漏等问题。

        1.一般而言,main执行流和信号捕捉执行流是两个执行流!

        2.如果在main中,和在handler中,该函数被反复进入:1出现问题的就是不可重入函数;2.没有出现问题的就是可重入函数。当然可重入和不可重入只是他们的特性,没有好坏之分。


4.3 volatile关键字

        我们在读取变量的值时,一般会从内存中读取,但是由于编译器的优化,就会将内存中的值加载到cpu的寄存器中,从而之后访问该变量的值只会从寄存器中读取,如果这个变量的值被修改了,自然而然内存上的值也被修改了,但是寄存器中的值仍然没有变化,还是修改之前的值,所以为了避免这种优化产生的后果,我们就会在变量前加上volatile,意为一直从内存中读取值!


总结:

        我们了解了信号的保存原来是通过进程pcb中的pending、block位图,handler函数指针数组来进行保存,从而信号递达。 

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

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

相关文章

[Java基础]面向对象

目录 1、对象和类 2、类之间的关系 3、引用 4、对象的创建和使用 5、构造函数/构造方法 6、内存解析 在这篇文章中&#xff0c;我们将学习面向对象的思想&#xff0c;并学习如何使用面向对象思想编程。在学习面向对象编程之前&#xff0c;我们先了解一下编程语言的发展&a…

Win安装Node.js Npm

1、在使用之前&#xff0c;先类掌握3个东西&#xff0c;明白它们是用来干什么的&#xff1a; npm: nodejs 下的包管理器。 webpack: 它主要用途是通过CommonJS 的语法把所有浏览器端需要发布的静态资源作相应的准备&#xff0c;比如资源的合并和打包。 vue-cli: 用户生成Vue工…

HNU-操作系统OS-作业4(37-40章)

OS_homework_4 这份文件是OS_homework_4 by计科2102 梅炳寅 202108010206 文档设置了目录,可以通过目录快速跳转至答案部分。 第37章 运行程序wolf/OS-homework/file-disks/disk.py 解释一些参数 -G可以查看可视化内容-c可以计算结果。-a 提供待访问的数组-S 将寻道速率改…

深度学习训练营之优化器对比

深度学习训练营之优化器对比 原文链接环境介绍前置工作设置GPU 数据处理导入数据数据集处理数据集可视化 模型构造模型训练结果可视化 原文链接 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f366; 参考文章&#xff1a;365天深度学习训练营…

deque(简单介绍一下)

deque的基本情况&#xff1a; 简单的来说deque是一个双头队列。且两边的尺寸可以动态收缩或者扩张。 其底层实现相当复杂&#xff0c;而且效率并不高。大多数时候都不会使用。 deque诞生的原因是vector和list的优缺点不可分割。 正好复习一下vector和list的优缺点。 vector的…

手机抓包fiddler配置及使用教程

本文基于Fiddler4讲解基本使用 fiddler抓包原理 注意&#xff1a;Fiddler 是以代理web服务器的形式工作的&#xff0c;它使用代理地址:127.0.0.1&#xff0c;端口:8888。当Fiddler退出的时候它会自动注销&#xff0c;这样就不会影响别的 程序。不过如果Fiddler非正常退出&…

学校热水供应系统方案

学校热水供应系统是现代化校园建设的重要组成部分。一套高效、可靠、安全、环保的热水供应系统&#xff0c;不仅能够满足学生、教职工的日常生活需求&#xff0c;也能提高学校形象和竞争力。 在设计学校热水供应系统方案时&#xff0c;需要考虑以下几个方面&#xff1a; 一、热…

【计算机网络复习之路】运输层(谢希仁第八版)万字详解 主打基础

运输层是OSI七层模型中最重要最关键的一层&#xff0c;是唯一负责总体数据传输和控制的一层。运输层要达到两个主要目的&#xff1a;第一&#xff0c;提供可靠的端到端的通信&#xff08;“端到端的通信” 是应用进程之间的通信&#xff09;&#xff1b;第二&#xff0c;向会话…

【css】box-sizing属性

box-sizing 是一个 CSS 属性&#xff0c;用于指定元素的总宽度和高度的计算方式。它影响内容框的大小&#xff0c;并可以包括或排除元素的填充、边框和外边距。 box-sizing 属性接受两个值&#xff1a; content-box&#xff1a;这是默认值。它指定元素的宽度和高度只包括内容区…

培训班出来拿17K,入职后8天就被裁了....

最近翻了一些网站的招聘信息&#xff0c;把一线大厂和大型互联网公司看了个遍&#xff0c;发现市场还是挺火热的&#xff0c;虽说铜三铁四&#xff0c;但是软件测试岗位并没有削减多少&#xff0c;建议大家有空还是多关注和多投简历&#xff0c;不要闭门造车&#xff0c;错过好…

电脑重装系统后无法开机是什么原因导致的

电脑重装系统是一种常见的解决问题和提升性能的方法&#xff0c;但有时候重装系统后可能会遇到无法开机的问题。本文将介绍一些常见原因和解决方法&#xff0c;帮助您解决电脑重装系统后无法开机的困扰。 工具/原料&#xff1a; 系统版本&#xff1a;windows7系统 品牌型号&…

HNU-操作系统OS-作业3(26-31章)

OS_homework_3 这份文件是OS_homework_3 by计科210X wolf 202108010XXX 文档设置了目录,可以通过目录快速跳转至答案部分。 第26章 运行该程序wolf/OS-homework/threads-intro/x86.py 通过README可知一些重要的标识符如下 -p:指定程序 -t:线程数 -i:指定每执行i个指令…

BT8918D----GPIO模块

0 Preface/Foreword 1 宏定义和数据结构 1.1 gpio_t typedef struct { psfr_t sfr; //GPIO SFR ADDR u8 num; u8 type; //type 1,高压IO&#xff0c;没有300R的强上下拉电阻。 type 0, 普通IO, 有内部300R上下拉电阻。 } gpio_t; 1.…

LeetCode - 18 四数之和

目录 题目来源 题目描述 示例 提示 题目解析 算法源码 题目来源 18. 四数之和 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给你一个由 n 个整数组成的数组 nums &#xff0c;和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], n…

selenium.chrome怎么写扩展拦截或转发请求?

Selenium WebDriver 是一组开源 API&#xff0c;用于自动测试 Web 应用程序&#xff0c;利用它可以通过代码来控制chrome浏览器&#xff01; 有时候我们需要mock接口的返回&#xff0c;或者拦截和转发请求&#xff0c;今天就来实现这个功能。 代码已开源&#xff1a; https:/…

Spring AOP(面向切面编程)详解

文章目录 前言AOP 概述AOP的基本概念AOP 应用场景AOP 常用的接口 Spring AOP 实现方式基于XML的配置基于注解的配置 Spring AOP 的使用一、Spring 的 API 接口实现AOP二、自定义类实现AOP三、注解实现AOP 模拟实现 Spring AOP 前言 Spring 提供了功能强大IOC、AOP等功能&#…

文心一言 VS 讯飞星火 VS chatgpt (36)-- 算法导论5.3 6题

六、请解释如何实现算法 PERMUTE-BY-SORTING&#xff0c;以处理两个或更多优先级相同的情形。也就是说&#xff0c;即使有两个或更多优先级相同&#xff0c;你的算法也应该产生一个均匀随机排列。 文心一言&#xff1a; 算法 PERMUTE-BY-SORTING 是一种基于排序的随机排列算法…

【第二章:物理层】

目录 知识框架No.0 : 引言No.1 &#xff1a;通信基础一、基本概念二、两个公式lim求极限1、失真&#xff08;失去真实性&#xff09; 三、编码和调制四、数据交换方式 No.2 &#xff1a;传输介质一、导向型介质二、非导向型介质 No.3 &#xff1a;物理层设备一、中继器二、集线…

selenium:元素定位之xpath、css

元素定位是在做UI自动化测试中最重要的一环&#xff0c;要牢牢掌握定位的方法&#xff0c;才能更有效率的进行UI自动化测试。 常见的元素定位方式&#xff1a; idnametag_nameclass_namelink_textpartial_link_textxpathcss 其中id&#xff0c;name是具有唯一性的&#xff0…

高考攀升小红书热榜!互动量破千万,品牌如何毕业季营销?

光影间&#xff0c;又是一年毕业季&#xff0c;弹指之间&#xff0c;那些青葱岁月如同白驹过隙般悄然从指缝溜走。近期&#xff0c;一年一度的高考、大学毕业又来袭&#xff0c;登上各大社媒平台热搜&#xff0c;成为热门话题&#xff1b;本期&#xff0c;随小编一起运用小红书…