【Linux】信号之信号的产生详解

🤖个人主页:晚风相伴-CSDN博客

💖如果觉得内容对你有帮助的话,还请给博主一键三连(点赞💜、收藏🧡、关注💚)吧

🙏如果内容有误的话,还望指出,谢谢!!!

✨下一篇文章《信号的阻塞》,敬请期待

目录

✨初识信号 

🔥从程序员角度看待信号 

理解组合键变成信号的原理

🔥常见的信号

理解信号为什么要被进程保存起来

🔥理解信号发送的本质

信号的常见处理方式

✨信号的产生

🔥Core Dump

初识信号的捕捉

🔥由系统调用产生信号

kill函数 

raise函数

abort函数

理解系统调用发送信号

🔥由软件条件产生信号

管道 

 alarm函数

理解软件条件给进程发送信号

🔥由硬件条件产生信号 

✨总结


在我们的生活中充满了大量的信号,我们一看到这些信号就能反应出后续的一系列动作,比如:红绿灯、交警的手势、你打游戏时给队友发送的信号等等。

举一个详细点的例子:在双11的时候,你在网上购买了许多的商品,并且在等待不同商品快递的到来。但是即使是快递还没到,你也能够清楚的判断正在运送的是否是你的快递(根据快递的单号),这也就是你能“识别快递”。当快递员到了你家楼下,你收到了快递到来的通知,但是你正在打游戏,并且正是关键时刻,需要5分钟之后才能拿下去拿快递。那么在这5分钟之内你并没有下楼取快递,但是你是知道快递已经到了的,也就是拿快递的行为并不是一定要立即执行,可以理解为“在合适的时候去拿快递”。从收到快递已到的通知再到你拿快递,这期间是有一个时间窗口的,在这段时间内,你并没有拿到快递,但是你知道快递已经到了。本质上是“你知道了有一个快递需要去拿”,但是此时你正在忙,当时间合适的时候,你会去将快递拿回来。当你将快递拿回来之后,需要处理这个快递,而处理这个快递一般方式有三种:1、拆开快递并自己使用(执行默认动作)  2、拆开快递立马送给别人(执行自定义动作)  3、快递拿回来后丢在一边,继续开一把游戏(忽略快递)。

这快递到来的整个一过程对你来说是异步的,你可以做你自己的事,因为你不能确定快递员什么时候把你的快递送到。

✨初识信号 

🔥从程序员角度看待信号 

当你在前台启动了一个进程,并且这个进程不会自己退出,这时你就可以从键盘输入快捷键Ctrl+C将这个进程终止掉。当你在键盘输入Ctrl+C时会产生一个硬件中断,被操作系统获取,并且操作系统会将其解释成2号信号,发送给前台进程,前台进程收到这个信号,进而引起前台进程的退出。

这里的进程就是对应上面例子中的你,而操作系统对应的就是快递员,信号对应的就是快递

因此得出结论

Linux信号本质是一种通知机制,用户或者操作系统通过发送相应的信号通知进程某些事件已经发生,你可以在后续(合适的时机)对这个信号进行处理。 

理解组合键变成信号的原理

键盘的工作方式是通过中断方式进行的,当你按下一个组合键时,键盘硬件会识别出这是一个特殊的组合,并生成相应的中断。Linux内核中的中断处理程序会捕获这个硬件中断,并且读取键盘的状态,确定是哪个键或者组合键被按下了。之后根据键盘中断的信息,中断处理程序就会生成一个或多个信号。

🔥常见的信号

使用kill -l命令可以查看信号列表

前1-31个信号为普通信号,后34-64个信号为实时信号,本篇文章只讨论普通信号,有兴趣的可以自行去了解一下实时信号。每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到。

理解信号为什么要被进程保存起来

我们知道进程在收到信号时并不是立即对信号进行处理,而是在合适的时机进行处理,那么信号就需要被我们的进程保存起来,所以在进程的PCB中肯定具有保存信号的相关数据结构。

那么用什么数据结构来保存信号最合适呢

这个数据结构要能体现这是一个什么信号、这个信号是否产生等,并且我们的信号只有62个,说到这,想必你已经猜到了,对,没错,用位图来保存信号是为最合适,每个信号都可以用一个二进制位来表示其状态。通过使用位图,Linux操作系统可以非常快速地检查、设置或清除某个信号的状态,而不需要遍历整个信号列表或者执行其它复杂的操作。

🔥理解信号发送的本质

信号发送的本质:操作系统修改目标进程的PCB中的指定信号的位图结构,将指定信号的二进制位由0 -> 1。

信号的常见处理方式

  1. 默认(进程自带的,程序员写好的逻辑)
  2. 忽略(不做处理)
  3. 自定义捕捉(捕捉信号)

✨信号的产生

🔥Core Dump

如果core dump是被打开的,进程在收到某些信号而异常终止时,操作系统会将此时进程地址空间中的内容以及有关进程状态的其它信息一同写入一个磁盘文件中,生成的这个文件的文件名通常是core,这就叫做core dump,也叫做核心转储。这个文件通常用于调试,可以帮助我们快速的定位到出错的地方。

还记得在上一篇文章进程控制中的一幅图

用一个比特位来表示core dump是否被打开,下面我们就会用代码提取这个比特位进行验证

如果你使用的和我一样是云服务器,那么一般核心转储的功能是关闭的,需要我们手动打开。

使用ulimit -a查看core dump

使用ulimit -c对core dump进行修改

示例代码

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;

int main()
{
    pid_t id  = fork();
    if(id == 0)
    {
        sleep(1);
        int a = 100;
        a /= 0;//除0错误
        exit(1);
    }
    int status = 0;
    waitpid(id, &status, 0);
    cout << "父进程pid: " << getpid() << " 子进程id: " << id << " 退出信号: " << (status & 0x7F) \
    << " core dump: " << ((status >> 7) & 1) << endl;

    return 0;
}

结果演示

利用生成的core dump文件定位问题

但是并不是所有的信号都会生成core dump文件的

man 7 signal查看手册

只有Action是core的才会生成core dump文件。

一般在生产环境中都是会关闭core dump的,原因如下

  1. 浪费磁盘空间:core dump文件非常大,会占用大量的磁盘空间
  2. 不安全:core dump文件可能包含敏感信息,如密码等,增加泄漏的风险
  3. 影响性能:生成的core dump会消耗CPU资源,影响系统性能。

初识信号的捕捉

参数

  • signum:对应的信号编号
  • handler:回调函数,通过回调的方式来修改对应信号的捕捉方法

返回值:一般不关心

示例代码如下

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

void handler(int signum)
{
    cout << "捕捉到一个信号: " << signum << endl;
}

int main()
{
    signal(2, handler);

    while(true) sleep(1);
    return 0;
}

结果演示

2号信号已经被我们自定义捕捉了,那么我们如何将这个进程终止掉呢?

可以使用kill命令将进程终止

3号信号(SIGQUIT)——进程退出(对应的快捷键:Ctrl+\)

🔥由系统调用产生信号

kill函数 

 参数

  • pid:目标进程的pid
  • sig:要发送的信号

返回值:成功返回0,失败返回-1

示例代码 

#include <iostream>
#include <string>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>

using namespace std;

void Usage(string proc)
{
    cout << "Usage:\r\n\t" << proc << " signumber processid" << endl;
}

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }

    int signumber = atoi(argv[1]);
    int processid = atoi(argv[2]);

    kill(processid, signumber);
    return 0;
}

结果演示

raise函数

自己给自己发送信号

参数

  • sig:要发送的信号

返回值:成功返回0,失败返回非0

示例代码

#include <iostream>
#include <string>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>

using namespace std;

int main(int argc, char* argv[])
{
    raise(8);//自己给自己发送信号
    return 0;
}

8号信号(SIGFPE)——浮点异常(比如除0错误、浮点型错误等) 

结果演示

abort函数

 

用于异常终止一个进程

没有返回值

示例代码

#include <iostream>
#include <string>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>

using namespace std;

int main(int argc, char* argv[])
{
    cout << "我开始执行我的代码了" << endl;
    sleep(1);
    abort();
    return 0;
}

结果演示 

 

理解系统调用发送信号

用户调用系统接口,执行操作系统对应的系统调用代码,操作系统提取其参数,向目标进程的PCB中的信号位图中修改信号标记位,进程会在合适的时候处理信号,执行相应的处理动作。

🔥由软件条件产生信号

管道 

在管道中,如果我们将读端关闭,写端一直写,写端将缓冲区写满后,操作系统会自动终止对应的写端进程,终止的本质是:操作系统向写端进程发送13号信号(SIGPIPE)。

示例代码 

#include <iostream>
#include <assert.h>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

int main()
{
    // 1.创建管道
    int pipefd[2] = {0}; // pipefd[0]:读端 , pipefd[1]: 写端
    int n = pipe(pipefd);
    assert(n != -1);
    (void)n;

    // 2.创建子进程
    pid_t id = fork();
    assert(id != -1);
    if (id == 0)
    {
        // 子进程
        close(pipefd[0]);
        // close(pipefd[0]);
        char buffer[1024 * 8];
        while (true)
        {
            string message = "我是子进程,我正在给你发消息";
            int count = 0;
            char send_buffer[1024];
            snprintf(send_buffer, sizeof(send_buffer), "%s[%d] : %d", message.c_str(), getpid(), count++);
            write(pipefd[1], send_buffer, strlen(send_buffer));
        }

        exit(0);
    }

    // 父进程
    close(pipefd[0]);

    int status = 0;
    pid_t ret = waitpid(id, &status, 0);
    assert(ret > 0);
    (void)ret;
    cout << "signumber: " << (status & 0x7F) << endl;
    close(pipefd[1]);
    return 0;
}

结果演示

 alarm函数

​ 

调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发送14号信号(SIGALARM),该信号默认的处理动作是终止当前进程。

这个函数的返回值是0或者是以前设定闹钟时间剩下的秒数。

打个比方,中午吃完饭午睡,我设定了一个30分钟的闹钟,但是在20分钟之后,我被外面的装修声给吵醒了,而我又不在想睡了,但是闹钟还有10分钟才会响,这10分钟就是设定闹钟时间剩下的秒数。

示例代码

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;

int main()
{
    alarm(1);
    int count = 0;
    while(true) 
    {
        cout << "count: " << count++ << endl;
    }
    return 0;
}

结果演示

这个程序的作用是1秒钟之内不停地数数,1秒钟到了就被SIGALRM信号终止。

理解软件条件给进程发送信号

操作系统先识别到某种软件条件触发或者不满足,之后操作系统就会构建相应的信号,并且发送给指定的进程。

🔥由硬件条件产生信号 

硬件异常是硬件通过某种检测方式识别到有错误并通知操作系统,然后操作系统会向当前进程发送适当的信号。例如当前进程中有除0错误,CPU内部的状态寄存器中的溢出标记位就会由0置1,操作系统识别到溢出标记位为1,立即找到该进程并且向该进程发送8号信号(SIGFPE),进程收到信号会在合适的时候进行处理。再比如当前进程访问了非法地址,寄存器中的MMU(内存管理单元)就会产生异常,操作系统就会识别到这个异常,并且立即找到该进程,向该进程发送11号信号(SIGSEGV),进程收到信号会在合适的时候进行处理。

示例代码

野指针异常

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

void handler(int signum)
{
    sleep(1);
    cout << "捕捉到一个信号: " << signum << endl;
}

int main()
{

    signal(SIGSEGV, handler);
    int *p= nullptr;
    *p = 100;
    return 0;
}

结果演示

​ 

先要明确一个点:一旦出现硬件异常,进程是不一定会退出的,但是一般默认是退出,因为进程即使不退出,我们也做不了什么。

在我们的结果演示中,虽然我们捕捉到了这个信号,但是为什么它会死循环呢?

因为寄存器中的异常一直没有被解决,操作系统就会一直识别到这个异常,也就一直会给该进程发送相应的信号。

✨总结 

  1. 所有信号的产生,最终都要有操作系统来进行执行,因为操作系统是进程的管理者
  2. 信号不是立即被处理的,是在合适的时机,进程会执行处理
  3. 信号不是被立即处理的,那么就需要被保存起来,信号会被保存在进程PCB中的信号位图上

 

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

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

相关文章

Golang——reflect(反射)

反射是指在程序运行期间对程序本身进行访问和修改的能力。 一. 变量的内在机制 变量包含类型信息和值信息类型信息&#xff1a;是静态的元信息&#xff0c;是预先定义好的值信息&#xff1a;是程序运行过程中动态改变的 二. 反射的使用 reflect包封装了反射相关的方法获取类型…

Unity数据持久化2——XML

简介&#xff1a; 基础知识 XML文件格式 XML基本语法 XML属性 练习&#xff1a; C#读取存储XML XML文件存放位置 读取XML文件 练习&#xff1a; 存储修改XML文件 练习&#xff1a; 总结 实践小项目 必备知识点 必备知识点——C#中XML序列化 必备知识点——C#中XML反序列化 必备…

奇偶数递增递减-第13届蓝桥杯选拔赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第70讲。 奇偶数递增递减&a…

SSE 与 SASE哪个云原生安全框架更加适合

近年来&#xff0c;随着云计算和网络技术的不断发展&#xff0c;出现了一种新的网络安全解决方案——SASE&#xff08;安全访问服务边缘&#xff09;。SASE是一种将网络和安全功能融合到单个基于云的服务中的框架&#xff0c;旨在提供更加安全、高效和便捷的网络访问体验。SASE…

MySQL之Schema与数据类型优化(三)

Schema与数据类型优化 BLOB和TEXT类型 BLOB和TEXT都是为存储很大的数据而设计的字符串数据类型&#xff0c;分别采用二进制和字符方式存储。 实际上它们分别属于两组不同的数据类型家族:字符类型是TINYTEXT&#xff0c;SMALLTEXT,TEXT&#xff0c;MEDIUMTEXT&#xff0c;LONG…

实现本地访问云主机,以及在云主机搭建FTP站点

前言 云计算是一种基于互联网的计算模式&#xff0c;通过网络提供按需访问的计算资源和服务。核心概念是把计算能力视作一种公共资源&#xff0c;用户可以根据自身需求动态分配和管理这些资源。 云主机 ECS (Elastic Compute Server)是一种按需获取的云端服务器&#xff0c;提…

【MySQL】库的操作和表的操作

库的操作和表的操作 一、库的操作1、创建数据库(create)2、字符集和校验规则&#xff08;1&#xff09;查看系统默认字符集以及校验规则&#xff08;2&#xff09;查看数据库支持的字符集&#xff08;3&#xff09;查看数据库支持的字符集校验规则&#xff08;4&#xff09;校验…

【stm32/CubeMX、HAL库】嵌入式实验五:定时器(2)|PWM输出

参考&#xff1a; 【【正点原子】手把手教你学STM32CubeIDE开发】 https://www.bilibili.com/video/BV1Wp42127Cx/?p13&share_sourcecopy_web&vd_source9332b8fc5ea8d349a54c3989f6189fd3 《嵌入式系统基础与实践》刘黎明等编著&#xff0c;第九章定时器&#xff0c…

【Linux】-Zookeeper安装部署[17]

简介 apache ZooKeeper是一个分布式的&#xff0c;开放源码的分布式应用程序协调服务&#xff0c;是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件&#xff0c;提供的功能包括&#xff1a;配置维护、域名服务、分布式同步、组服务等。 除了为Hadoop和H…

五步定位性能瓶颈

一、着手测试前的准备&#xff1a;优化数据流向与系统架构分析 在进行性能测试或系统优化之前&#xff0c;明确数据流向和系统架构的细节是至关重要的步骤。这不仅能够帮助识别潜在的瓶颈&#xff0c;还能确保测试用例设计的全面性与针对性。以下是关键步骤和方法&#xff1a;…

17.3zabbix主动模式和被动模式

主动与被动区别 默认是被动: 被动模式,100个监控,需要100个回合&#xff1b;&#xff08;zabbix-server依次去找每一个agent节点取值&#xff0c;效率低&#xff09; 主动模式,100个监控,需要1个回合&#xff1b;&#xff08;agent节点将自己的信息整理好主动去找zabbix-serve…

QT调用Tinyxml2库解析XML结构文件

在学习SVG结构的时候&#xff0c;发现SVG结构可以通过以XML文件直接解析&#xff0c;所以就去了解了Tinyxml2库的使用&#xff0c;相关教程也比较多。 个人感觉Tinyxml2库比官方的XML解析库更好用&#xff0c;这里做个技术总结&#xff0c;记录Tinyxml2库解析XML文件结构的简单…

计组期末必考大题

一.寻址方式详解 1.直接寻址 指令地址码直接给到操作数所在的存储单元地址 2.间接寻址 A为操作数EA的地址 3.寄存寻址 4.寄存器间接寻址 5.变址寻址 6.基地址寻址 7.小结 二、指令周期详解 一、基本概念 指令周期:去除指令并执行指令所需要的时间指令周期:由若干个CPU周…

分割训练日志的可视化

这一部分主要是将mmdetection训练得到的json文件可视化&#xff0c;代码主要源于github&#xff0c;具体哪一个忘记了&#xff08;readme里面没有原址…&#xff09;是专门做的mmdetection 结果可视化的&#xff0c;非常强&#xff01;&#xff01;。使用时如果出现keyerror的话…

Servlet的request对象

request对象的继承关系 1.HttpServletRequest接口继承了ServletRequest接口&#xff0c;对其父接口进行了扩展&#xff0c;可以处理满足所有http协议的请求 2.HttpServletRequest和ServletRequest都是接口&#xff0c;不能创建对象&#xff0c;因此在tomcat底层定义实现类并创…

文本三剑客之 sed 编辑器

一.sed 概述 1.sed 介绍 sed是一种流编辑器&#xff0c;流编辑器会在编辑器处理数据之前基于预先提供的一组规则来编辑数据流。 sed编辑器可以根据命令来处理数据流中的数据&#xff0c;这些命令要么从命令行中输入&#xff0c;要么存储在一个 命令文本文件中。 2.sed 的工…

基于python实现搜索的目标站点内容监测系统

基于python实现搜索的目标站点内容监测系统 开发语言:Python 数据库&#xff1a;MySQL所用到的知识&#xff1a;Django框架工具&#xff1a;pycharm、Navicat、Maven 系统功能实现 登录页面 后台的登录一般是为了管理员的管理方便进行一个用户权限的验证。也是为管理员提供的唯…

【贪心算法题目】

1. 柠檬水找零 这一个题目是一个比较简单的模拟算法&#xff0c;只需要根据手里的钱进行找零即可&#xff0c;对于贪心的这一点&#xff0c;主要是在20元钱找零的情况下&#xff0c;此时会出现两种情况&#xff1a;10 5 的组合 和 5 5 5 的组合&#xff0c;根据找零的特点&a…

DVWA代码审计--SQL注入

NO.1 Low 首先来看下代码 <?php if( isset( $_REQUEST[ Submit ] ) ) { // Get input $id $_REQUEST[ id ]; // Check database $query "SELECT first_name, last_name FROM users WHERE user_id $id;"; $result mysql_query( $query ) or die( <pre>…

vue中数据已经改变了,但是table里面内容没更新渲染!

解决方案&#xff1a; 给table或者el-table标签上添加一个动态key值&#xff0c;只要数据发生改变&#xff0c;key值变动一下即可 标签上&#xff1a; :key“timeStamp” 初始data&#xff1a;timeStamp:0, 更新数据&#xff1a;this.timeStamp 这样每次更新数据&#xff…