当前位置: 首页 > news >正文

【Linux修行路】信号的产生

目录

⛳️推荐

一、信号的产生

二、产生信号的系统调用

2.1 kill——给指定的进程发送指定的信号

2.2 模拟实现指令 kill

2.3 raise——给调用的进程发送指定的信号

2.4 abort——给调用者发送 6 号信号

三、验证哪些信号不可以被捕捉

四、为什么除0和解引用空指针会给进程发信号呢?

五、alarm——设置闹钟


⛳️推荐

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站【Linux修行路】动静态库详解点击跳转到网站

一、信号的产生

  • 键盘组合键ctrl+c 给进程发送2号信号;ctrl+\ 给进程发送3号信号;ctrl+z 给进程发送19号信号(该信号无法被 signal 信号捕捉)。

  • 指令kill -signo pid

  • 系统调用killraiseabort

  • (硬件)异常:例如常见的除0(Floating point exception),当程序中出现除0异常,操作系统就会给对应的进程发送 8 号信号,来终止进程(这是 8 号信号的默认动作);对空指针解引用(Segmentation fault),当程序中出现对空指针解引用的时候,操作系统会给对应的进程发送 11 号信号。

  • 软件条件:管道通信中,写端正常,读端关闭,操作系统会给写端进程发送 13 号信号,终止掉正在进行向管道中写入的进程。alarm 闹钟 。

无论信号是如何产生的,最终一定是由操作系统发送给进程的,因为操作系统是进程的管理者。

二、产生信号的系统调用

2.1 kill——给指定的进程发送指定的信号

image-20240308092323511

2.2 模拟实现指令 kill

#include <iostream>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
#include <string>using namespace std;void Manual(char *directives)
{cout << "\n\t" << directives << " signum pid\n\n";
}int main(int argc, char *argv[])
{if(argc != 3){Manual(argv[0]);exit(1);}int signum = stoi(argv[1]);int pid = std::stoi(string(argv[2]));cout << signum << ' ' << pid <<endl;pid_t ret = kill(pid, signum);if(ret == -1){perror("kill");exit(1);}return 0;
}

2.3 raise——给调用的进程发送指定的信号

image-20240308102100461

raise 就相当于:kill(getpid(), signum)

2.4 abort——给调用者发送 6 号信号

image-20240308102625369

abort 函数内部,不仅会执行自定义捕捉(前提是捕捉了 6 号信号),在执行完自定义捕捉之后,还要去执行 6 号信号默认的终止动作。通过 kill 指令去给进程发送 6 号信号,进程只会执行捕捉动作(前提是对 6 号进行了捕捉)或者只会执行默认动作(前提是没有对 6 号信号做捕捉)。

三、验证哪些信号不可以被捕捉

#include <iostream>
#include <signal.h>
#include <unistd.h>using namespace std;void myhandler(int signo)
{cout << "process get a signal: " << signo << endl;// exit(1);
}int main()
{// signal(2, myhandler);signal(19, myhandler);for(int i = 1; i <= 31; i++){signal(i, myhandler);}while(true){cout << "Hello Linux" << ' ' << getpid() << endl;sleep(1);}return 0;
}

先通过循环将31个信号都捕捉,然后在命令行通过 kill 给进程发送信号,看是否被成功捕捉。经过验证 9、19 号信号都无法被捕捉。其中 9 号信号是杀进程,19 号信号是暂停进程。

四、为什么除0和解引用空指针会给进程发信号呢?

以除0为例,首先,程序中所有的指令是需要被 CPU 来执行的,计算任务也不例外。所以 CPU 里面有特定的寄存器来存储操作数,还有一个状态寄存器,里面都是比特位级别的标记位,其中有一个标记位就是用来表示当前运算是否发生溢出,当 CPU 在执行除 0 的时候,会发生溢出,此时 CPU 就会去修改溢出标记位,这些寄存器中的内容,都属于当前进程的上下文数据,当进程切换的时候,这些数据同时也会被其它进程的上下文数据替换,当该进程再一次被替换进 CPU 执行的时候,这些上下文数据又会恢复出来。**所以任何异常只会影响该进程本身,并不会波及到操作系统。**CPU 是也是硬件资源,操作系统作为硬件的管理者,它是必须要关心硬件的健康,所以当 CPU 中的溢出标志位被设置成溢出的时候,操作系统一定是能知道的,(本质上,发生溢出会给操作系统发送中断),操作系统知道后就会向该进程发送对应的信号。

空指针解引用、越界访问等本质都是因为虚拟到物理转化失败,转化失败还导致硬件报错,最终被操作系统识别到。

程序对于异常信号,默认动作是让程序立即终止。但是我们可以在程序中对异常信号进行捕捉,如果捕捉方法里没有做特殊处理,比如说让程序退出,那这导致的后果就是程序不会立即终止,而是一直被调用运行,硬件错误就一直存在,操作系统就会一直给进程发送异常信号

总结:程序中出现的所有异常,最终一定会转化成硬件错误,操作系统能够识别这种硬件错误,最终给进程发送对应的信号。

image-20240308133955080

信号捕捉并不是为了让我们来解决问题的,而是当收到这个信号后,程序可能要被立即终止,信号捕捉给了我们应对程序即将被终止的机会,我们可以在捕捉函数里做一些数据保存,打印日志等工作。

五、alarm——设置闹钟

image-20240308135828669

  • seconds:闹钟将在 seconds 秒时候响起(给进程发送 14 号信号),如果 seconds == 0 ,则之前设置的闹钟会被取消,并将剩下的时间返回。

  • 返回值:返回之前闹钟的剩余秒数,如果之前未设闹钟,或者上一次设置的闹钟已经响过了,那么返回的就是 0 。

小Tips:如果在上一次闹钟还没响的时候,再一次调用 alarm 函数设置闹钟,那么这一次调用的返回值就是上一次闹钟的剩余时间,并且闹钟的响应时间会被更新成这一次的,上一次那个还没响的闹钟就会被作废。

设置闹钟

#include <iostream>
#include <unistd.h>using namespace std;int main()
{int n = alarm(5);while(true){cout << "process is running..." << endl;sleep(1);}return 0;
}

image-20240308141730584

捕捉闹钟信号

#include <iostream>
#include <signal.h>
#include <unistd.h>using namespace std;void handler(int signum)
{cout << "get a signal: " << signum << endl;
}int main()
{signal(14, handler);int n = alarm(5);while(true){cout << "process is running..." << endl;sleep(1);}return 0;
}

image-20240308142040976

alarm 返回值验证

#include <iostream>
#include <signal.h>
#include <unistd.h>using namespace std;void handler(int signum)
{cout << "get a signal: " << signum << endl;int n = alarm(5); // 闹钟每五秒响一次cout << n << endl;
}int main()
{signal(14, handler);int n = alarm(50);while(true){cout << "process " << getpid() << " is running..." << endl;sleep(1);}return 0;
}

第一次设置的闹钟是 50 秒,在前 50 秒内,通过命令行向进程发送 14 号信号,此时程序就会去执行 handler 方法,在该方法中又调用了一个 alarm,这就是前一个闹钟还没响,就又设置了一个闹钟。

在这里插入图片描述

🎁结语:

        今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,您的支持就是我前进的动力!

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 性能测试的复习3-jmeter的断言、参数化、提取器
  • Apache Pulsar 与 Kafka Streams
  • Java后端开发(十六)-- JavaBean对象拷贝工具类:运用反射机制,实现对象的深拷贝
  • 速盾:cdn海外加速服务是什么?
  • 3C电子胶黏剂在手机制造方面有哪些关键的应用
  • 2024年9月12日(k8s环境及测试 常用命令)
  • springboot系列--yaml配置文件使用
  • spring项目整合log4j2日志框架(含log4j无法打印出日志的情况,含解决办法)
  • 数据结构算法——排序算法
  • 【计算机毕业设计】微信小程序的美甲店铺座位预约系统
  • 小程序面试题七
  • 图论篇--代码随想录算法训练营第五十六天打卡| 108. 冗余连接,109. 冗余连接II
  • PHP一键约课高效健身智能健身管理系统小程序源码
  • 线性规划及其MATLAB实现
  • Java发邮件:如何配置SMTP服务器实现发信?
  • 【comparator, comparable】小总结
  • 【附node操作实例】redis简明入门系列—字符串类型
  • Angular 响应式表单之下拉框
  • HTTP传输编码增加了传输量,只为解决这一个问题 | 实用 HTTP
  • Javascript 原型链
  • Python语法速览与机器学习开发环境搭建
  • sessionStorage和localStorage
  • vue:响应原理
  • vue从创建到完整的饿了么(11)组件的使用(svg图标及watch的简单使用)
  • Vue小说阅读器(仿追书神器)
  • 记录一下第一次使用npm
  • 开源SQL-on-Hadoop系统一览
  • 排序算法之--选择排序
  • 前端性能优化--懒加载和预加载
  • 试着探索高并发下的系统架构面貌
  • Hibernate主键生成策略及选择
  • NLPIR智能语义技术让大数据挖掘更简单
  • UI设计初学者应该如何入门?
  • 昨天1024程序员节,我故意写了个死循环~
  • ​浅谈 Linux 中的 core dump 分析方法
  • #FPGA(基础知识)
  • #LLM入门|Prompt#1.7_文本拓展_Expanding
  • $.ajax()
  • (3)选择元素——(17)练习(Exercises)
  • (C++二叉树05) 合并二叉树 二叉搜索树中的搜索 验证二叉搜索树
  • (delphi11最新学习资料) Object Pascal 学习笔记---第14章泛型第2节(泛型类的类构造函数)
  • (ISPRS,2023)深度语义-视觉对齐用于zero-shot遥感图像场景分类
  • (附源码)python房屋租赁管理系统 毕业设计 745613
  • (附源码)ssm智慧社区管理系统 毕业设计 101635
  • (企业 / 公司项目)前端使用pingyin-pro将汉字转成拼音
  • (转)Android中使用ormlite实现持久化(一)--HelloOrmLite
  • (转)微软牛津计划介绍——屌爆了的自然数据处理解决方案(人脸/语音识别,计算机视觉与语言理解)...
  • ..thread“main“ com.fasterxml.jackson.databind.JsonMappingException: Jackson version is too old 2.3.1
  • .gitattributes 文件
  • .md即markdown文件的基本常用编写语法
  • .NET Core Web APi类库如何内嵌运行?
  • .NET Core中如何集成RabbitMQ
  • .NET DevOps 接入指南 | 1. GitLab 安装
  • .net 后台导出excel ,word
  • .NET 使用配置文件