Linux——进程信号(一)

目录

1、信号入门

1.1、技术应用角度的信号

1.2、注意

1.3、信号概念

1.4、用kill -l命令可以查看系统定义的信号列表

1.5、信号处理常见方式概览

2、产生信号

2.1通过终端按键产生信号

Core Dump

2.2、调用系统函数向进程发信号

2.3、由软条件产生信号

3、总结思考一下



1、信号入门

通过自然世界中人对信号的基本理解:

接下来是对进程的分析:

我们可以看到信号都是宏

那么还有一个问题:信号是如何发送的以及如何记录的?

首先回答信号是如何记录的:普通信号的编号是从【1,31】,所以信号应该用位图来保存信号数据,信号的记录是进程的task_struct(PCB)->结构体变量,本质更多的是为了记录信号是否产生。

如何发送:进程收到信号,本质是进程内信号位图被修改了,也只有OS才有资格修改进程内的数据,因为操作系统是进程的管理者,所以绝对有资格修改进程数据,本质就是OS直接去修改目标进程task_struct中信号位图。(信号发送只有OS有资格,但是信号发送的方式可以有多种)

1.1、技术应用角度的信号

1、用户输入命令,在shell下启动一个前台进程。

用户按下Ctrl-C,这个键盘输入产生一个硬件中断,被OS获取,解释成信号,发送给目标前台进程

前台进程因为收到信号,进而引起进程退出

#include <stdio.h>
#include <unistd.h>
int main()
{while(1){printf("hello world!\n");sleep(1);}return 0;
}

当我们用ctrl+c这个组合键结束这个进程的本质是,操作系统识别到ctrl+c这个组合键,操作系统将ctrl+c解释成了2号新号,也就是SIGINT。

这个就是处理信号三种方案中的默认动作,为了要能够让信号自定义,有下面这个接口:

#include <signal.h>
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

第一个参数是信号编号,也就是信号中的1-31。

第二个参数的类型是一个函数指针,且是一个回调函数,相当于我们可以通过signal,提前向进程注册一个对信号的处理方法。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>void sigcb(int signo)
{printf("get a sig: %d\n", signo);
}int main()
{signal(2,sigcb);while(1){printf("hello world!\n");sleep(1);}return 0;
}

可以看到这次ctrl+c的时候,操作系统没有终止进程,因为默认行为被我们改成了自定义行为。这里无论使用ctrl+c还是kill -2 id操作都是执行我们的自定义行为,程序不会被终止,如果要退出进程,只能发送其他的退出信号

1.2、注意

1、Ctrl+C产生的信号只能发给前台进程。一个命令后面加个&可以放到后台运行,这样Shell不必等待进场结束就可以接受新的命令,启动新的进程。

2、Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像Ctrl+C这种控制键产生的信号。

3、前台进程在运行过程中用户随时可能按下Ctrl+C而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到SIGINT信号而终止,所以信号相对于进程的控制流程来说是异步的。

1.3、信号概念

信号是进程之间事件异步通知的一种方式,属于软中断。

1.4、用kill -l命令可以查看系统定义的信号列表

每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到

编号34以上的是实时信号,暂不做讨论。其他信号各自在什么条件下产生,默认的处理动作是什么,在signal(7)中都有详细说明:man 7 signal

1.5、信号处理常见方式概览

可选的处理动作有以下三种:

1、忽略此信号

2、执行该信号的默认处理动作

3、提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉一个信号


2、产生信号

2.1通过终端按键产生信号

SIGINT的默认动作是终止进程,SIGQUIT的默认动作是终止进程并且Core Dump,现在我们来验证一下:

可以看到,CTRL+\对应的是三号信号:SIGQUIT,3号信号默认的动作是Core,表示在结束的时候它有一个动作叫核心转储。

使用ulimit -a指令查看系统资源

看到core file size的大小为0,意味着它的核心转储是关闭的,那么我们为它设置一个值:

接着再运行一遍程序:

可以看到,进程也退出了,而且后面还多了一个(core dumped),用ls查看的时候也多了一个core.1751这个临时文件,这个1751数字叫做发生这次核心转储进程的id。

一个进程在终止的时候有很多终止方式,其中Terminal一般是直接退出,也可以理解成是我们手动的让它退出了,但不做任何转储文件的dump(转储),而我们如果自己打开了核心转储,并且我们收到了信号(不同的信号又不同的作用,不同的信号是一种不同的错误类别),而有些信号是需要进行和核心转储的。

比方说,代码运行的时候出错了,我们关心的是代码为什么出错了,我们之前讲的代码的三种退出方式:1、代码跑完结果对,2、代码跑完结果不对,3、代码运行中的时候出错。前两个最起码跑完了,最后根据退出码就能判断哪里有问题,那么第三种:代码运行中的时候出错了,我么也要有办法判定是什么原因出错了。

我们在平时出现第三种情况的时候,我们一般式通过调试来判断哪里出了问题,但其实还有Linux中的一种方法就是通过核心转储功能:把进程在内存中的核心数据转储到磁盘上,core.pid->核心转储文件。目的是为了调试、定位问题。一般云服务器是属于线上生产环境,默认是关闭的。

打开的状况我们上面那也进行了演示。那么还有一个问题:

为什么在云服务器上核心转储功能默认是关闭的呢?

比方说我们在服务器上写一个网络服务或者定期执行的一个任务,这个服务可能因为某种异常而挂掉,如果你打开了核心转储,那么挂掉之后会在本地的磁盘文件中生成corn文件,这个无可厚非,但是一般大的互联网公司在服务挂掉的时候,最重要的事情不是在乎是因为什么原因挂掉的,重要是的想尽快的让它恢复正常。因为BUG不是经常时间,而是偶尔的事情。所以重要的是先让服务跑起来,不要让公司收到太大的影响。当服务回复之后再对故障进行排除工作。

如果是小问题的话那么就先让服务恢复出来,然后再进行检查,但是如果出了大问题,而且有一个一崩就重启的功能,那么已重启就崩,崩了就重启,如此往复。就会出现大量的core file文件:

而且我们可以看到,这种文件一个都要1MB多,每个都不小,要说重启很长时间,那么我们去排查的时候会发现core文件将某个分区或者磁盘文件都占满了,最终导致服务想重启都没法重启,甚至操作系统都挂了,所以默认是关闭的。

Core Dump

首先解释什么是Core Dump。当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump。进程异常终止通常是因为有Bug,比如非法内存访问导致段错误,事后可以用调试器检查core文件以查清错误原因,这叫做Post-mortem Debug(事后调试)。一个进程允许产生多大的core文件取决于进程的Resource Limit(这个信息保存在PCB中)。默认是不允许产生core文件的,因为core文件中可能包含用户密码等敏感信息,不安全。在开发调试阶段可以用ulimit命令改变这个限制,允许产生core文件。首先ulimit命令改变Shell进程的Resource Limit,允许core文件最大为1024K:$ulimit-c 1024。

下面通过一个实例来观察一下:

可以发现出现了一个浮点型异常,而且多出了一个core.27575这个core dumped文件。

通过gdb和core.27575文件找到了问题在20行,这样我们就快速定位到了刚刚的代码是因为什么原因出错的,这个调试叫做事后调试,也就是程序崩溃了再进行调试。

为什么C/C++进程会崩溃?

本质就是因为收到了信号。

那为什么会受到信号?

首先我们要知道,信号都是又OS发送的,那么OS又怎么识别到有进程触发了问题呢?

OS在进行正常运行的时候发现CPU内有一个计算机状态标志位发生了除0错误,然后操作系统就立马定位当前运行的那个进程,所以就来进行终止。

所以操作系统识别到了硬件错误,然后将这个硬件错误解释(包装)成信号发送给目标进程。

其实本质就是找到这个进程的PCB,向目标的位图比特位由0置1,然后这个进程在合适的时候处理8号信号时默认就给“自己终止了”。

所以错误最终一定会在硬件层面上有所表现,进而被OS识别到,所以进场最后才会崩溃。

2.2、调用系统函数向进程发信号

这里我们要用到的接口是kill

#include <sys/types.h>

#include <signal.h>

int kill(pid_t pid, int sig);

我们写了一个重复打印的mytest,然后通过系统调用kill掉了mytest进程,可以看到已经成功的使mytest退出。

还有两个给自己发送信号的接口

#include <signal.h>

int raise(int sig);

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>void handler(int signo)
{printf("get a sig: %d\n", signo);
}int main()
{signal(2, handler);while(1){printf("I am a process, pid: %d\n", getpid());sleep(1);raise(2);}return 0;
}

#include <stdlib.h>

void abort(void);

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>void handler(int signo)
{printf("get a sig: %d\n", signo);
}int main()
{signal(6, handler);while(1){printf("I am a process, pid: %d\n", getpid());sleep(1);abort();}return 0;
}

可以看到,运行起来后,接收到的是6号信号,而且接收到一次之后就退出了,可是上面明明对6号信号进行了捕捉。

这是因为有些信号是可以被捕捉,有些信号不可以被捕捉,6号信号既被捕捉了,也被终止了,这就是6号信号,abort的作用很像我们一直用的exit(),但是exit()是正常终止,而abort()的本质是通过信号来终止,是自己终止自己,但是要说明的是,exit()本质上是函数,只要是函数就说明它可能会失败,而abort函数总是会成功(函数无返回值)。

2.3、由软条件产生信号

我们之前的异常本质上是由软件引起的,但最终引起的问题是在硬件上,也就是CPU的状态寄存器出了问题,MMU转化出了问题,所以最后我们就看到操作系统识别硬件出了错误,然后转化成信号发送给进程。

软件条件产生信号:在我们写管道那里的时候说,有一端是读端,有一端是写端,如果将读端关闭,写端一只写,那么写端就会被立刻终止。这样的原因就是写入的软件条件不满足,也就是当前管道式不允许你写入的,所以我们当时就收到了一个SIGPIPE这个信号,这个信号就是由于软件条件产生的信号,所以就是我们写入的条件不成熟,这就是软件条件。

当然还有其他的软件条件,就是alarm(闹钟)函数。

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

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

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

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>void handler(int signo)
{printf("get a sig: %d\n", signo);
}int main()
{alarm(1);int count = 0;while(1){printf("count is: %d\n", count++);}return 0;
}

这个代码的意思是,1秒后发送14号信号SIGALRM,然后在这1秒内看能进行多少次count++并打印出来,我们可以看到,在五万次左右,但是这其实不代表真实的速度,因为我们这里是在外设打印了就会慢很多。而且也会有网络的原因,我们在网络上计算,然后再发送过来,就会慢。

这里我们改一改:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>int count = 0;void handler(int signo)
{printf("count is: %d\n", count);exit(1);
}int main()
{signal(14, handler);alarm(1);while(1){count++;}return 0;
}

可以看到,我们直接让它累加,最后再打印,就可以看到会加到很大,这就是因为在累加的时候没有进行IO,所以我们得知,如果计算机在进行IO的时候,效率非常低。

2.4、硬件异常产生信号

硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。


3、总结思考一下

1、上面所说的所有信号产生,最终都要由OS来进行执行,为什么?

答:OS是进程的管理者

2、信号的处理是否是立即处理的?

答:在合适的时候

3、信号如果不是被立即处理,那么信号是否需要暂时被进程记录下来?记录在哪里最合适呢?

答:需要。记录在进程的PCB中,有对应的PCB位图

4、一个进程在没有收到信号的时候,能否能知道,自己应该对合法信号作何处理呢?

答:知道:默认、自定义、捕捉

5、如何理解OS向进程发送信号?能否描述一下完整的发送处理过程?

答:本质就是OS根据某种信号类别,直接去修改PCB位图中的0 1序列,进而达到发送信号的目的

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

遥遥领先!基于transformer变体的时间序列预测新SOTA!

目前&#xff0c;以CNN、RNN和 Transformer 模型为代表的深度学习算法已经超越了传统机器学习算法&#xff0c;成为了时间序列预测领域一个新的研究趋向。这其中&#xff0c;基于Transformer架构的模型在时间序列预测中取得了丰硕的成果。 Transformer模型因其强大的序列建模能…

类和对象(1)(至尊详解版)

相信对于大家而言&#xff0c;对于类和对象都会是一头雾水吧&#xff01;什么是类&#xff1f;或者你有对象吗&#xff1f;那么本期的内容呢&#xff1f;就由我来为大家再次增加对于它们的理解&#xff0c;由于水平上的原因&#xff0c;可能会存在不当之处&#xff0c;敬请读者…

测试环境搭建整套大数据系统(九:docker学习)

一&#xff1a;为什么学习dockder&#xff1f; 对于组件的搭建和部署&#xff0c;可以简化。 二&#xff1a;什么是docker&#xff1f; docker是一个平台。 三&#xff1a;怎么使用docker&#xff1f; 1. 安装&#xff0c;切换仓库。 安装 curl -fsSL https://test.docke…

MATLAB2020a安装编译器mingw-64(6.3.0)

MATLAB2020a指定安装mingw-64&#xff08;6.3.0&#xff09;版本编译器 记录一下几个要点 mingw-64&#xff08;6.3.0&#xff09; 找到对应的mingw-64安装包 设置mingw的bin文件路径到环境变量 变量名&#xff1a;MW_MINGW64_LOC MATLAB设置路径

c# combox 行间距调整

初始化combox comboBox1.DropDownStyle ComboBoxStyle.DropDownList;comboBox1.ItemHeight 25; // 设置 combox 的行高comboBox1.DrawMode DrawMode.OwnerDrawVariable; 添加 DrawItem 事件 private void comboBox1_DrawItem(object sender, DrawItemEventArgs e){if (…

接口自动化测试用例的编写方法

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号【互联网杂货铺】&#xff0c;回复 1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 phpunit 接口自动化测试系列 Post接口自动化测试用例 Post方式…

洛谷 P8816 [CSP-J 2022] 上升点列(T4)

目录 题目传送门 算法解析 最终代码 提交结果 尾声 题目传送门 [CSP-J 2022] 上升点列 - 洛谷https://www.luogu.com.cn/problem/P8816 算法解析 k 0 且 xi, yi 值域不大时&#xff0c;这题是非常简单的 DP&#xff0c;类似「数字三角形」。 记 dp(x,y) 为「以 (x,y) …

【生态适配】亚信安慧AntDB数据库与OpenCloudOS、TencentOS Server五款产品完成兼容互认

日前&#xff0c;亚信安慧AntDB数据库与OpenCloudOS8、OpenCloudOS9、TencentOS Server 2、TencentOS Server 3、TencentOS Server 4五款操作系统完成兼容互认。经过严格测试&#xff0c;亚信安慧AntDB数据库与这五款操作系统兼容良好&#xff0c;整体运行稳定。 图1&#xff1…

代理IP以及动态拨号VPS的关系是什么?

在数字时代&#xff0c;网络安全和隐私保护已成为全球关注的热点话题。代理IP和动态拨号VPS作为提升网络匿名性和安全的重要技术&#xff0c;它们在维护网络隐私中扮演着至关重要的角色。虽然这两种技术在表面上看似相似&#xff0c;实际上它们在功能、应用场景以及用户需求满足…

QT:用opencv的KNN识别图片中的LED数字(一)

前言 一款功能测试的软件demo,使用了QT作为界面,主要使用了opencv的KNN识别,使用gstreamer作为管道,用来打开图片。后期会写一篇打开摄像头实时识别的文章。 (正在写,未完成,稍候) 效果一预览: 效果二预览: 效果三预览: 正在写。。。 设计思路 1. 软件UI设…

《汇编语言》第3版 (王爽)检测点11.1解析

第11章 检测点11.1解析 在Debug中各标志位定义如下: 写出下面每条指令执行后&#xff0c;ZF、PF、SF等标志位的值。 sub al,al ZF1 PF1 SF0 ;执行完此指令后&#xff0c;结果为0&#xff0c;所以ZF1;1的个数为0是偶数个&#xff0c;所以PF1;0非负&#xff0c;所以SF0。各标…

【SpringBoot】多环境切换的灵活配置

文章目录 profile 的使用激活 profile 的方式命令行启动idea 中配置配置文件中激活 开发中最灵活的多环境配置创建四个配置主配置文件其他几个环境配置使用方式 配置文件拆分总结 在日常的开发中&#xff0c;一般都会分好几种环境&#xff0c;比如通常的 开发环境&#xff1a;一…