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.mzph.cn/news/730580.shtml

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

相关文章

尚硅谷JavaScript高级学习笔记

01 准备 JavaScript中函数是对象。我们后续描述构造函数的内存模型时&#xff0c;会将构造函数称为构造函数对象。 02 数据类型 typeof 运算符来查看值的类型&#xff0c;它返回的是类型的字符串值 会做数据转换 03 相关问题 04数据_变量_内存 05相关问题1 06相关问题2 …

Typescript 哲学 morn on funtion

函数重载 overload 有一些编程语言&#xff08;eg&#xff1a;java&#xff09;允许不同的函数参数&#xff0c;对应不同的函数实现。但是&#xff0c;JavaScript 函数只能有一个实现&#xff0c;必须在这个实现当中&#xff0c;处理不同的参数。因此&#xff0c;函数体内部就…

2024蓝桥杯每日一题(前缀和)

一、第一题&#xff1a;壁画 解题思路&#xff1a;前缀和贪心枚举 仔细思考可以发现B值最大的情况是一段连续的长度为n/2上取整的序列的累加和 【Python程序代码】 import math T int(input()) for _ in range(1,1T):n int(input())s input()l math.ceil(len(s)/…

人工智能在日常生活中的应用

在我们的日常生活中&#xff0c;人工智能已经成为一种无处不在的力量&#xff0c;从智能家居到在线助手&#xff0c;再到高度个性化的服务和推荐&#xff0c;它无声地改变着我们的生活方式和习惯。随着技术的不断进步和普及&#xff0c;人工智能正以前所未有的速度和规模渗透到…

JVM-垃圾收集器G1

G1垃圾回收器 概述&#xff1a; 是一款面向服务器的垃圾收集器,主要针对配备多个处理器及大容量内存的机器. 以极高效率满足GC停顿时间要求的同时,还具备高吞吐量性能特征.G1保留了年轻代和老年代的概念&#xff0c;但不再是物理隔阂了&#xff0c;它们都是&#xff08;可以不连…

在别的地方下载的二次封装Windows镜像怎么安装?GHO镜像详细安装教程

前言 在系统之家或者其他地方下载的镜像文件怎么装到电脑上&#xff1f; 首先要知道系统之家下载的Windows镜像文件基本上都是.iso结尾的&#xff0c;要进入到对应镜像包才能看出系统镜像是什么格式。 如何分辨镜像的格式 选择对应的.iso镜像&#xff0c;点击【鼠标右键】-【装…

《UE5_C++多人TPS完整教程》学习笔记26 ——《P27 在线会话测试(Testing An Online Session)》

本文为B站系列教学视频 《UE5_C多人TPS完整教程》 —— 《P27 在线会话测试&#xff08;Testing An Online Session&#xff09;》 的学习笔记&#xff0c;该系列教学视频为 Udemy 课程 《Unreal Engine 5 C Multiplayer Shooter》 的中文字幕翻译版&#xff0c;UP主&#xff0…

【OJ比赛日历】快周末了,不来一场比赛吗? #03.09-03.15 #13场

CompHub[1] 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号会推送最新的比赛消息&#xff0c;欢迎关注&#xff01; 以下信息仅供参考&#xff0c;以比赛官网为准 目录 2024-03-09&#xff08;周六&#xff09; #6场比赛2024-03-10…

Python笔记|基础算数运算+数字类型(1)

重新整理记录一下python的基础知识 基础运算符 、-、*、/ &#xff1b;括号 ()用来分组。 >>>2 2 4 >>>50 - 5*6 20 >>>(50 - 5*6) / 4 5.0 >>>8 / 5 1.6向下取整除法&#xff1a;向下舍入到最接近的整数的数学除法。运算符是 //。比如1…

【趣味项目】2048 简单实现

【趣味项目】2048 简单实现 算法原理 假设用一个二维矩阵表示 2048 页面&#xff0c;操作是左滑 const matrix [[2, 2, 4, 0],[0, 2, 4, 0],[0, 2, 2, 0],[2, 4, 4, 8] ];将所有非空的数字向左移动 matrix [[2, 2, 4, 0],[2, 4, 0, 0],[2, 2, 0, 0],[2, 4, 4, 8] ]将相邻的…

自动化工程师涨薪难,原因出在这里

大家好&#xff0c;今天说说真实的工控行业&#xff0c;摒弃虚无的鸡汤&#xff0c;聊点实在的。 举个例子&#xff0c;某工做销售&#xff0c;卖电控器件&#xff0c;眼见PLC收入可观&#xff0c;开始感到压力。于是&#xff0c;他下定决心学PLC&#xff0c;报了培训班。毕业后…

手写简易操作系统(一)--环境配置

本专栏是我新开设的一个学术专栏&#xff0c;旨在全面介绍手写操作系统的相关内容。其中包括实模式向保护模式的过渡、锁机制、信号量操作、内存分配、硬盘驱动、文件系统、简单shell和管道等操作系统核心知识。该专栏旨在为有意开发自己操作系统的研究人员提供指导与帮助。作为…

昏暗场景增强-低照度增强-弱光增强(附代码)

引言 随着现代科技的发展&#xff0c;图像采集设备已经渗透到生活的方方面面&#xff0c;然而在昏暗场景、低照度或弱光条件下&#xff0c;图像的质量往往受到严重影响&#xff0c;表现为亮度不足、对比度低下、色彩失真以及细节丢失等问题。这类图像对于人眼识别和计算机视觉…

【NR技术】 3GPP支持无人机的关键技术以及场景

1 背景 人们对使用蜂窝连接来支持无人机系统(UAS)的兴趣浓厚&#xff0c;3GPP生态系统为UAS的运行提供了极好的好处。无处不在的覆盖范围、高可靠性和QoS、强大的安全性和无缝移动性是支持UAS指挥和控制功能的关键因素。与此同时&#xff0c;监管机构正在调查安全和性能标准以及…

C++进阶之路---继承(二)

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、继承与友元 友元关系不能继承&#xff0c;也就是说基类友元不能访问子类私有和保护成员。 class Student; class Per…

leetcode 热题 100_除自身以外数组的乘积

题解一&#xff1a; 前缀 / 后缀数组&#xff1a;某元素除自身以外的乘积&#xff0c;也就是其全部前缀元素乘积 * 全部后缀元素乘积&#xff0c;因此我们可以构造前缀数组和后缀数组&#xff0c;分别存储前i个元素的成绩和后i个元素的乘积&#xff0c;再将i-1前缀乘积 * i1后缀…

SpringBoot整合Redis实现分布式锁

SpringBoot整合Redis实现分布式锁 分布式系统为什么要使用分布式锁&#xff1f; 首先&#xff0c;分布式系统是由多个独立节点组成的&#xff0c;这些节点可能运行在不同的物理或虚拟机器上&#xff0c;它们通过网络进行通信和协作。在这样的环境中&#xff0c;多个节点可能同…

Java数组常用操作

创建数组 int[] a {1,2,3};int[] a new int[]{1,2,3};int[] a new int[3];ArrayList<Integer> arr new ArrayList<>(); 添加元素 arr.add(99); //将99加入到数组末尾arr.add(3,99); //将99加入到指定索引3处访问元素 int c1 c[1]; int arr1 arr.get(1); …

方阵的特征值与特征向量

目录 特征值 & 特征向量 相关性质 特征值 & 特征向量 相关性质

BlackHole

BlackHole 文章目录 BlackHole一、关于 BlackHole功能描述 二、安装、卸载安装方式一&#xff1a;下载安装器方式二&#xff1a;使用 Homebrew 安装 卸载方式一&#xff1a;使用卸载器方式二&#xff1a;手动卸载 三、用户使用指南1、Logic Pro X2、GarageBand3、Reaper4、录制…