【Linux】进程信号的产生与捕捉、核心转储

news2024/11/17 13:40:18

目录

一、信号的引入

二、信号捕捉

三、核心转储

四、系统调用发送信号

五、软件条件产生信号

六、硬件异常产生信号


一、信号的引入

Linux信号本质是一种通知机制,用户 or 操作系统通过发送一定的信号,通知进程,某些事件已经发生,可以在后续进行相应的处理。

例如,当用户输入命令,在Shell下启动一个前台进程后,我们便可以通过信号的方式终止进程。

接下来我们运行一下test.c程序,因为是一个死循环,然后我们使用Ctrl+C停止该进程。

当我们按下Ctrl+C时,实际上就是给进程发送了一个信号。

  • 当用户按下Ctrl+C时,这个键盘输入产生一个硬件中断,被OS获取,解释成信号,发送给目标前台进程,前台进程收到信号,进而引起进程退出。

那系统是如何将组合键编程信号的呢?

  • 实际上当用户按Ctrl+C时,这个键盘输入会产生一个中断,被操作系统获取并解释成信号(Ctrl+C被解释成2号信号),然后操作系统将2号信号写入对应的信号到进程PCB内部的位图结构中,然后进程检测到位图中数据的变化,做出相应的操作。
  • 信号发送的本质:OS 向目标进程写信号,OS直接修改PCB中的指定位图结构,就完成了”发送“信号的过程。

我们可以使用 kill -l 命令 查看 Linux 中的信号列表。

 一共有62个信号(没有32、33号信号),其中白色区域【1-31】的为普通信号。【35-64】为实时信号。


二、信号捕捉

信号的可选动作有以下三种:

  1. 忽略此信号。
  2. 执行该信号的默认处理动作。
  3. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式被称为捕捉(catch)一个信号。

即信号的处理方式有三种:1.默认处理,2.忽略,3.自定义捕捉

接下来我们先了解一下第三种方式——自定义捕捉,OS为我们提供了一个系统调用 signal

 第一个参数:要传入信号编号或宏(即62个信号中的其中一个),用于捕捉该信号, 部分如下:

第二个参数:要求传入一个函数指针,这个传入的函数是当信号产生时执行的自定义功能了。调用一个函数传入另一个函数的函数指针,这种操作在C语言中被称为回调函数

接下来写一段简单代码看一下:

当SIGINT(即Ctrl+C)信号产生时,不去执行该信号的默认操作,而是去执行我们的catchSig函数。

 然后我们来看看运行结果,并从键盘按下Ctrl+C.

 说明signal函数,修改了当前进程对特定信号的处理动作,转而执行我们指定的函数。


三、核心转储

我们可以输入man 7 signal 查看信号的默认处理行为。这里不同信号的Action不同,有Term、Core、Ign、Cont、Stop等状态行为。

接下来就是了解一下Core动作——核心转储。

关于进程等待中,status 中如果是正常终止就保存返回值、错误码。

如果被信号所杀,第7位上保存的这个就叫做core dump,如果是0表示没有发生核心转储,为1则是发生了核心转储。

我们可以打印code_dump位的信息 (左移7位然后与上1即可)。

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        sleep(1);
        int a = 100;
        a /= 0;
        exit(0);
    }
    int status = 0;
    waitpid(id, &status, 0);
    cout << "父进程:" << getpid() << "子进程:" << getppid() << endl;
    //退出信号:
    cout << "exit sig" << (status & 0x7f) << endl;
    // 打印core dump位
    cout << "core dump" << (status > 7 & 1) << endl;
}

如果使用的云服务器,其默认核心转储功能是关闭的

我们可以使用ulimit -a 进行查看

然后使用命令 ulimit -c 10240 就可以打开(这种打开方式仅针对当前会话)

 打开之后,运行mysingal程序,然后使用kill -8 信号终止该进程。

此时当前目录下就多了一个 core.4530 文件。

这个文件就叫做核心转储:

  • 当进程出现某种异常的时候,是否由OS将当前进程在内存中的相关核心数据,转存到磁盘中。大白话就是将内存中的重要数据保存起来,主要作用是用于调试。

所以在信号的默认处理行为中action我们就知道是什么意思了。(ign表示忽略,Cont表示继续)

那这个核心转储生成的文件有什么用呢?
其主要作用是用于调试,接下来我演示core文件进行调试。

接下来我编写一段代码,其中有句代码执行了整数除以0,会触发8号信号生成core文件。我们加上-g选项生成可执行文件。

#include <iostream>
#include <unistd.h>
using namespace std;
int main()
{
    cout<<"i am a process,pid:"<<getpid()<<endl;
    sleep(1);
    int a=100;
    a/=0;
    cout<<"----run ....."<<endl;
    return 0;
}

 然后使用gdb打开core文件。


四、系统调用发送信号

操作系统提供了许多系统调用接口让我们产生并发送信号:

  • kill 接口
  • raise 接口
  • abort 接口

接下来让我们先来认识 kill 接口

 第一个参数为指定的进程pid,第二个参数为对应的信号编码。

其实本质kill命令就是调用的系统调用kill,所以我们也可以实现一个kill命令:

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        exit(1);
    }
    int procid = atoi(argv[1]);
    int signumber = atoi(argv[2]);
    // 系统调用kill
    kill(signumber, procid);
    return 0;
}

检验步骤步骤如下:

1. 让当前会话的前台进程中直接sleep 10000秒,然后在会话2中查看该进程的pid

2.调用 mykill ,传入信号和sleep 10000的pid,观察结果。

结果:

raise命令:

kill 是给指定进程发送信号,而如果想让自己给自己发信号,可以使用 raise 命令

接下来我们写一段程序,让进程本身给自己发送8号信号。 

举例使用如下:

int main()
{
    cout << "pid: " << getpid() << "run......" << endl;
    raise(8);
    return 0;
}

abort接口:

给自己发送abort信号,也就是6号信号。相当于代码:raise(6) 或  kill(getpid(),6)

 举例代码如下:

int main()
{
    cout << "pid: " << getpid() << "  run......" << endl;
    abort();    //raise(6) 或 kill(getpid(),6)
    return 0;
}


五、软件条件产生信号

举一个例子:

当管道,读端不进行读取,还关闭了文件描述符,而写端一直写入,会发生什么问题?

操作系统会自动终止对应写端进程,通过发送信号的方式,发送SIGPIPE信号。

验证步骤:

1.创建匿名管道

2.让父进程进行读取,子进程进行写入

3.让父进程关闭读端 && waitpid(),子进程一直进行写入

4.子进程退出,父进程waitpid拿到子进程的退出status。

5.提取退出信号。

SIGPIPE便是一种软件条件产生的信号,除了管道中会发出SIGPIPE信号,接下来我们学习其它软件产生的信号—— alarm 函数与SIGALRM 信号。

系统调用中的 alarm 函数会产生 SIGALRM  信号。

接下来让我们了解一下 alarm 接口。

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

有了 alarm 这个计时接口,我们可以写一段代码来验证1秒内,服务器一共能进行多少次count++;

int main()
{
    alarm(1);
    int count=0;
    while(1)
    {
        cout<<"count:"<<count++<<endl;
    }
    return 0;
}

运行程序,看看结果是多少:

观察结果:1秒钟为什么count只能++到6万左右,为什么这么慢?

因为1.对屏幕进行IO浪费了太多时,

2. 云服务器是网络传输,消耗太多时间。

如果想单纯计算算力,我们对代码进行改写,让其在服务器上进行计算,然后将结果返回给我们。

int count = 0;

void catchSig(int signum)
{
    cout << "final count: " << count << endl;
}
int main()
{
    // 1秒后发送消息
    alarm(1);
    signal(SIGALRM, catchSig);
    while (1)
    {
        ++count;
    }
    return 0;
}

这时结果就是5亿多。

因为singal的原因,如果alarm时间到了,就会执行catchSig函数。所以我们可以设置一个周期程序,每一秒种打印计算机累计算了多少,改动如下:

 所以每隔一秒,就自动打印count的值,这样,我们就实现了一个定时器的功能。

有了这个定时器的功能,我们就可以实现定时打印信息。

每隔一秒钟就打印我们想要检测的内容


typedef function<void()> func;
vector<func> callbacks;
long long count = 0;
void showCount()
{
    cout << "final count: " << count << endl;
}

void showLog()
{
    cout << "打印日志……" << endl;
}
// 打印登录的用户
void logUser()
{
    if (fork() == 0) // 子进程进行程序替换执行who命令
    {
        execl("/usr/bin/who", "who", nullptr);
        exit(1);
    }
    wait(nullptr);
}

void catchSig(int signum)
{
    // 定时执行以下任务:
    for (auto &f : callbacks)
    {
        f();
    }
    alarm(1);
}
int main()
{
    // 1秒后发送消息
    alarm(1);
    signal(SIGALRM, catchSig);
    callbacks.push_back(showCount);
    callbacks.push_back(showLog);
    callbacks.push_back(logUser);
    while (1)
        ++count;
    return 0;
}

运行如下:

那如何理解软件条件给进程发送信号?

  • OS先识别到某种软件条件触发或不满足。
  • OS构建信号,发送给指定的进程。

六、硬件异常产生信号

硬件怎么产生信号呢?我们现在先写一段整数除以0的代码进行引入:

void handler(int signum)
{
    sleep(1);
    cout << "signal is : " << signum << endl;
}
int main()
{
    signal(SIGFPE, handler);
    int a=100;
    a/=0;
    while (1)
        sleep(1);
    return 0;
}


那如何理解整数除以0这个操作呢?

  1. 因为计算的是CPU,如果CPU计算出现错误,会将错误信息放入到状态寄存器中,状态寄存器中有对应的状态标记位(类比成 位图),其中会存在溢出标记位,OS会自动进行计算完毕之后的检查。
  2. 如果OS识别到有溢出问题,根据 current指针(指向当前正在运行的进程) 找到进程,然后提取出 PID,O S再进行信号发送到该进程,进程则会再合适的时候,进行信号的处理。
  3. 立即找到当前 task_struct中有一个current指针,当程序进行执行时,current内的内容也会被加载到CPU的寄存器中。
  4. 所以,整数除以零是一个硬件异常的问题

那一旦出现硬件异常,进程一定会退出吗?

  • 不一定,一般默认是退出,但是如果我们不进行退出,我们也不能进行任何操作,因为无权访问CPU中的寄存器数据。

为什么会发生死循环?

  • 因为寄存器中的异常一直没有被解决。
  • 所以一般我们出现除0等错误,一般就直接exit()退出了。

指针越界、野指针一般被称为段错误 (11号信号SIGSEGV)

那如何理解野指针或越界问题?

  1. 都必须通过地址,找到目标位置,
  2. 语言上的地址,全部都是虚拟地址
  3. 将虚拟地址转化为物理地址
  4. 页表+MMU(Memmory Manager Unit——硬件)
  5. 野指针,越界->非法地址->MMU转化的时候,一定会报错。因为MMU这个硬件其中也有寄存器,注意,外设也有寄存器的,不只是CPU有寄存器。

结论:

  • 所以说,硬件也能产生信号。所有的信号,都有其来源,但最终全部都是被OS被识别、解释、发送的。

最后就是几个信号的常见问题:

  • 为什么所有的信号产生,最终都要由OS来执行?

        因为OS是进程的管理者。

  • 信号的处理是否是立即处理的?

        由OS在合适的时机进行处理。

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

        需要被记录下来,记录在进程PCB中对应的信号记录位图。

  • 如何理解OS向进程发送信号?

        本质是OS直接修改PCB中的信号位图,根据信号编号修改特定的比特位(由0置1)。

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

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

相关文章

JavaWeb-MyBatis | Mapper代理开发及案例

本专栏主要是记录学习完JavaSE后学习JavaWeb部分的一些知识点总结以及遇到的一些问题等&#xff0c;如果刚开始学习Java的小伙伴可以点击下方连接查看专栏 本专栏地址&#xff1a;&#x1f525;JDBC Java入门篇&#xff1a; &#x1f525;Java基础学习篇 Java进阶学习篇&#x…

【5G NTN】5G NTN(非地面组网)介绍

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

DTD语法详细介绍

在编写XML文档时&#xff0c;需要掌握XML语法。同理&#xff0c;在编写DTD文档时&#xff0c;也需要遵循一定的语法。DTD的结构一般由元素类型定义、属性定义、实体定义、记号(notation)定义等构成&#xff0c;一个典型的文档类型定义会把将来要创建的XML文档的元素结构、属性类…

LeetCode Hot100 ---- 双指针算法专题

167, 88, 142, 76双指针双指针法&#xff0c;有时也叫快慢指针&#xff0c;在数组里是用两个整型值代表下标&#xff0c;在链表里是两个指针&#xff0c;一般能实现O(n)的时间解决问题&#xff0c;两个指针的位置一般在第一个元素和第二个元素或者第一个元素和最后一个元素&…

【Leetcode每日一题】844. 比较含退格的字符串|重构字符串/双指针

博主简介&#xff1a;努力学习的预备程序媛一枚~博主主页&#xff1a; 是瑶瑶子啦所属专栏: LeetCode每日一题–进击大厂 前言&#xff1a; 昨天的【Leetcode每日一题】27. 原地移除元素|神级理解双指针一文中&#xff0c;生动形象的为大家讲解如何理解双指针&#xff0c;受到…

《收获,不止Oracle》表的连接学以致用

6.2 三大类型 6.2.1 连接类型 嵌套循环 排序合并及散列连接 第一种方式就是数据库表连接中的嵌套循环连接&#xff08;Nested Loops Join&#xff09;&#xff0c;而第二种方式就是表连接中的排序合并连接&#xff08;Merge Sort Join&#xff09;或者散列连接&#xff08;…

【SpringCloud】Gateway服务网关的基本使用

一、初识Gateway服务网关为什么需要网关&#xff1f;在微服务中&#xff0c;各个模块之间的调用&#xff0c;也可以称其为远程调用&#xff01;但是&#xff0c;如果是外部&#xff08;用户&#xff09;对微服务进行访问时&#xff0c;发的请求能不加处理的直接访问微服务吗&am…

使用Client Java构建Exporter程序

一、client_java client_java是Prometheus针对JVM类开发语言的client library库&#xff0c;我们可以直接基于client_java用户可以快速实现独立运行的Exporter程序&#xff0c;也可以在我们的项目源码中集成client_java以支持Prometheus。注意&#xff1a;Prometheus 提供的cl…

WordPress默认数据库中的12个数据表

WordPress 安装的时候数据库会有 12 张默认的数据表&#xff0c;每张表的数据都包含了 WordPress 不同的功能。看看这些表的结构&#xff0c;你能很容易的了解网站不同的部分都是存在哪里的。目前&#xff0c;默认的 WordPress 安装会创建如下12个数据表。表名描述wp_users您的…

大数据在5G应用场景下有哪些示范项目?

近期&#xff0c;贵州省2022年5G应用场景示范观摩会在贵阳召开&#xff0c;此次观摩会由贵州省大数据发展管理局、贵州省通信管理局主办。数据宝“基于5G技术的交通国有大数据治理多元应用”获得贵州省5G应用场景示范项目&#xff08;五星&#xff09;称号。 据悉&#xff0c;…

二叉树(二)

前言本章我们继续了解二叉树。上文我们对树和二叉树有了简单的了解&#xff0c;详见二叉树&#xff08;一&#xff09;这里我要解释一下为什么我们不对树进行增删查改呢&#xff1f;答案是&#xff1a;没有意义。我们更应该把有限的精力用作有意的地方&#xff0c;那么我们今天…

TLS协议。

IPSec通过安全关联实现IP分组安全关联两端之间的安全传输过程&#xff0c;TLS通过建立安全连接实现数据在两个应用进程之间的安全传输过程。TLS建立安全连接时&#xff0c;实现安全连接两端应用进程之间的双向身份鉴别过程&#xff0c;保证经过安全连接传输的数据的保密性和完整…

【GPLT 二阶题目集】L2-017 人以群分

社交网络中我们给每个人定义了一个“活跃度”&#xff0c;现希望根据这个指标把人群分为两大类&#xff0c;即外向型&#xff08;outgoing&#xff0c;即活跃度高的&#xff09;和内向型&#xff08;introverted&#xff0c;即活跃度低的&#xff09;。要求两类人群的规模尽可能…

时间序列模型

一、季节分解 1、概念 时间序列也称为动态序列&#xff0c;是指将某种现象的指标数值按照时间顺序排列而成的数值序列。主要由时间要素和数值要素构成。时期序列中&#xff0c;数值要素反映现象在一定时期内发展的结果&#xff1b;时点序列中&#xff0c;数值要素反映现象在一…

如何在FreeBSD中安装Nginx,MySQL,PHP(FEMP)

本文介绍如何在FreeBSD 13系统中安装Nginx、MySQL、和PHP服务。系统环境FreeBSD 13.0-RELEASE更新系统在安装任何软件之前更新系统是一个好习惯&#xff0c;以便检查系统更新&#xff1a;rootfreebsd:~ # freebsd-update fetchrootfreebsd:~ # freebsd-update install安装Nginx…

在线教育-谷粒学院学习笔记(九)

文章目录1 内容介绍2 搭建项目前台环境NUXT3 整合项目首页4 nuxt路由5 首页数据banner显示—初始配置6 首页数据banner显示—接口7 首页数据banner显示—前端实现8 首页数据显示—热门课程和名师9 Redis复习10 首页数据添加Redis缓存1 内容介绍 搭建项目前台系统环境 NUXT 整合…

【PHP 随记】—— Composer 安装及镜像配置

&#x1f449;总目录&#x1f448;\large\colorbox{skyblue}{&#x1f449;总目录&#x1f448;}&#x1f449;总目录&#x1f448;​ 文章目录1、Composer 安装2、配置镜像Composer 是 PHP 用来管理依赖&#xff08;dependency&#xff09;关系的工具&#xff1b;简单来说&…

等级保护介绍

等保流程 等级保护 等保发展历程 。1994 首次国家提出等级保护概念 。1999 针对信息系统保护有法律依据 。2007 等保1.0措施。 。2017 立法了《网络安全法》 。2019年 等保2.0 颁布 等保2.0和1.0有什么特点和区别 1.名称变了信息安全技术信息等级保护要求 改为 信息安全基础网络…

1、测试用例通用模板

一、针对大版本需求的测试用例模板 1、测试用例模板说明 1.1、表头说明 项目版本号V1.0--测试用例&#xff08;示例标题&#xff09;(xxxxx有限公司测试组/ONLY FOR INTERNAL USE ONLY)Confidentiality level 密级(xxxxxx测试组/仅供内部使用) Internal 内部公开 修订记录版…

软件设计(二)

软件设计&#xff08;一&#xff09;https://blog.csdn.net/ke1ying/article/details/128779601 11、windows系统当双击.jpg文件的时候&#xff0c;系统会通过建立的 文件关联 来决定使用什么程序来打开该图像文件。 &#xff08;双击.jpg文件&#xff0c;系统会建立‘window…