【Linux】第三十八站:信号处理

news2024/11/20 6:34:14

文章目录

  • 一、信号处理
  • 二、再谈进程地址空间
  • 三、内核如何实现信号的捕捉
  • 四、sigaction

一、信号处理

我们知道,信号保存以后,会在合适的时候进行处理这个信号。

那么信号是如何被处理的?什么时候进行处理呢?

当我们的进程从内核态返回到用户态的时候,进行信号的检测和处理!

内核态:就是允许访问操作系统的代码和数据

用户态:只能访问用户自己的代码和数据

当我们使用系统调用的时候,操作系统是会自动做“身份”切换的,用户身份变为内核身份,或者反着来

我们的CPU它可以响应外部的中断,也可以内部直接产生中断

下面这条汇编指令,它可以直接从用户态陷入内核态。即就可以访问操作系统的代码了

int 80

image-20240127153506433

二、再谈进程地址空间

如下图所示,我们都是比较熟悉的

image-20240127154824874

我们会发现,进程地址空间中,有一个3G的用户空间,和1G的内核空间。

我们前面所谈的所有东西,都是这3GB的用户空间中的内容

上面的这1GB内核空间,它映射的是操作系统的代码和数据

而这内核空间会通过内核级页表映射操物理内存的底部!(当然也有的会通过直接映射到物理内存,这里我们不讨论它)

image-20240127160445194

那么用户级页表有几份?

有几个进程,就有几份用户级页表,进程具有独立性

内核级页表有几份?

内核级页表只有一份,即每个进程看到的34GB的东西是一样的!在整个系统中,进程再怎么切换,34GB的空间的内容是不变的!!!


所以站在进程的视角:调用系统中的方法,就是在我们自己的地址空间中进行执行的

站在操作系统的视角:任何一个时刻,都会有进程执行(即便一个进程都没有,操作系统也有自己对应的进程在运行。只需要它自己的内核空间即可)。我们想要执行操作系统的代码,就可以随时执行!


操作系统的本质:

基于时钟中断的一个死循环!


我们可能会疑惑,我们自己的代码是让操作系统去运行的。是谁让操作系统去运行的呢?

其实在计算机硬件中,有一个时钟芯片,每隔很短的时间(纳秒级别),向计算机发送时钟中断。

如下图所示,时钟会不断的发送时钟中断,然后CPU中的寄存器接收到信号以后,去中断向量表中寻找对应的方法,去执行。比如里面就会进行检查时间片到了没有,如果到了,就把这个进程剥夺下去,换下一个进程。从而完成进程的调度。然后上面的不断的进行死循环。

也就是被动的由时钟去驱动操作系统。

image-20240127163310590

操作系统的最后会卡在这样一个死循环里面,然后就靠着时钟中断来进行驱动

for(;;) pause();

我们知道,用户只能访问自己的代码和数据;无法直接访问操作系统的代码和数据。因为操作系统不相信任何人。

我们知道,在CPU当中,有一个CR3寄存器,指向这个页表

image-20240127164713497

在CPU当中还有一个ecs寄存器。它最低的两个比特位中。有两种权限位。

一个是0,一个是3。如果是0表示内核态,如果是3表示用户态

如果我们想要访问内核的代码,我们必须把这个3设置为0

这就叫做进入内核态。就允许去访问操作系统的代码和数据了。

image-20240127170822815

所以我们到底处于用户态还是内核态,就是由CPU来决定的

那么CPU就必须提供一个方法,让我们可以去改变这个工作级别。也就是下面的这个方法,陷入内核。

int 80//陷入内核

所以前面的这张图中

是基于用户捕捉代码的

image-20240127172142711

第一步第二步比较好理解

在第三步中,这个do_signal()函数会先遍历这个pending表,如果pending表中有1,那么在看一下block表,如果为1,不管这个信号,继续遍历pending表,直到遇到pending表有1,block表中为0以后。先将pending表对应的位置从1置为0,在看handler表,如果是SIG_DFL,那么则在内核中执行默认的动作。如果是SIG_IGN,那么就相当于忽略,直接返回代码。如果是自定义的handler,那么要先变为用户态(因为操作系统不想去执行我们的代码,不想让在内核中执行,害怕我们的代码中有非法的操作),然后执行第四步。

注意,先将pending表的位置置为0,才去执行的handler

在第四步中,我们的信号处理完成以后,它会自动系统调用sigretum系统调用接口返回内核。这里它在执行第四步之前会先将sigreturn进行入栈帧,然后在去入这个函数的栈帧。当这个函数的栈帧结束后,自然就会调用这个系统调用了

在第五步中,在去使用sys_sigreturn()函数去返回用户态。返回到我们原来的代码

我们可以用下面这张图去理解上面的过程

image-20240127173857090

三、内核如何实现信号的捕捉

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。

由于信号处理函数的代码是在用户空间的,处理过程比较复杂举例如下:

用户程序注册了SIGQUIT信号的处理函数sighandler。 当前正在执行main函数,这时发生中断或异常切换到内核态。 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是 两个独立的控制流程。sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。

image-20240127181851761

其实在操作系统中,所有的系统调用都是一张函数指针表,调用的时候也就是将对应的编号写入到寄存器中,就可以通过函数指针表去进行调用了

四、sigaction

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

这个函数和signal是很相似的。都是捕捉信号的

第一个参数是信号编号

第二个参数和第三个参数中,函数名和结构体名字是一样的,这样是可以的。不过不推荐

第二个参数是输入型参数,负责把数据结构设置为它

第三个参数是输出型参数,负责把设置之前老的结构给返回

返回值中,0是成功,-1是失败

如下所示,是这个结构体的内容

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

在这个结构体中,我们只关心这两个字段。其他的字段与实时信号有关

image-20240127183448674

第一个参数就是信号捕捉的处理方法。

如下样例所示可以简单的先用起来这个函数

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

void handler(int signo)
{   
    cout << "I catch a signal: " << signo << endl;
}

int main()
{
    struct sigaction act, oact;
    memset(&act, 0, sizeof(act));
    memset(&oact, 0, sizeof(oact));

    act.sa_handler = handler;
    sigaction(2, &act, &oact);

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

运行结果为

image-20240127185017572

pending位图,什么时候从1 -> 0?

我们可以用这段代码来进行测试

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

void PrintPending()
{
    sigset_t set;
    sigpending(&set);
    for(int signo = 31; signo >= 1; signo--)
    {
        cout << sigismember(&set, signo);
    }
    cout << endl << endl;
}

void handler(int signo)
{   
    PrintPending();
    cout << "I catch a signal: " << signo << endl;
}

int main()
{
    struct sigaction act, oact;
    memset(&act, 0, sizeof(act));
    memset(&oact, 0, sizeof(oact));

    act.sa_handler = handler;
    sigaction(2, &act, &oact);

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

运行结果为

image-20240127185810536

所以pending位图,执行捕捉方法之前,先清0,在调用

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。

我们可以用如下代码来进行验证

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

void PrintPending()
{
    sigset_t set;
    sigpending(&set);
    for(int signo = 31; signo >= 1; signo--)
    {
        cout << sigismember(&set, signo);
    }
    cout << endl << endl;
}

void handler(int signo)
{   
    cout << "I catch a signal: " << signo << endl;
    while(true)
    {
        PrintPending();
        sleep(1);
    }

}

int main()
{
    struct sigaction act, oact;
    memset(&act, 0, sizeof(act));
    memset(&oact, 0, sizeof(oact));

    act.sa_handler = handler;
    sigaction(2, &act, &oact);

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

运行结果如下所示:

当我们在捕捉处理的过程中,再次发送信号,发现pending为1,也就意味着没有被执行,即被被阻塞了。那么只能是block表被置为1了

image-20240127192011131

即操作系统不允许对某个信号重复捕捉,最多只能捕捉一层

信号被处理的时候,对应的信号也会被添加到block表中,防止信号捕捉被嵌套调用

sigaction中的sa_mask字段代表什么呢?

这个字段是一个sigset_t 类型的字段。它代表着屏蔽的信号。也就是会将block表给设置

如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需
要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。

我们用如下代码来进行演示

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

void PrintPending()
{
    sigset_t set;
    sigpending(&set);
    for(int signo = 31; signo >= 1; signo--)
    {
        cout << sigismember(&set, signo);
    }
    cout << endl;
}

void handler(int signo)
{   
    cout << "I catch a signal: " << signo << endl;
    while(true)
    {
        PrintPending();
        sleep(1);
    }

}

int main()
{
    struct sigaction act, oact;
    memset(&act, 0, sizeof(act));
    memset(&oact, 0, sizeof(oact));
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, 1);
    sigaddset(&act.sa_mask, 3);
    sigaddset(&act.sa_mask, 4);

    act.sa_handler = handler;
    sigaction(2, &act, &oact);

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

运行结果为

image-20240127193829636

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

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

相关文章

数据结构三:线性表之单链表(带头结点单向)的设计与实现

线性表的链式存储结构正是所谓的单链表&#xff0c;何谓单链表&#xff1f;通过地址将每一个数据元素串起来&#xff0c;进行使用&#xff0c;这可以弥补顺序表在进行任意位置的插入和删除需要进行大量的数据元素移动的缺点&#xff0c;只需要修改指针的指向即可&#xff1b;单…

git配置用户名和邮箱

1.git 1.配置用户名和邮箱 2.git初体验 git init 初始化git仓库 管理项目让git管理你的本次代码变更 git add .git commit -m “你完成的功能” 后续如果新增/修改/删除代码&#xff0c; 完成新功能时 重复2 3.查看日志 1.git log 4.版本回退 1.查看提交的版本记录 git l…

扭蛋机小程序开发:探索用户体验与商业价值的融合

一、引言 随着移动互联网的快速发展&#xff0c;小程序作为一种新型的应用形态&#xff0c;正逐渐改变着人们的生活方式。扭蛋机小程序便是其中一例&#xff0c;它结合了线上线下的互动体验&#xff0c;为用户带来了全新的娱乐方式。本文将探讨扭蛋机小程序的开发过程&#xf…

遇到字符串拼接用它就对啦!什么你居然不知道Java中对象作为方法参数和基本数据类型作为参数的区别?有巨坑!

今天刷代码随想录&#xff0c;在使用字符串拼接时&#xff0c;发现String类确实比StringBuilder慢了不是&#xff0c;总结了StringBuilder类&#xff08;详见下面文章内容&#xff0c;点击可跳转&#xff09;&#xff0c;还有在做后两题时&#xff0c;发现了Java中集合作为参数…

二刷代码随想录|Java版|回溯算法1|回溯基础理论+组合问题

理论 写链表之类的真的很痛苦&#xff0c;赶紧跳到回溯&#xff01;这次我想结合算法设计这本书&#xff0c;把java版写出来。放在第三部分吧。希望能够在研一完成这项工作&#xff01; 从一刷总结以下的几个要点&#xff1a; 回溯方法模板性非常强&#xff01;&#xff01;可…

redis报错:WRONGTYPE Operation against a key holding the wrong kind of value

这个是在redis存取的数据时&#xff0c;存数据时的数据类型和取数据时的数据类型不一致导致的 原因分析 首先需要明白的是&#xff0c;出现这种错误的原因是因为我们在取值的时候&#xff0c;使用的命令不对&#xff0c;比如你用获取string类型的get命令去取列表list类…

前端工程化之上cdn

一、cdn介绍 cdn的使用还是和前端打包相关&#xff0c;我们都希望前端最后的打包页面越小越好。那么可不可以把一些包不pack进去&#xff0c;让用户的流浪器自行下载呢&#xff1f;答案是可以的&#xff0c;那这些包就会被托管到分发站点上&#xff0c;就是在全国都有服务器&a…

Vue3探索编辑部——关于Pinia(1)

目录 什么是Pinia&#xff1f; Vue3中的Pinia 创建项目 数据准备和引入Pinia 使用Pinia 采用action修改数据 总结 什么是Pinia&#xff1f; Pinia是Vue3的专属的状态管理工具&#xff0c;什么是状态呢&#xff1f;其实我们可以把状态理解为数据&#xff0c;或者一个业务…

(七)for循环控制

文章目录 用法while的用法for的用法两者之间的联系可以相互等价用for改写while示例for和while的死循环怎么写for循环见怪不怪表达式1省略第一.三个表达式省略&#xff08;for 改 while&#xff09;全省略即死循环&#xff08;上面已介绍&#xff09; 用法 类比学习while语句 …

Linux:命名管道及其实现原理

文章目录 命名管道指令级命名管道代码级命名管道 本篇要引入的内容是命名管道 命名管道 前面的总结中已经搞定了匿名管道&#xff0c;但是匿名管道有一个很严重的问题&#xff0c;它只允许具有血缘关系的进程进行通信&#xff0c;那如果是两个不相关的进程进行通信&#xff0…

C#,计算几何,二维贝塞尔拟合曲线(Bézier Curve)参数点的计算代码

Pierre Bzier Bzier 算法用于曲线的拟合与插值。 插值是一个或一组函数计算的数值完全经过给定的点。 拟合是一个或一组函数计算的数值尽量路过给定的点。 这里给出 二维 Bzier 曲线拟合的参数点计算代码。 区别于另外一种读音接近的贝塞耳插值算法&#xff08;Bessels int…

市场复盘总结 20240123

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 一支股票&#xff0c;只有10%的时间是可以操作&#xff0c;90%的时候都应该空仓 昨日主题投资 连板进级率 7/1…

前端实现转盘抽奖 - 使用 lucky-canvas 插件

目录 需求背景需求实现实现过程图片示意实现代码 页面效果lucky-canvas 插件官方文档 需求背景 要求实现转盘转动抽奖的功能&#xff1a; 只有正确率大于等于 80% 才可以进行抽奖&#xff1b;“谢谢参与”概率为 90%&#xff0c;“恭喜中奖”概率为 10%&#xff1b; 需求实现 实…

鸿蒙入门学习的一些总结

前言 刚开始接触鸿蒙是从2023年开始的&#xff0c;当时公司在调研鸿蒙开发板能否在实际项目中使用。我们当时使用的是OpenHarmony的&#xff0c;基于DAYU/rk3568开发板&#xff0c;最开始系统是3.2的&#xff0c;API最高是API9&#xff0c;DevecoStudio 版本3.1的。 鸿…

国考省考行测:分析推理,形式逻辑,所有有的分析

国考省考行测&#xff1a; 2022找工作是学历、能力和运气的超强结合体! 公务员特招重点就是专业技能&#xff0c;附带行测和申论&#xff0c;而常规国考省考最重要的还是申论和行测&#xff0c;所以大家认真准备吧&#xff0c;我讲一起屡屡申论和行测的重要知识点 遇到寒冬&am…

2024三掌柜赠书活动第七期:一本书读懂AIGC:探索AI商业化新时代

目录 前言AI商业化的背景和挑战关于《一本书读懂AIGC&#xff1a;探索AI商业化新时代》编辑推荐内容简介作者简介图书目录书中前言/序言《一本书读懂AIGC&#xff1a;探索AI商业化新时代》全书速览结束语 前言 不用多讲&#xff0c;想必大家也都知道&#xff0c;人工智能在过…

LLM大语言模型(五):用streamlit开发LLM应用

目录 背景准备工作切记streamlit开发LLM demo开一个新页面初始化session先渲染历史消息接收用户输入模拟调用LLM 参考 背景 Streamlit是一个开源Python库&#xff0c;可以轻松创建和共享用于机器学习和数据科学的漂亮的自定义web应用程序&#xff0c;用户可以在几分钟内构建一…

optee编译调试

编译运行 使用的是 ubuntu22.04 需要提前设置好网络 optee运行环境搭建&#xff1a;https://optee.readthedocs.io/en/latest/building/prerequisites.html 安装必要的库 sudo apt install -y \adb \acpica-tools \autoconf \automake \bc \bison \build-essential \ccach…

MySQL十部曲之六:数据操作语句(DML)

文章目录 前言语法约定DELETEINSERTSELECT查询列表SELECT 选项子句FROMWHEREORDER BYGROUP BYHAVINGWINDOWLIMITFOR SELECT ... INTO连接查询CROSS JOIN和INNER JOINON和USINGOUTER JOINNATURE JOIN 子查询标量子查询使用子查询进行比较带有ANY、IN或SOME的子查询带有ALL的子查…

C++ //练习 3.5 编写一段程序从标准输入中读入多个字符串并将它们连接在一起,输出连接成的大字符串。然后修改上述程序,用空格把输入的多个字符串分隔开来。

C Primer&#xff08;第5版&#xff09; 练习 3.5 练习 3.5 编写一段程序从标准输入中读入多个字符串并将它们连接在一起&#xff0c;输出连接成的大字符串。然后修改上述程序&#xff0c;用空格把输入的多个字符串分隔开来。 环境&#xff1a;Linux Ubuntu&#xff08;云服务…