【Linux】进程信号 --- 信号处理

在这里插入图片描述

👦个人主页:Weraphael
✍🏻作者简介:目前正在学习c++和算法
✈️专栏:Linux
🐋 希望大家多多支持,咱一起进步!😁
如果文章有啥瑕疵,希望大佬指点一二
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍


目录

  • 一、信号的处理时机(什么时候处理)
  • 二、用户态与内核态
      • 2.1 概念
      • 2.2 再谈进程地址空间
  • 三、信号的处理过程(如何处理)
  • 四、信号的捕捉
      • 4.1 内核如何实现信号的捕捉
      • 4.2 系统调用 --- sigaction
  • 五、进程信号总结
  • 六、相关代码

一、信号的处理时机(什么时候处理)

在前面的学习中,我们反复说过:当操作系统给进程发生信号后,进程可能不会立即去处理信号,而是等到合适的时候去处理,这是因为进程可能正在做更重要的事情!

但在特殊情况下,信号可能会立即被处理。比如:当信号被阻塞后,信号产生时,此时信号被阻塞了,没有立即被处理,那么信号就会记录在pending表中,当阻塞解除后,信号会被立即递达,此时信号会被立即处理。

那么对于进程来说什么是合适的时候呢?

进程要处理一个信号,首先要知道自己收到信号了。那么进程可以通过查阅block表、pending表和handler表。但这些表都是内核的数据结构,进程无法直接查看或修改它们,只能通过操作系统提供的系统调用接口来与之交互(进程处于内核状态)。因此,进程处理的合适时机是:进程从内核态返回到用户态时,会对信号进行检测及处理

二、用户态与内核态

2.1 概念

  • 用户态执行用户所写的代码时,就属于用户态

  • 内核态执行操作系统的代码时,就属于内核态

我们知道,一旦程序被加载到内存中形成了一个进程,CPU可以开始执行这个进程的代码。如果代码中调用系统调用,由于系统调用的实现在操作系统内核中,并且操作系统不相信任何人。因此,CPU在这时会从用户态切换到内核态(“身份”变化,提升权限),才允许开始执行相应的操作系统内核代码。当内核代码执行完之后,就会从内核态切换回用户态继续执行用户写的代码。

总之,进程调用系统调用不仅仅是调用函数这么简单,操作系统还需要自动进行“身份”变换

下面来结合进程地址空间深入理解操作系统的代码及状态切换等内容。

2.2 再谈进程地址空间

在这里插入图片描述

32位操作系统中,通常会将整个4GB的虚拟地址空间划分为用户空间内核空间两部分。

  • 用户空间:这个部分包含了用户进程可以直接访问的虚拟内存地址范围。每个用户进程都有自己的独立的用户空间,使得它们之间的内存访问相互隔离,提高了系统的安全性和稳定性。

  • 内核空间:这个部分是保留给操作系统内核使用的,存储的就是操作系统相关代码和数据。并且这块区域采用内核级页表与物理地址进行映射进程所谓的执行操作系统的代码及系统调用,就是在使用这1 GB的内核空间(和动态库类似)

  • 内核级页表用于管理操作系统内核的虚拟地址和物理地址之间的映射。它与用户级页表有所不同,用户级页表只管理用户进程的地址映射。这两张页表是相互独立的!

    • 对于用户级页表,有几个进程,操作系统就要维护几份,因为要保证进程独立性
    • 对于内核级页表,操作系统只需维护一份,因为内核的代码和数据对所有进程来说都是相同的。

如何理解进程切换?

  1. 进程切换需要调用系统调用,CPU在这时会从用户态切换到内核态,在当前进程的进程地址空间中的内核空间,找到操作系统的代码和数据。
  2. 执行操作系统的代码,将当前进程的用户空间的代码和数据剥离下来,从而换上另一个进程的代码和数据,但内核空间的内容永远不变。

操作系统的本质?

操作系统是管理软硬件资源的软件。当电脑开机时,它就已经被加载到内存,因此操作系统也是一个进程。但我们可能会疑惑,用户写的进程是通过操作系统去运行调度的。那是谁在推动操作系统去运行的呢?答案:硬件。

这里直接给出结论:操作系统是基于时钟中断的一个死循环!

在这里插入图片描述

其实在计算机硬件中,有一个时钟芯片,每隔很短的时间(纳秒级别),向CPU发送时钟中断。CPU收到中断请求后,会暂停当前正在执行的任务,并根据中断号找到对应的中断向量表(本质上就是数组)中对应中断号的条目。中断向量表是一个存储在内存中的数据结构,由操作系统维护,表中的条目是调用硬件设备驱动程序的方法的地址,找到后并执行。比如里面就会进行检查进程的时间片到了没有,如果到了,就把这个进程剥夺下去,换下一个进程。从而完成进程的调度。

如果没有进程需要调度,操作系统会陷入空闲状态,内核可能会执行一个类似for(;;) pause();的死循环。这段代码的作用是让内核进入一种等待状态,等待系统中的某些事件发生,比如新的进程就绪或者外部中断的到来等。

用户态和内核态之间是如何转化?

CPU有还2个寄存器:

  • CR3 寄存器:是页表基址寄存器,用于存储页表的地址(物理地址)。在内核态和用户态身份切换时,操作系统会更新CR3寄存器,以加载新的地址空间信息。
  • ECS寄存器CPU是执行内核态代码还是用户态代码,主要看ECS寄存器最低2位,排列组合式00011011。如果是00代表是内核态,11代表用户态。如果要调用系统调用接口,操作系统都会对此寄存器做检测。

三、信号的处理过程(如何处理)

  • 说明:以下不考虑信号被阻塞的情况。如果信号被阻塞,信号不会立即被处理,一旦解除阻塞,信号就会立即被处理。

在这里插入图片描述

因此,进程处理的合适时机是:进程从内核态返回到用户态时,会对信号进行检测及处理

通过一张图快速记录信号的处理过程

在这里插入图片描述

四、信号的捕捉

4.1 内核如何实现信号的捕捉

这里其实就是对下图第③步再进行详细讲解。

在这里插入图片描述

  • 概念:如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,则称为捕捉信号。
  • 因为用户自定义动作是位于用户空间中的,所以当内核态中任务(执行内核代码)完成,准备返回用户态时,会检测三表中的pending表为1且没有被阻塞block表为0,并且此时信号处理动作handler表为用户自定义动作,先将pending表对应1置为0,再切入用户态 ,完成用户自定义动作的执行。
  • 注意:用户自定义动作sighandler函数和main函数属于不同的堆栈空间,它们之间也不存在调用与被调用的关系(是操作系统调用的sighandler函数),是两个独立的执行流。
  • sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态,其中sigreturn的目的是告诉操作系统当前进程的信号处理已经完成。
  • 最后当执行完sighandler函数后会到内核中后,如果发现有信号解除屏蔽,还会再次进行信号处理,否则自动执行特殊的系统调用sys_sigreturn,这次再返回用户态就是恢复main函数的上下文继续执行了。

4.2 系统调用 — sigaction

sigaction函数也可以用户自定义动作,比signal函数功能更丰富。

函数原型如下:

#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

参数解释:

  • signum:要处理的信号的编号,比如 SIGINT等。
  • act:指向 struct sigaction 结构的指针,用来设置新的信号处理方式。
  • oldact:指向 struct sigaction 结构的指针,用来获取之前该信号的处理方式。或者可以设置为nullptr表示不获取之前该信号的处理方式。
  • 返回值:成功时返回0,失败时返回-1,并设置errno表示具体错误原因。

以下是 struct sigaction 结构的定义

struct sigaction 
{
    void (*sa_handler)(int); 
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;      
    int sa_flags;       
    void (*sa_handler)(void);
};

字段解释:

  • void (*sa_handler)(int);:指定的信号处理函数,可以是一个函数指针,用于处理指定的信号。可以设置为 SIG_IGN(忽略信号)或 SIG_DFL(默认处理方式)。
  • sigset_t sa_mask;:当信号在执行 用户自定义动作时,可以将部分信号进行屏蔽,直到用户自定义动作执行完成。也就是说,我们可以提前设置一批 待阻塞 的 屏蔽信号集,当执行用户自定义动作时,这些 屏蔽信号集 中的 信号将会被屏蔽(避免干扰用户自定义动作的执行),直到用户自定义动作执行完成。
  • void (*sa_sigaction)(int, siginfo_t *, void *);:不关心。
  • int sa_flags;:不关心。
  • void (*sa_handler)(void);:不关心。

可以简单用一下sigaction函数

#include <iostream>
#include <signal.h>
#include <cstring>
#include <unistd.h>
using namespace std;

void DisplayPending(sigset_t pending)
{
    // 打印 pending 表
    cout << "当前进程的 pending 表为: ";
    int i = 1;
    while (i < 32)
    {
        if (sigismember(&pending, i))
            cout << "1";
        else
            cout << "0";

        i++;
    }
    cout << endl;
}

// 自定义信号处理函数
void handler(int signo)
{
    cout << "捕捉到了一个信号:" << signo << endl;
    int n = 10;
    while (n--)
    {
        // 获取进程的pending表
        sigset_t pending;
        sigemptyset(&pending);

        int n = sigpending(&pending);
        if (n < 0)
        {
            continue;
        }

        DisplayPending(pending);
        sleep(1);
    }
    exit(0);
}

int main()
{
    struct sigaction act, oldact;

    // 初始化结构体对象(可选)
    memset(&act, 0, sizeof(act));
    memset(&oldact, 0, sizeof(oldact));

    // 设置信号处理函数
    act.sa_handler = handler;
    // 初始化 屏蔽信号集
    sigaddset(&act.sa_mask, 3);
    sigaddset(&act.sa_mask, 4);
    sigaddset(&act.sa_mask, 5);

    //  给2号信号注册自定义动作
    sigaction(2, &act, &oldact);

    while (true)
    {
        cout << "I am a process: " << getpid() << endl;
        sleep(1);
    }

    return 0;
}

【程序结果】

在这里插入图片描述

并且我们还发现了一个现象:当我们捕捉2号信号后,对应的2号信号的pending位图是为0,说明在执行handler方法之前,就已经将其置为0了,这就和我们之前的理论对应上了。

注意:除了sa_mask可以在当信号在执行用户自定义动作时,将部分信号进行屏蔽,直到用户自定义动作执行完成。除此之外,当某个信号的处理函数被调用时,内核自动会将当前信号屏蔽,也就是将当前的pending表和block表对应位置为1,直到用户自定义动作执行完成后恢复。如果在执行用户自定义动作的过程中,这种信号再次产生,那么它会被阻塞到当前处理结束为止。这是因为操作系统不允许对某个信号重复捕捉,最多只能捕捉一层,防止信号捕捉被嵌套调用

#include <iostream>
#include <signal.h>
#include <cstring>
#include <unistd.h>
using namespace std;

void DisplayPending(sigset_t pending)
{
    // 打印 pending 表
    cout << "当前进程的 pending 表为: ";
    int i = 1;
    while (i < 32)
    {
        if (sigismember(&pending, i))
            cout << "1";
        else
            cout << "0";

        i++;
    }
    cout << endl;
}

void handler(int signo)
{
    cout << "捕捉到了一个信号:" << signo << endl;
    while (true)
    {
        // 获取进程的pending表
        sigset_t pending;
        sigemptyset(&pending);

        int n = sigpending(&pending);
        if (n < 0)
        {
            continue;
        }

        DisplayPending(pending);
        sleep(1);
    }
    exit(0);
}

int main()
{
    struct sigaction act, oldact;

    // 初始化结构体对象(可选)
    memset(&act, 0, sizeof(act));
    memset(&oldact, 0, sizeof(oldact));

    // 设置信号处理函数
    act.sa_handler = handler;

    //  给2号信号注册自定义动作
    sigaction(2, &act, &oldact);

    while (true)
    {
        cout << "I am a process: " << getpid() << endl;
        sleep(1);
    }

    return 0;
}

【程序结果】

在这里插入图片描述

五、进程信号总结

截至目前,信号 处理的所有过程已经全部学习完毕了

  • 信号产生阶段:有四种产生方式,包括 键盘键入、系统调用、软件条件、硬件异常

  • 信号保存阶段:内核中存在三张表,blcok 表、pending 表以及 handler 表,信号在产生之后,存储在 pending 表中

  • 信号处理阶段:信号在 内核态 切换回 用户态 时,才会被处理

在这里插入图片描述

六、相关代码

本篇博客的代码:点击跳转

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue.js入门

目录 一. 框架概述 二. vue常用命令 2.1 插值表达式 2.2 v-text 2.3 v-html 2.4 v-on 2.5 v-model 2.6 v-show 2.7 v-if 2.8 v-else 2.9 v-bind 2.10 v-for 三. vue生命周期函数 目录 一. 框架概述 二. vue常用命令 2.1 插值表达式 2.2 v-text 2.3 v-html 2…

Chapter 5: 二叉树详解

在探索计算机科学和编程世界的旅途中&#xff0c;数据结构是构成程序骨干的重要组成部分。它们不仅仅是存储数据的容器&#xff0c;更是提高算法效率、优化资源使用的关键。在众多的数据结构中&#xff0c;二叉树以其独特的结构和灵活性&#xff0c;成为了实现高效算法和解决复…

智能编程,一触即发:使用AIGC优化CSS——提升前端开发效率与质量

文章目录 一、AIGC在CSS优化中的应用场景智能代码生成自动布局调整性能优化建议样式和色彩建议 二、使用AIGC优化CSS的具体步骤明确需求选择AIGC工具输入描述或设计稿审查和调整集成和测试 三、AIGC优化CSS的优势与挑战优势&#xff1a;挑战&#xff1a; 《CSS创意项目实践&…

按图搜索新体验:阿里巴巴拍立淘API返回值详解

阿里巴巴拍立淘API是一项基于图片搜索的商品搜索服务&#xff0c;它允许用户通过上传商品图片&#xff0c;系统自动识别图片中的商品信息&#xff0c;并返回与之相关的搜索结果。以下是对阿里巴巴拍立淘API返回值的详细解析&#xff1a; 一、主要返回值内容 商品信息 商品列表…

<PLC><HMI><汇川>在汇川HMI画面中,如何为UI设置全局样式?

前言 汇川的HMI软件是使用了Qt来编写的,因此在汇川的HMI程序编写过程,是支持使用qt的样式来自定义部件样式的,即qss格式。 概述 汇川的软件本身提供三个系统的style样式,我们可以直接使用,但是,如果系统提供的样式不符合你的需求,那么你可以对其进行修改,或者自己新建…

Docker无法拉取镜像!如何解决?

问题现象 继去年Docker Hub被xxx后&#xff0c;各大NAS的注册表均出现问题&#xff0c;例如群晖的Docker套件注册表无法连接&#xff08;更新至DSM7.2版本后恢复&#xff09;。而在今年2024年6月初&#xff08;约2024.06.06&#xff09;&#xff0c;NAS中最重要的工具Docker又…

RV1126 Linux 系统,接外设,时好时坏(二)排查问题的常用命令

在 RV1126 Linux 系统中,排查外设连接问题时,可以使用多种命令来诊断和调试。以下是一些常用的命令和工具: 1. 查看系统日志 dmesg: 显示内核环形缓冲区的消息,通常包含设备初始化、驱动加载和错误等信息。 dmesg | grep <设备名或相关关键字>journalctl: 查看系统…

内网横向:PTHPTKPTT

1.PHT横向 2.PTK横向 3.PTT横向 1.PHT横向&#xff1a; 条件&#xff1a;有管理员的NTLM Hash 并且目标机器开 放445端口 在工作组环境中&#xff1a; Windows Vista 之前的机器&#xff0c;可以使用本地管理员组内用户进行攻击。 WindowsVista 之后的机器&#xff0c;只能是…

怎么将图片转为pdf?教你5种图片转pdf小技巧

在日常的学习办公中&#xff0c;图片转PDF的需求日益增多&#xff0c;无论是整理旅行照片、工作报告还是学习资料&#xff0c;将图片转换为PDF格式都能让文件更加规范、易于分享和保存。下面给大家分享5种能够将图片转为PDF格式的方法&#xff0c;让你的文档处理变得轻松又高效…

HTTP 缓存

缓存 web缓存是可以自动保存常见的文档副本的HTTP设备&#xff0c;当web请求抵达缓存时&#xff0c;如果本地有已经缓存的副本&#xff0c;就可以从本地存储设备而不是从原始服务器中提取这个文档。使用缓存有如下的优先。 缓存减少了冗余的数据传输缓存环节了网络瓶颈的问题…

UI界面卡顿检测工具--UIHaltDetector

引言&#xff1a; 在日常工作当中&#xff0c;我们经常会遇到软件的界面出现卡顿的问题&#xff0c;而为了确定卡顿原因&#xff0c;我特地写了一个UI界面卡顿的小工具&#xff1a;UIHaltDetector&#xff1b;该工具可以在检测到目标窗口出现卡顿的时候直接打印堆栈日志和输出…

Android 15 适配整理——实践版

背景 谷歌发布Android 15后&#xff0c;国内的手机厂商迅速行动&#xff0c;开始了新系统的适配工作。小米、OPPO、vivo和联想等金标联盟成员联合发布了适配公告&#xff0c;督促APP开发者在2024年8月31日前完成适配工作&#xff0c;否则将面临搜索标签提示、应用降级、分机型…

邮件安全篇:邮件反垃圾系统运作机制简介

1. 什么是邮件反垃圾系统&#xff1f; 邮件反垃圾系统是一种专门设计用于检测、过滤和阻止垃圾邮件的技术解决方案。用于保护用户的邮箱免受未经请求的商业广告、诈骗信息、恶意软件、钓鱼攻击和其他非用户意愿接收的电子邮件的侵扰。 反垃圾系统的常见部署形式 2. 邮件反垃圾…

win11 安装 Gradle

一、win11 安装Gradle(7.5.1)&#xff1a; 1.1、下载二进制包 Gradle下载页面 1.2、配置环境变量 变量名&#xff1a;GRADLE_HOME 变量值&#xff08;二进制包解压路径&#xff09;&#xff1a;D:\develop-tool\gradle-7.5.1 变量名&#xff1a;GRADLE_USER_HOME 变量值&a…

6 C 语言指针的奥秘:理论与实践详解

目录 1 变量访问机制 1.1 内存地址 1.2 变量的直接访问 1.3 变量的间接访问 2 指针变量及其内存大小 2.1 指针与指针变量 2.2 指针变量的定义格式 2.3 指针变量的内存大小 3 取地址操作符与取值操作符 3.1 取地址操作符 & 3.2 取值操作符 * 3.3 解引用与数据类…

Android APP CameraX应用(02)预览流程

说明&#xff1a;camera子系统 系列文章针对Android12.0系统&#xff0c;主要针对 camerax API框架进行解读。 1 CameraX简介 1.1 CameraX 预览流程简要解读 CameraX 是 Android 上的一个 Jetpack 支持库&#xff0c;它提供了一套统一的 API 来处理相机功能&#xff0c;无论 …

基于微信小程序的高校排课系统 /基于微信小程序的排课管理系统/课程管理系统

摘 要 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时代&a…

PE文件(十二)导入表

导入表 导入表的引入 当一个PE文件&#xff08;如.dll/.exe等&#xff09;需要使用别的模块的函数&#xff0c;也叫做依赖某模块&#xff0c;就需要一个清单来记录使用的模块&#xff08;一般为.dll文件&#xff0c;为方便理解&#xff0c;以后我们将模块都认为是.dll文件&am…

electron安装及快速创建

electron安装及快速创建 electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 详细内容见官网&#xff1a;https://www.electronjs.org/zh/docs/latest/。 今天来记录下练习中的安装过程和hello world的创建。 创建项目文件夹&#xff0c;并执行npm 初始化命…

将iPad 作为Windows电脑副屏的几种方法(二)

将iPad 作为Windows电脑副屏的几种方法&#xff08;二&#xff09; 1. 前言2. EV 扩展屏2.1 概述2.2 下载、安装、连接教程2.3 遇到的问题和解决方法2.3.1 平板连接不上电脑 3. Twomon SE3.1 概述3.2 下载安装教程 4. 多屏中心&#xff08;GlideX&#xff09;4.1 概述4.2 下载安…