Linux进程信号 | 信号产生

news2024/11/24 19:37:02

前面的文章中我们讲述了进程间通信的部分内容,在本文中我们继续来学习进程信号相关的知识点。

信号入门

生活角度的信号

在我们的日常生活中,就有着各种各样的信号,它会给我们传递各种各样的信息,可以知道的是一个信号例如:红绿灯。在我们没有收到红绿灯这种信号的时候,就已经知道了信号的具体的内容,知道这种信号应该被怎样处理。其次,当收到信号的时候,不一定能够立马处理这个信号,因此进程还需要有记录信号的能力。

技术应用角度的信号

在我们以往的Linux编程经历中,我们在shell下启动一个进程 ,当按下 Ctrl+c 的时候键盘输入就会产生一个硬件中断,被OS获取,解释成信号,并将这个信号发送给对应的进程,从而引起进程的退出。

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

每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到。可以发现没有32、33这两个信号。编号34以上的是实时信号,在我们的文章中只讨论编号34以下的信号即普通信号,不讨论实时信号。

关于信号有这样的几点需要我们注意:

  1. 程序员在设计进程的时候,早就已经设计了对信号的识别能力,因此进程在没有收到一个信号的时候就知道一个信号该怎样的处理;
  2. 从信号的产生到信号的处理有着一个时间窗口,在进程收到信号的时候如果没有立马处理这个信号,需要进程拥有记录信号的能力;
  3. 信号的产生对于进程来讲是异步的;
  4. 由于在这里我们使用的信号只有31个,需要描述一个信号并且使用一种技数据结构管理这个信号,因此在PCB中必定要存在一个位图结构来记录信号;
  5. 所谓的发送信号,本质就是写入信号直接修改特定进程的信号位中的特定比特位0->1,比特位的位置表示信号的编号,比特位的内容表示是否收到该信号;
  6. task_struct数据内核结构,只能由OS进行修改,无论后面我们有多少种信号的产生方式,最终都必须让OS来玩成最后的发送过程;
  7. 信号产生之后不是立即处理的,实在合适的时候处理的。

信号处理的常见方式

一个信号的处理动作有以下三种:

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

前台进程/后台进程

我们一般使用 ./运行我们的程序 这个程序一般是前台进程,我们屏幕上会一直打印数据,当我恩输入指令的时候是没有反应的。当我们在 ./运行程序的时候再后面添加上一个 & ,这样该程序就变为了后台进程,它也会一直向屏幕上输出但是我们同样可以输出指令。此时需要我们使用kill指令发送了一个信号才能删除该后台进程。那么Ctrl-c和kill-9都可以发送信号,怎样证明Ctrl-c发送的是一个信号呢?这里需要我们来认识一个函数

signal

这个函数的第一个参数就是信号的编号,第二个参数未来哪一个进程调用了该函数,该函数就会将收到信号的编号对应处理的默认动作改为第二个参数中对应的方法。

下面来简要的举一个例子(ctrl-c对应的就是编号为2的信号 ):

void handler(int signo)
{
    std::cout << "get a singal:" << signo << std::endl;
}

int main()
{
    signal(2, handler);

    while (true)
    {
        std::cout << "我是一个进程,我正在运行 ..., pid:" << getpid() << std::endl;
        sleep(1);
    }
}

当我们运行程序就会看到程序从原来的终止变为执行我们自定义的方法。当我们使用 kill -2 指令的时候同样可以看到程序并没有终止,而是同样执行我们自定义的方法,因此可以确定ctrl-c对应的是2号信号。

  1. signal 可以对指定的信号设置自定义处理动作;
  2. signal(2,handler)调用完这个函数的时候,handler方法调用了吗?没有,做了什么,只是更改了2号信号的处理动作,并没有调用该方法;
  3. 那么handler方法,什么时候被调用?当2号信号产生的时候。
  4. signo: 当特定的信号被发送给当前进程的时候,执行handler方法的时候,要自动填充对应的信号给handler方法。

下面的图就是信号对应的默认动作:

下面我们进行一个大胆的尝试,如果我们把所有的信号都替换为自定义动作是否就无法进行退出了?可以发现例如2号信号,3号信号都无法退出,但是使用kill -9 还是可以退出。 

产生信号

通过终端按键产生信号

例如上文中所述的 ctrl-c 表示2号信号,ctrl-\ 表示3号信号。 

调用系统函数产生信号

kill

可以使用kill函数来产生信号。这里我们

想编写一个函数使用这样的方式 ./mykill 9 1234 来进行信号的产生。

void Usage(std::string) // 如果输入的参数个数不正确就进行提示
{
    std::cout << "Usage: \n\t";
    std::cout << "信号编号 目标进程\n" << std::endl;
}

int main(int argc, char** argv)
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    int signo = atoi(argv[1]);
    int target_id = atoi(argv[2]);

    int n = kill(target_id, signo);
    if (n != 0)
    {
        std::cerr << errno << " : " << strerror(errno) << std::endl;
        exit(2);
    }
}

raise 

raise函数式是谁调用该函数就给该函数发送对应的信号。

void myhandler(int signo)
{
    std::cout << "get a signal: " << signo << std::endl;
}

int main(int argc, char** argv)
{
    while (true)
    {
        signal(SIGINT, myhandler);
        sleep(1);
        raise(2);
    }
}

通过上面我们学习的signal函数就可以对这个函数发送的指令进行捕捉,可以发现这个程序在持续运行,需要我们手动进行退出:

abort

让一个进程直接终止;

int main(int argc, char** argv)
{
    while (true)
    {
        signal(SIGABRT, myhandler);
        std::cout << "begin" << std::endl;
        abort();
        std::cout << "end" << std::endl;
    }
}

这里可以发现while循环没有继续进行下去,而是直接终止。

由软件条件产生的信号

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。

下面我们看一个简单的程序,我们想要知道1s计算机的计算能力:

void myhandler(int signo)
{
    std::cout << "get a signal: " << signo << std::endl;
    std::cout << count << std::endl;
    exit(1);
}

int main(int argc, char** argv)
{
    // IO的效率其实非常底下
    signal(SIGALRM, myhandler);
    alarm(1);
    while (true)
    {
        count++;
        // 打印,显示器打印,网络,IO
        // std::cout << count++ << std::endl; // 1s我们的计算机会将一个整数累计到多少
    }
}

使用IO,打印等操作:

直接++: 

这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。打个比方,某人要小睡一觉,设定闹钟为30分钟之后响,20分钟后被人吵醒了,还想多睡一会儿,于是重新设定闹钟为15分钟之后响,“以前设定的闹钟时间还余下的时间”就是10分钟。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数。 

硬件异常产生信号

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

下面我们就来看一下一些简单的例子:

除零错误

我们知道在程序中当除数为零时,系统就会报错退出,在这里我们简单的编写一个除数为零的例子,可以看出当我们运行该程序的时候就会发生报错。那么为什么除零就会异常报错呢?这是因为运行程序的时候CPU会将代码加载到内部进行处理,那么除零的这句代码同样会发送到CPU中,当实际在进行运算的时候CPU内部还会有状态寄存器,内部有比特位来记录当前计算的状态。除零的本质是计算机在除零的时候会发生溢出,那么状态寄存器中的相关位置就会记录相关的溢出信息。然后在一定的时间切片内CPU就会发现出现了问题并且会想操作系统进行告知,操作系统一旦发现溢出就会向我们的进程发送浮点数异常信号,将进程终止。从信号列表中可以发现是8号信号出现了异常。除零的本质就是触发硬件异常。

经过我们之前学习的signal函数就可以自定义信号的处理动作,因此确实可以看到是8号信号 

野指针问题

int* p = nullptr;
// p = 100; // 这里将100当做地址写给p,但是编译器可能会报错
*p = 100; // 会发生段错误,这里会向0号地址进行写入,但是我们并没有申请零号地址空间,因此会发生错误(野指针问题)

cout << "野指针问题 ... " << endl;

之前的文章中讲述过虚拟地址空间的问题,我们定义的变量都存在虚拟地址处,然后通过页表以及MMU硬件映射到物理地址中,在页表中除了对应的映射关系,还有着读写权限。当*p = 100这条指令运行时,第一步,并不是写入,而是首先进行虚拟地址到物理地址的转换 (没有映射,MMU硬件报错,有映射但是没有权限,MMU直接报错) 。操作系统发现硬件产生错误之后就会向进程发送对应的错误信号,将进程终止。

core dump以及Term、Core

当我们再次查看信号的默认动作时,可以发现,大部分的信号都是终止操作,但是终止操作也分为了两种,一种叫Term,另一种叫Core。

之前我们在学习进程控制的时候讲述过可以使用waitpid函数,通过获得输出型参数status来获取进程退出的信息,其中又一个coredump当时并没有详细的记性说明,下面我们就来进行具体的说明

 Linux系统级别提供了一种能力,可以让一个进程在异常的时候,OS可以在该进程异常的时候,将核心代码部分进行核心存储 -- 将内存中进程的相关数据全部存储到磁盘中,一般会在当前进程的运行目录下,形成core.pid这样的二进制文件(核心转储文件)。使用ulimit -a 指令就能够进行查看。

core file size就是核心转储文件,然后我们可以使用ulimit -c 指令对其设置大小。

然后我们使用while循环运行一个程序,让其接受我们自己发出的终止信号来模拟发生了异常。首先发送2号信号,可以发现进程正常的终止了,但是在目录下并没有生成对应的核心转储文件。接着我们使用3号信号,此时可以发现在目录下生成了对应的核心转储文件,并且进程退出时也会有coredump对应的标识符。

同时,可以发现对应进程的默认指令一个是Term,一个是Core。Term终止的就是终止没有多余的动作,Core终止会先进行核心转储,再终止进程。

核心转储有什么用?方便异常后进行调试。我们将程序设置为debug模式,然后运行gdb通过加载对应的core文件就能够获得报错相关的位置信息,这样我们就可以不用自己定位问题的位置,由gdb自动定位。(事后调试)

core dump 就是标志着是否开启了核心转储。

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

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

相关文章

webpack提升开发体验SourceMap

一、开发场景介绍 开发中我们不可避免的会写一些bug出来&#xff0c;这时候要调试&#xff0c;快速定位到bug到底出现在哪尤为关键。 例如我故意在sum函数中写一个错误代码如下&#xff1a; 这时我们用前面章节已经写好的开发模式的webpack.dev.js运行&#xff0c;控制台会出…

【Spring】— MyBatis与Spring的整合

目录 1.整合环境1.1准备所需的JAR包1&#xff0e;所需Spring框架的JAR包2&#xff0e;所需MyBatis框架的JAR包3&#xff0e;MyBatis与Spring整合所需的中间JAR包4&#xff0e;数据库驱动JAR包5&#xff0e;数据源所需JAR包 1.2 编写配置文件 2.整合2.1 传统DAO方式的开发整合1&…

龙蜥社区第 17 次运营委员会会议顺利召开

5 月 26 日&#xff0c;龙蜥社区走进 Arm 北京办公室召开了第 17 次运营委员会会议。本次会议由龙蜥社区运营委员会副主席金美琴主持。来自 Arm、阿里云、电信、红旗软件、飞腾、海光、Intel、浪潮信息、联通软研院、龙芯、凝思软件、麒麟软件、普华基础软件、申泰、统信软件、…

Vue-Element-Admin项目学习笔记(7)用Node.js写一个简单后端接口

前情回顾&#xff1a; vue-element-admin项目学习笔记&#xff08;1&#xff09;安装、配置、启动项目 vue-element-admin项目学习笔记&#xff08;2&#xff09;main.js 文件分析 vue-element-admin项目学习笔记&#xff08;3&#xff09;路由分析一:静态路由 vue-element-adm…

Opencv-C++笔记 (3) : opencv的库介绍以及和C++对接转换

文章目录 一、Opencv库的介绍calib3dcontribcoreimgprocfeatures2dflannhighguilegacymlnonfreeobjdetectoclphotostitchingsuperrestsvideoVideostab 二、C和MAT 转换方式2.1、一维Vector2.2、二维vector2.3 数组2.4、类型转换 ——一维转 数组2.5、类型转换 -------- 一维MAT…

抖音同城榜相关介绍来啦,这篇文不若与众带你去详细了解它

一、抖音同城榜是什么&#xff1f; 抖音同城榜是抖音平台上的一个功能&#xff0c;它可以根据用户所在的地理位置&#xff0c;推荐附近热门的视频和达人。用户可以通过同城榜来了解当地的热门话题、美食、景点等信息&#xff0c;也可以通过同城榜认识志同道合的朋友和达人&…

DTD怎样进行元素类型定义?【语法格式】

Bootstrap常用组件包括按钮、导航、菜单和表单等。使用Bootstrap不需要编写复杂的样式代码&#xff0c;只需要使用Bootstrap组件就可以实现复杂的页面架构。下面将对Boolstrap按钮组件进行详细讲解。 Bootstrap提供了多种样式的按钮&#xff0c;每个样式的按钮都有自己的语义用…

动态规划:最长公共子序列

动态规划&#xff1a;最长公共子序列 前言一、动态规划 前言 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在…

练习1:线性回归

练习1&#xff1a;线性回归 介绍 在本练习中&#xff0c;您将 实现线性回归并了解其在数据上的工作原理。 在开始练习前&#xff0c;需要下载如下的文件进行数据上传&#xff1a; ex1data1.txt -单变量的线性回归数据集ex1data2.txt -多变量的线性回归数据集 在整个练习中&…

【IMX6ULL驱动开发学习】09.Linux驱动之GPIO中断(附SR501人体红外感应驱动代码)

Linux驱动的GPIO中断编程主要有以下几个步骤&#xff1a; 1、 通过GPIO号获取软件中断号 (中断编程不需要设置GPIO输入输出&#xff0c;当然申请GPIO&#xff0c;设置输入也没问题) int gpio_to_irq(unsigned int gpio)参数含义gpioGPIO引脚编号 2、 注册中断处理函数&#…

k8s中docker0默认ip修改

原因&#xff1a; 由于ip冲突&#xff0c;必须要修改docker0的默认ip 过程&#xff1a; &#xff08;1&#xff09;修改文件 /etc/docker/daemon.json 添加内容 “bip”: “ip/netmask” [ 切勿与宿主机同网段 ] &#xff08;2&#xff09; &#xff08;3&#xff09;重启docke…

视觉SLAM十四讲——ch9实践(后端1)

视觉SLAM十四讲——ch9的实践操作及避坑 0.实践前小知识介绍0.1 数据集的使用 1. 实践操作前的准备工作2. 实践过程2.1 Ceres BA2.2 g2o求解BA 3. 遇到的问题及解决办法3.1 查看.ply文件时报警告 0.实践前小知识介绍 0.1 数据集的使用 Ceres BA使用的是BAL数据集。在本例中&a…

为什么说2023年最难招聘的岗位是高性能计算工程师?

随着毕业季的临近&#xff0c;高校毕业生将进入就业关键阶段。据统计&#xff0c;2023届全国高校毕业生预计达到1158万人&#xff0c;同比增加82万人&#xff0c;再创新高。尽管有千万的大学毕业生&#xff0c;但是企业反馈依然很难招聘到合适的高性能计算工程师。 这主要归因于…

看到就赚到的5款小众软件

今天推荐5款十分小众的软件&#xff0c;知道的人不多&#xff0c;但是每个都是非常非常好用的&#xff0c;有兴趣的小伙伴可以自行搜索下载。 图文识别——PandaOCR PandaOCR是一款用于识别和转换图片中的文字的工具。它可以让你对任何格式的图片进行文字识别&#xff0c;并输…

STM32速成笔记—按键检测

如果需要本文程序工程&#xff0c;请评论区留邮箱或者私信。 文章目录 一、按键检测原理二、硬件连接三、程序设计3.1 初始化GPIO3.2 按键扫描函数 四、按键控制LED4.1 初始化LED和KEY的GPIO4.2 编写按键扫描函数4.2 编写LED控制函数4.3 编写按键服务函数 五、拓展5.1 一个按键…

如何使用二维码实现配电箱巡检

施工工地的外部环境条件恶劣,加之工地上机动车辆的运行和机械设备的应用&#xff0c;均易导致电气故障的发生。现场配电箱缺乏专业技术人员的管理,易造成触电伤害、火灾等事故。现场纸质巡检存在以下问题&#xff1a; 1、信息查询不便:配电箱信息、负责人&#xff0c;历史巡检维…

Flowable服务组件-扩展组件

Flowable服务组件-扩展组件 扩展组件 文章目录 Flowable服务组件-扩展组件前言Flowable给我们提供了非常丰富的组件&#xff0c;但是在实际场景中&#xff0c;我们有需要企业个性化的组件&#xff0c;如何扩展自己的组件至关重要 一、扩展微服务回调组件二、程序步骤1.定义我们…

618父亲节,感恩的祝福送给父亲!

父亲节&#xff08;Fathers Day&#xff09;&#xff0c;是感恩父亲的节日。Fathers day, is a day of thanksgiving for fathers. 第一个提出父亲节理念的人是1906年的多德夫人。她想用一个特殊的日子来纪念她的父亲&#xff0c;她的妈妈多年前就去世了。起初&#xff0c;多德…

1.4 场效应管

1.什么是场效应管&#xff1f; 场效应管&#xff08;Field-Effect Transistor&#xff0c;简称FET&#xff09;是一种基于电场效应调控电流的三端器件。它是一种用于电子电路中的重要元件&#xff0c;常用于放大信号、开关电路和模拟电路等应用。 场效应管主要由一个导电的沟…

git 的详细介绍使用

点击下载&#xff1a;Git下载地址 下载完成后在本地文件夹空白位置右键能看到即为安装成功 git简介&#xff1a;git是一个版本控制系统&#xff0c;见下方图详解 快速查看git的全局配置项 git config --list --global 查看指定的全局配置项 git config user.name git conf…