【Linux】信号|Core|Term|raise|abort|硬件异常|软件异常|signal信号

news2024/9/19 2:33:34

目录

​编辑

前言

一,信号的概念

1.1信号的处理

1.2信号列表

1.3信号的存储

 1.4查看信号

二,Core和Term

2.1生成Core文件

三,初识捕捉信号 

3.1signal函数

四,产生信号的方式

4.1通过终端按键产生信号

4.2调用系统函数向进程发送信号

4.2.1使用 kill函数模拟 kill命令(mykill)

mykill.cpp

循环测试test.cpp 

4.3raise函数 

 4.4abort函数

 4.5硬件异常产生信号

4.5.1除0操作产生的异常

​编辑 

 对信号捕捉

4.5.2空指针异常 

4.6软件条件产生信号 

4.6.1alarm函数 


前言

信号是一种进程间的通信机制,用于通知接收进程某个事件的发生。信号可以由硬件异常、软件异常或其他进程触发,并且可以被用来中断进程的正常执行流程,从而允许进程响应特定事件。

如同我们按下ctrl+c就能终止一个进程,实际上就是bash进程向子进程发送了一个终止信号。

一,信号的概念

Linux操作系统中,信号是一种软件中断机制,用于通知进程某些事件的发生。信号通常用于处理异步事件。信号可以由操作系统、其它进程、或者同一进程内的其它线程发送。 

1.1信号的处理

  • 识别信号:你认识这个信号,信号到来你会产生相应的行为;
  • 信号到来(即将处理信号):不一定立即处理信号,也可以在某个合适时间进行处理信号,但是必须要把这个信号记住;
  • 拿到信号(处理信号):拿到信号后分三种行为 (1、默认动作:拿到信号后使用信号  2、忽略动作:拿到信号后忽略信号,什么事也不干  3、自定义动作:拿到信号去干别的事)

1.2信号列表

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

kill -l

在使用这些信号时,可以用信号名,也可以用信号编号,它是一样的,都是宏定义后的结果。

1.3信号的存储

信号是给进程的,必然存储在进程当中,而进程是通过内核数据结构来管理的,所以我们可以推断出,信号放在进程的task_struct结构体中

信号在PCB中且数量为31,task_struct中必然不会设置31个变量来存放信号;信号之分有和没有,所以31个信号放在一个32位的整形变量中(位图),每个比特位代表着一个信号;

struct task_struct
{
	//进程属性
	unsigned int signal;
	//.......
}

而 task_struct 是内核维护的一种数据结构对象,所以 task_struct 的管理者是OS,只有OS才有权利修改 task_struct 里面的内容,所以以此推导:本质都是通过OS向目标进程发送信号(谁都没有权利修改OS内的数据结构,只有OS自己可以);

我们要操作信号,OS必须提供发送信号、处理信号的相关系统调用;

 1.4查看信号

除了kill-l外,还可以使用man 7signal;

每个信号都有一个编号和一个宏定义名称

二,Core和Term

通过上图可以发现大多数信号的默认响应行为都是Core或者Term;

这两种信号都表示终止进程

区别:

  • Term就是普通的终止进程,之后没有其他动作。
  • Core不仅会终止进程,还会生成一个核心转储文件

核心转储(Core Dump):

        文件包含了进程终止时的内存映像,和关于进程状态的详细信息,也就意味着,通过这个核心转储文件我们就能知道这个进程在终止之前发生了什么。这对于开发者来说是非常宝贵的调试信息(可以借助gdb调试器加载其中的信息并调试)。 

2.1生成Core文件

云服务器默认进程终止时是不生产Core文件的,因为该文件中可能包含用户密码等隐私信息;

但是可以使用ulimit指令改变这个限制,允许生产Core文件;

生成的文件大小取决于进程Resource Limit(该信息保存在PCB中,默认是0);

如果子进程终止之后生成了core文件,那么子进程的退出码中的core_dump标记位就会置为1。 

使用ulimit -a查看当前资源限制的设定 ;

其中,第一行显示core文件的大小为0,即表示核心转储是被关闭的

 通过ulimit -c size 命令来设置Core文件的大小(同时也是打开了核心转储

 写入一个死循环代码测试

#include <iostream>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
int main()
{
    while (true)
    {
        cout << "pid: " << getpid() << endl;
        sleep(1);
    }
    return 0;
}

运行之后在终端按下Ctrl+\(SIGQUIT信号)

三,初识捕捉信号 

3.1signal函数

signal
 
头文件:
 #include <signal.h>
 
函数原型:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
 
参数:
    第一个参数signum:需要捕捉的信号编号
    第二个参数handler:对信号自定义的行为,对捕捉信号的处理方法(函数),handler是一个回调函数,该处理方法的参数是 int,返回值是void
    
    sighandler_t是一个函数指针

返回值是一个指向之前处理该信号的函数的指针,或者在错误情况下返回 SIG_ERR。
signal函数并不是系统调用,但是其内部封装了系统调用sigaction。一旦我们使用signal函数捕捉了某个信号,该进程响应该信号的方式就可以自己决定了。 

该函数允许程序自定义特定信号的响应行为

四,产生信号的方式

4.1通过终端按键产生信号

void handler(int sig)
{
    std::cout<<"进程:"<<getpid()<<"接收到了sig:"<<sig<<"信号"<<std::endl;
}

int main()
{
    //捕捉信号
    signal(2,handler);

    //循环打印
    while(true)
    {
        std::cout<<"hello signal"<<std::endl;
        sleep(1);
    }
    return 0;
}

按下ctrl+c之后发现并没有终止进程,而是执行了自定义的handler函数。 

4.2调用系统函数向进程发送信号

其实就是我们常用的kill指令发送信号给指定进程。

kill指令本质上是调用了系统调用kill函数。kill系统调用可以发信号到某一个进程,也可以发送到某一组进程。

 

函数:kill
 
头文件
#include <sys/types.h>
#include <signal.h>
 
函数原型:
int kill(pid_t pid, int sig);
 
参数
    第一个参数pid就是进程的pid
    第二个参数sig是信号的编号
 
返回值
发送成功,返回0,否则返回-1
 

可以用 kill -信号编号 进程pid 的形式进行发送信号 

4.2.1使用 kill函数模拟 kill命令(mykill)

先制作Makefile,一次编辑多个目标文件;

mykill.cpp
//不满足 打印
static void Usage(const string& proc)
{
    cout << "\nUsage: " << proc << " pid signo\n" << endl;
}
 
int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    
    //把字符串转整型
    pid_t id = atoi(argv[1]);
    int signo = atoi(argv[2]);
 
    int n = kill(id, signo);
    if(n != 0)
    {
        perror("kill");
    }
 
    return 0;
}
 
循环测试test.cpp 
int main()
{
    //循环打印
    while(true)
    {
        std::cout<<"hello signal"<<"进程:"<<getpid()<<std::endl;
        sleep(1);
    }
    return 0;
}

 ​​​

4.3raise函数 

 raise函数的作用是:给当前进程发信号(自己给自己发信号);

头文件
#include <signal.h>
 
函数原型
int raise(int sig);
 
参数:sig是要发送的信号编号
 
返回值
发送成功,则返回0,否则返回一个非零值

演示 

int main()
{
    //捕捉信号
    //signal(2,handler);

    //循环打印
    int count = 0;
    while(true)
    {
        std::cout<<"hello signal"<<"进程:"<<getpid()<<"count:"<<count<<std::endl;
        count++;
        //条件满足raise
        if(count == 10)
        {
            std::cout<<"raise!给当前进程发送信号"<<std::endl;
            raise(2);
        }
        sleep(1);
    }
    return 0;
}

 

 4.4abort函数

这个函数没有参数,并且它也没有返回值。调用 abort 函数后,程序会立即异常终止。本质上这个函数调用之后,操作系统就会先该进程发送一个SIGABRT信号,这个信号会终止当前进程,且通常生成core文件。(其实就是调用raise发送一个SIGABRT信号

 4.5硬件异常产生信号

  • 信号并不总是由用户明确发送的;有些信号是由操作系统(OS)在检测到特定事件时自动产生的,特别是硬件异常。当硬件检测到这类异常时,它会通知内核,后者则将异常转换为信号并将其发送给当前进程。
  • 硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。具体这个异常是怎么被检测出来的我们无需关心,我们只要知道发生硬件异常之后操作系统会向该进程发送一个信号来终止进程就行了。

4.5.1除0操作产生的异常

进行进程除0操作

int main()
{
    while(true)
    {
        std::cout<<"进程运行中..."<<std::endl;
        sleep(1);
        int a = 10;
        a/=0;

    }
    return 0;
}

 

 为什么除0会终止进程??

因为进程收到了来自OS的信号,该信号是SIGFPE,8号信号;

该信号的默认动作也是 Core,也是终止进程

 Floating point exception:浮点异常
 对信号捕捉
//自定义信号处理
void handler(int signo)
{
    cout << "捕捉到一个信号,该信号编号是:" << signo << endl;
    sleep(1);   //方便观察设置的
}
 
int main()
{
    signal(8, handler);
    while(true)
    {
        cout << "进程运行中..." << endl;
        sleep(1);
 
        //除0操作
        int a = 10;
        a /= 0;
    }
 
    return 0;
}

可以看到一直在对该信号的捕获,为什么❓

在CPU中,有许多寄存器用于存储和处理数据,例如通用寄存器如eax,ebx和指令指针寄存器eip当执行算术运算时,CPU会将操作数加载到寄存器中进行计算,并将结果存回到寄存器中。

例如,在进行除法运算时,如果尝试用0作为除数,这实际上是一个非法操作,因为数学上除以零没有定义。在这种情况下,由于结果是无穷大,而寄存器无法存储这样的值,状态寄存器中的溢出标志会被设置为1。此时,CPU会触发一个异常信号,操作系统会识别这个异常,并将其解释为一个硬件异常信号(通常是信号8,即 SIGFPE),然后操作系统会将这个信号发送给相应的进程。

简而言之,当执行除以零的操作时,状态寄存器的溢出标志会被置位,CPU会发出一个异常信号,操作系统检测到这个异常后,会向进程发送一个信号( SIGFPE),表明发生了非法算术运算。

为什么会一直死循环捕获到8号信号❓

 代码在CPU中执行的时候,此时CPU内的寄存器的内容属于该进程的上下文数据,因为寄存器只有一份,代码没有运行完,当该进程的时间片到了,就会从CPU上切下去,该进程的上下文数据也被会保存,包括溢出标志位

进程被来回切换,就有无数次寄存器的内容被保存会恢复的过程,所以每次恢复的时候,OS都会识别到CPU内部的状态寄存器溢出标志位为1,OS就会给该进程发送8号信号由于我们把该信号捕获了,执行自定义行为,该信号的默认行为就不会执行,每次恢复的时候,进程还没有被终止,OS依旧会识别到CPU内部的状态寄存器溢出标志位为1,就又发8号信号给该进程,以此往复,就陷入死循环,所以我们会看到8号信号一直被捕获

4.5.2空指针异常 

 空指针(野指针)问题

int main()
{
    signal(8, handler);
    while(true)
    {
        cout << "进程运行中..." << endl;
        sleep(1);
 
        //除0操作
        //int a = 10;
        //a /= 0;

        //野指针 空指针问题
        int* p = nullptr;
        *p = 10;
    }
 
    return 0;
}

结果发现,空指针操作进程直接被终止了,为什么进程会被终止?

因为进程收到了来自OS的信号,该信号是11号信号 SIGSEGV

默认动作也是 Core 

Invalid memory reference:无效内存引用
Segmentation fault:段错误
void handler(int sig)
{
    printf("catch a sig : %d\n", sig);
    sleep(1);
}

int main()
{
    //对该信号进行捕获
    signal(SIGSEGV, handler);
    sleep(1);
    int *p = NULL;
    *p = 100;
    return 0;
}

 代码int *p = nullptr; *p = 10; 显然会引发一个 SIGSEGV 信号,因为它尝试向 NULL 指针所指向的内存地址写入一个值,这是非法的内存访问。之后CPU触发一个硬件异常信号,执行handler函数之前操作系统会保存触发信号的指令地址于上下文中,执行完handler之后,操作系统又会回到之前保存的地址中去,即又回到了信号发生时的状态,于是就又重新执行*p这个代码。于是就产生了死循环。

4.6软件条件产生信号 

 软件条件产生信号其实就是软中断的一种,就是因为某种软事件或程序内部逻辑触发的信号。比如管道读端关闭之后,写端就会收到一个SIGPIPE信号进而终止。 

4.6.1alarm函数 

alarm函数是一个定时器,可以设置一个时间,这个定时器会在未来的一个时刻发送一个SIGALRM信号给当前进程;该信号默认处理动作是终止进程

函数原型:
unsigned int alarm(unsigned int seconds);
 
参数:传入一个时间,单位秒
 
返回值:
若调用alarm函数前,进程已经设置了闹钟,则返回上一个闹钟时间的剩余时间,并且本次闹钟的设置会覆盖上一次闹钟的设置
如果调用alarm函数前,进程没有设置闹钟,则返回值为0
int main()
{
    //1秒后才发送信号
    alarm(1);
    int cnt = 0;
    while(true)
    {
         cout << "cnt: " << cnt << endl;
         cnt++;
    }
    return 0;
}

 

 

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

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

相关文章

金航标电子和萨科微半导体

金航标电子和萨科微&#xff08;www.slkormicro.com&#xff09;半导体打造有活力有创造力的团队&#xff0c;周末聚餐&#xff0c;唱歌、打球等团建活动已成为公司特色&#xff0c;一直为增强团队凝聚力和向“芯”力而贡献力量。为此&#xff0c;8月3日下午&#xff0c;kinghe…

ISO 26262中的失效率计算:IEC TR 62380-Section 15-Switches and keyboards

目录 概要 1 开关和键盘的分类 2 开关和键盘失效率的计算 2.1 Switches and keyboards 2.1.1 Base失效率 2.1.2 接触数量 2.1.3 温度循环De-rating系数 概要 IEC TR 62380《电子组件、PCBs和设备的可靠性预计通用模型》是涵盖电路、半导体分立器件、光电组件、电阻器、电…

python爬虫学习记录-请求模块urllib3

&#xff08;文章内容仅作学习交流使用&#xff09; urllib3是一个功能强大、条理清晰&#xff0c;用于HTTP客户端的第三方模块 urllib3-发送网络请求 使用urllib3发送网络请求时&#xff0c;需要先创建PoolManager对象&#xff0c;并使用该对象的request方法发送请求&#…

黑马Java零基础视频教程精华部分_17_冒泡排序、选择排序、插入排序、快速排序

系列文章目录 文章目录 系列文章目录一、冒泡排序核心思想&#xff1a;相邻的数据两两比较&#xff0c;小的放前面&#xff0c;大的放后面动图展示算法步骤代码 二、选择排序核心思想&#xff1a;不用相连的两个数据比较&#xff0c;小的放前面&#xff0c;大的放后面动图展示算…

爬虫兽问题解答1-抖音评论区爬虫采集拓客系统

总结了一些用户经常提出的问题这里记录一下方便使用 抖音视频评论拓客系统FAQ问题解答 软件服务端镇楼 问题一&#xff1a;搜索到得抖音视频评论能否永久保留随时查询 答&#xff1a;是可以得&#xff0c;通过软件中得历史记录即可查询。 问题二&#xff1a;搜索到得评论&…

Seatunnel Mysql数据同步到Mysql

环境 mysql-connector-java-8.0.28.jar、connector-cdc-mysql 配置 env {# You can set SeaTunnel environment configuration hereexecution.parallelism 2job.mode "STREAMING"# 10秒检查一次&#xff0c;可以适当加大这个值checkpoint.interval 10000#execu…

ShardingSphere自定义分布式主键生成策略、自定义分片规则

文章目录 主键生成策略源码KeyGenerateAlgorithm源码入口实现扩展 自定义分布式主键生成策略 分片算法ShardingAlgorithm实现扩展 自定义分片算法踩的坑 主键生成策略源码 开发者手册 KeyGenerateAlgorithm 全限定类名org.apache.shardingsphere.sharding.spi.KeyGenerateAl…

【HBZ分享】bean的生命周期 以及 各个阶段在spring的哪个类被调用

Ioc容器通过配置文件读取bean的定义信息&#xff0c;并保存在BeanDefinition中执行BeanFactoryPostProcessor的postProcessBeanFactory方法&#xff0c;对bean定义的信息进行处理&#xff0c;这里还是可以设置bean的基本信息&#xff0c;比如&#xff1a;单例多例&#xff0c;初…

如何将 GTA Online 的加载时间缩短 70%

注&#xff1a;机翻。未校。 How I cut GTA Online loading times by 70% t0st 2021-02-28 GTA Online. Infamous for its slow loading times. Having picked up the game again to finish some of the newer heists I was shocked (/s) to discover that it still loads j…

如何使用浏览器发post请求

如何使用浏览器发送post请求 第一种&#xff1a;无请求体第二种&#xff1a;要设置请求体的post请求 通过浏览器发送post请求有两种简单的方式&#xff0c;只需要根据实际情况在console执行以下代码即可。 第一种&#xff1a;无请求体 没有请求体&#xff0c;可以直接使用以下…

Vue3与Vue2的主要区别

本篇文章适用于熟练掌握Vue2的小伙伴们&#xff0c;不想重新学习Vue3&#xff0c;那看本篇文章就够啦&#xff01;希望大家收获多多&#xff01;&#xff01; Vue3是向下兼容的&#xff0c;可以运行Vue2代码 一、页面区别 Vue2定义属性方法 <template><div ><…

【网络爬虫篇】“逆向实战—某东:滑块验证码(逆向登录)”自动化实现滑块登录验证(2024.8.7)最新发布,包干货,包详细

【网络爬虫篇】更多优秀文章借鉴&#xff1a; 1. 使用Selenium实现黑马头条滑块自动登录 2. 使用多线程采集爬取豆瓣top250电影榜 3. 使用Scrapy爬取去哪儿网游记数据 4. 数据采集技术综合项目实战1&#xff1a;国家水稻网数据采集与分析 5. 数据采集技术综合项目实战2&#x…

【leetcode】根据二叉树创建字符串、二叉树的前中后遍历(非递归链表实现二叉树)

Hi~&#xff01;这里是奋斗的明志&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f331;&#x1f331;个人主页&#xff1a;奋斗的明志 &#x1f331;&#x1f331;所属专栏&#xff1a;数据结构、LeetCode专栏 &#x1f4da;本系…

扫地机/洗地机语音芯片ic,工业级声音播放芯片ic,NV170H

扫地机/洗地机作为智能家居清洁领域的创新驱动力&#xff0c;不仅赋予了清洁设备&#xff0c;还需要一些智能化的功能&#xff0c;比如语音提示&#xff0c;将用户体验提升至全新高度。NV170H语音芯片成为了首要选择。 NV170H语音芯片是一款OTP&#xff08;‌一次性可编程&…

html+css网页设计 酷狗首页1个页面 (无js)

htmlcss网页设计 酷狗首页1个页面无js功能 页面还原度80% 网页作品代码简单&#xff0c;可使用任意HTML编辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。 …

干货满满!Stable Diffusion 从入门到精通之提示词手册,免费分享,自学转行,零基础首选!

前言 Stable Diffusion 技术把 AI 图像生成提高到了一个全新高度&#xff0c;文生图 Text to image 生成质量很大程度上取决于你的提示词 Prompt 好不好。本文从“如何写好提示词”出发&#xff0c;从提示词构成、调整规则和 AIGC辅助工具等角度&#xff0c;对文生图的提示词输…

腾讯云AI代码助手评测:如何智能高效完成Go语言Web项目开发

腾讯云AI代码助手评测&#xff1a;如何智能高效完成Go语言Web项目开发 &#x1f680; 文章目录 腾讯云AI代码助手评测&#xff1a;如何智能高效完成Go语言Web项目开发 &#x1f680;背景引言开发环境介绍腾讯云AI代码助手使用实例1. 代码补全2. 技术对话3. 代码优化4. 规范代码…

LVS实验(实现服务器集群的负载均衡)

基本概念 LVS&#xff08;Linux Virtual Server&#xff09;是Linux虚拟服务器的简称。 LVS通过将一个真实服务器集群虚拟成一台服务器来对外提供服务&#xff0c;同时在集群内部实现负载均衡。这种技术能够显著提高服务的处理能力和可靠性&#xff0c;降低单台服务器的负载压…

C++——类和对象(part1)

前言 本篇博客来为大家介绍C中的一个重要内容——类与对象&#xff0c;这部分的内容较多&#xff0c;将会分三篇文章来介绍&#xff0c;本篇为第一篇&#xff0c;如果你学习C或对C感兴趣&#xff0c;那么请继续往下阅读&#xff0c;下面进入正文部分。 1. 类的定义 1.1 类定…

【Material-UI】Button Group:实验性 API 详解

文章目录 一、按钮组概述1. 组件介绍2. 基本用法 二、实验性 API 详解1. LoadingButton 组件1.1 基本用法1.2 位置属性 三、实验性 API 的应用场景1. 数据加载按钮2. 提交表单按钮3. 保存操作按钮 四、按钮组的样式定制1. 变体&#xff08;Variants&#xff09;2. 颜色&#xf…