Linux——进程信号(一)

news2024/11/20 4:51:05

目录

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.coloradmin.cn/o/1500555.html

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

相关文章

华为北向网管NCE开发教程(2)REST接口开发

华为北向网管NCE开发教程&#xff08;1&#xff09;闭坑选接口协议 华为北向网管NCE开发教程&#xff08;2&#xff09;REST接口开发 华为北向网管NCE开发教程&#xff08;3&#xff09;CORBA协议开发 假设你现在要开始华为北向接口REST协议之前&#xff0c;需要准备如环境 1准…

学习与学习理论 - 2024教招 - test

一 方向 方向性很重要&#xff0c;像学投篮一样关注发力顺序才是关键出发点&#xff0c;如果这个出发点没确定下来&#xff0c;会走许多弯路。所有学习理论大的观点&#xff0c;到某个人物个人的观点。被干掉之前&#xff08;不能被干掉&#xff09;&#xff0c;掌握所需的知识…

C++:Stack和Queue的模拟实现

创作不易&#xff0c;感谢三连&#xff01; 一、容器适配器 适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结)&#xff0c;该种模式是将一个类的接口转换成客户希望的另外一个接口。 就如同是电源适配器将不适用的交流电…

STM32CubeMX学习笔记13 ---IIC总线

1、IIC 简介 IIC(Inter&#xff0d;Integrated Circuit)总线是一种由NXP&#xff08;原PHILIPS&#xff09;公司开发的两线式串行总线&#xff0c;用于连接微控制器及其外围设备。多用于主控制器和从器件间的主从通信&#xff0c;在小数据量场合使用&#xff0c;传输距离短&…

《汇编语言》- 读书笔记 - 第17章-实验17 编写包含多个功能子程序的中断例程

《汇编语言》- 读书笔记 - 第17章-实验17 编写包含多个功能子程序的中断例程 逻辑扇区根据逻辑扇区号算出物理编号中断例程&#xff1a;通过逻辑扇区号对软盘进行读写 代码安装 int 7ch 测试程序效果 实现通过逻辑扇区号对软盘进行读写 逻辑扇区 计算公式: 逻辑扇区号 (面号*8…

【PCIe 链路训练】之均衡(equalization)

1、概述 这篇文章简单介绍一下PCIE phy的均衡原理和过程,USB phy,ethernet phy这些高速的串行serdes也有相同或者相似的结构。可以不用太关注其中的细节,等到debug的时候可以查询协议,但是需要了解这个故事讲的大概内容。整个equalization过程是controller和phy一起配合完成…

喜报|炼石免改造数据安全入选上海网安产业创新大会优秀案例

近日&#xff0c;上海网络安全产业创新大会隆重召开&#xff0c;上海普陀区委副书记、区长肖文高&#xff0c;上海市经济和信息化委员会总工程师葛东波出席并致辞&#xff0c;普陀区副区长肖立出席。大会以“产业赋能、生态打造”为主题&#xff0c;为发掘数据安全领域的优秀产…

【代码随想录算法训练营Day29】 491.递增子序列;46.全排列;47.全排列 II

文章目录 ❇️Day 29 第七章 回溯算法 part05✴️今日内容❇️491.递增子序列自己的思路随想录思路自己的代码 ❇️46.全排列思路代码流程 ❇️47.全排列 II思路代码 ❇️Day 29 第七章 回溯算法 part05 ✴️今日内容 491.递增子序列46.全排列47.全排列 II ❇️491.递增子序…

低压MOS在步进电机驱动器上的应用-REASUNOS瑞森半导体

一、前言 步进电机驱动器是一种用于控制步进电机运动的装置&#xff0c;它是将控制信号转换成步进电机可以识别的控制电压或电流的电路。它在工业自动化领域有着广泛的应用&#xff0c;如机器人、印刷机、木工机床、喷绘机等。步进电机驱动器的组成结构主要由以下部分&#xf…

《C缺陷和陷阱》-笔记

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 文章目录 前言 一、理解函数声明 1.(*(void(*)( ))0)( ); 2.signal 函数接受两个参数&#xff1a; 3.使用typedef 简化函数声明&#xff1a; 二、运算符的优先级…

面试经典150题【61-70】

文章目录 面试经典150题【61-70】61.旋转链表86.分隔链表104. 二叉树的最大深度100.相同的树226.翻转二叉树101.对称二叉树105.从前序与中序遍历序列构造二叉树106.从后序和中序遍历序列构造二叉树117.填充每个节点的下一个右侧节点指针II114.二叉树展开为链表 面试经典150题【…

修复 因 fstab 中UUID 错误导致系统无法正常工作的问题

操作系统&#xff1a; PVE 8.0 /debian 12 &#xff08;bookworm&#xff09; 问题症状&#xff1a;可以正常启动进入系统&#xff0c;但是系统盘以只读方式挂载 问题原因&#xff1a;/etc/fstab 中引导区的UUID 被错误修改导致 解决方法&#xff1a; 重启系统&#xff0c;在…

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

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

吴恩达深度学习笔记:深度学习引言1.1-1.5

目录 第一门课&#xff1a;神经网络和深度学习 (Neural Networks and Deep Learning)第一周&#xff1a;深度学习引言(Introduction to Deep Learning)1.1 欢迎(Welcome)1.2 什么是神经网络&#xff1f;(What is a Neural Network)1.3 神经网络的监督学习(Supervised Learning …

【C++】C++模板基础知识篇

个人主页 &#xff1a; zxctscl 文章封面来自&#xff1a;艺术家–贤海林 如有转载请先通知 文章目录 1. 泛型编程2. 函数模板2.1 函数模板概念2.2 函数模板格式2.3 函数模板的原理2.4 函数模板的实例化2.5 模板参数的匹配原则 3. 类模板3.1 类模板的定义格式3.2 类模板的实例化…

Trans论文复现:考虑源荷不平衡性的微电网鲁棒定价方法程序代码!

适用平台&#xff1a;MatlabYalmipCplex/Gurobi 程序针对目前微电网中高比例新能源发电的波动性和间歇性&#xff0c;提出了考虑源荷不平衡特性的微电网鲁棒定价方法&#xff0c;综合考虑电力市场边际收益和边际成本&#xff0c;利用价格波动来平衡电源和负荷。程序算例丰富、注…

腾讯云轻量 2核2G4M新用户首购活动,99续费同价来了!!

阿里云199一年续费同价&#xff0c;腾讯云99一年续费同价&#xff0c;平台卷起来&#xff0c;对用户的角度来说&#xff0c;真的是香麻了~ 腾讯云新春采购节&#xff0c;2核2G4兆的基础配置&#xff0c;新官方直接放大招&#xff0c;99一年&#xff0c;活动期间内&#xff0c;…

EXSI create datastore

文章目录 1. 简介2. 清空磁盘3. 删除表4. 创建database 1. 简介 在 ESXi 环境中创建数据存储(Datastore)的步骤如下: 登录 vSphere Web Client 打开 Web 浏览器,输入 ESXi 主机或 vCenter Server 的 IP 地址,使用有权限的账户登录。 在 ESXi 环境中创建数据存储(Datastore)…

二、TensorFlow结构分析(4)

TF数据流图图与TensorBoard会话张量Tensor变量OP高级API 目录 1、变量 2、高级API 1、变量 2、高级API