Linux系统:信号概念 信号产生

news2024/11/27 8:26:08

Linux系统:信号概念 & 信号产生

    • 信号概念
    • 信号产生
      • 软件信号
        • kill
        • raise
        • abort
        • alarm
      • 硬件信号
        • 键盘产生信号
        • 硬件中断


信号概念

信号是进程之间事件异步通知的一种方式

在Linux命令行中,我们可以通过ctrl + c来终止一个前台运行的进程,其实这就是一个发送信号的行为。我们按下ctrl + c是在shell进程中,而被终止的进程,是在前台运行的另外一个进程。因此信号是一种进程之间的通知方式。

可以通过指令kill -l来查询信号:

在这里插入图片描述

以上就是Linux中的全部信号,它们分为两个区间:[1, 31][34, 64],也就是说没有0,32,33这三个信号,虽然信号的最大编号为64,但实际上只有62个信号。

  • [1, 31]:这些信号称为非实时信号,当进程收到这些信号后,可以自己选择合适的时候处理
  • [34, 64]:这些信号称为实时信号,当进程收到这些信号后,必须立马处理

由于现在的操作系统基本都是分时操作系统,因此实时信号其实是不符合设计理念的,几乎用不到实时信号,本博客只讲解非实时信号

上图中,所有信号都是大写的单词,在C/C++中,一般来说就是大写的,其实信号名就是宏

那么进程收到信号后要怎么处理呢?

进程有三种处理信号的方式:

  1. 忽略此信号
  2. 执行信号的默认处理函数
  3. 执行信号的自定义处理函数,这种方式也称为信号捕捉

可以通过man 7 signal来查看信号的默认处理行为:

在开头,可以看到如下页面:

在这里插入图片描述

其中TermIgnCoreStopCont就是信号处理的默认行为,我简单翻译一下它们的作用:

  • Term:默认操作是终止进程
  • Ign:默认操作是忽略信号
  • Core:默认操作是终止进程并转储核心
  • Stop:默认操作是暂停进程
  • Cont:默认操作是,如果该进程当前已暂停,则继续该进程

以上五种

其中TermCore都是终止进程,不过Core会额外进行一个核心转储,这个会在博客后续讲解。

再往下翻阅,就可以看到每个信号的描述:

在这里插入图片描述

各列的含义如下:

  • Signal:信号的名称
  • Standard:该信号在哪一个标准中提出
  • Action:进程收到该信号后的默认处理行为
  • Comment:对信号的简单描述

对于刚才说的三种处理方式:

  1. 忽略此信号 -> 即 ActionIgn
  2. 执行信号的默认处理函数 -> 即Action 不为 Ign
  3. 执行信号的自定义处理函数,这种方式也称为信号捕捉

那么我们又要如何自己定义信号处理函数呢?

signal函数,包含在头文件<signal.h>中,可以自定义信号的处理方式,函数原型如下:

sighandler_t signal(int signum, sighandler_t handler);

其中这个sighandler_t类型,本质是一个void (*)(int)类型的函数指针,也就是说自定义的信号处理函数必须是void (int)的格式。其中这个处理函数的第一个参数int,就是用来接收信号的编号的。

而返回值也是sighandler_t,其返回原先该信号处理的函数的函数指针。

示例:

void handler(int sig)
{
    cout << "get sig: " << sig << endl;
}

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

    while(true)
    {
        cout << "hello world!" << endl;
        sleep(1);
    }

    return 0;
}

以上代码中,我们通过signal(2, handler);2号信号的处理方式变成了执行函数handler,此后进程收到2 号信号时,就会执行cout << "get sig: " << sig << endl;了。

2号信号SIGINT就是ctrl + C发送的信号,因此我们可以直接在shell中通过ctrl + C来发送2号信号,从而验证效果。

输出结果:

在这里插入图片描述

可以看到,我在hello world!期间按下了两次ctrl + C,本来ctrl + C发送2号信号时会直接终止进程,但是现在却是输出get sig 2了,因为我们将2号信号的行为改变了。


信号产生

简单讲解了一下信号的三种处理方式后,再来看看信号是如何产生的,在Linux中,信号主要有两种产生方式:软件条件硬件异常

软件信号

在 Linux 中,“软件条件” 发出的信号指的是由 进程自身或其他进程 产生的信号,而不是由硬件中断或其他外部事件触发的信号。

Linux中有多种系统调用可以发送信号,在此我讲解killraiseabortalarm四种接口,其中abort并不是一个系统调用,而是一个用户操作接口。

kill

kill函数用于给指定pid的进程发送指定信号,需要头文件<sys/types.h><signal.h>,函数原型如下:

int kill(pid_t pid, int sig);

参数:

  • pid:收到该信号的进程的pid
  • sig:发送哪一个信号

返回值:

  • 返回0:发送信号成功
  • 返回-1:发送信号失败

示例:

void handler (int sig)
{
    cout << "get sig: " << sig << endl;
    exit(1);
}

int main()
{
    pid_t id = fork();

    if (id == 0) // 子进程
    {
        signal(2, handler);

        while (true)
        {
            cout << "I am child process" << endl;
            sleep(1);
        }
    }

    sleep(5);
    kill(id, 2);

    return 0;
}

以上示例中,父进程通过fork创建子进程,sleep五秒后通过kill(id, 2);给子进程发送(2) SIGINT信号。子进程通过signal(2, handler);修改了信号处理方式,随后每秒钟输出一次I am child process

handler中,会先输出get sig: 2,表示自己收到了信号,然后exit退出进程。

输出结果:

在这里插入图片描述

可以看到,我们确实通过kill函数给进程发送信号了。

另外的,也可以通过kill指令发送信号,格式为:

kill -sig pid

其中sig为要发送的信号,pid为收到信号的进程pid

示例:

现在进程执行如下代码:

void handler(int sig)
{
    cout << "get sig: " << sig << endl;
    exit(1);
}

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

    while (true)
    {
        cout << "pid = " << getpid() << endl;
        sleep(1);
    }

    return 0;
}

进程先修改信号2的处理方式为handler,然后死循环输出自己的pid。随后我们在另外一个窗口通过kill -2 pid来发送(2) SIGINT信号。

输出结果:

在这里插入图片描述

左侧窗口中,进程输出自己的pid = 130988,在右侧窗口通过kill -2 130988向进程发送信号,输出一句get sig 2后退出。

其实kill指令底层就是调用kill接口,依然属于系统调用的范围。


raise

raise函数用于给自己发送信号,需要头文件<signal.h>,函数原型如下:

int raise(int sig);

参数:

  • sig:发送哪一个信号

返回值:

  • 返回0:发送信号成功
  • 返回-1:发送信号失败

示例:

void handler(int sig)
{
    cout << "get sig: " << sig << endl;
    exit(1);
}

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

    int cnt = 5;
    while (cnt)
    {
        cout << "I am a process cnt = " << cnt-- << endl;
        sleep(1);
    }

    raise(2);

    return 0;
}

先通过signal(2, handler);修改信号的处理函数,随后循环五次,输出"I am a process cnt = ",最后通过raise(2);给自己发送(2) SIGINT信号。

输出结果:

在这里插入图片描述


abort

abort函数用于给自己发送(6) SIGABRT信号,需要头文件<stdlib.h>,属于用户操作接口,函数原型如下:

void abort(void);

这个函数功能十分简单,就是给自己发送(6) SIGABRT信号,示例:

int main()
{
    abort();
    
    return 0;
}

我们就在进程内部直接调用abort();,输出结果:

在这里插入图片描述

最后进程输出了Aborted表示自己收到了(6) SIGABRT信号。


alarm

alarm函数用于设定一个闹钟,在一定之间后给当前进程发送信号(14) SIGALRM,需要头文件<unistd.h>,函数原型如下:

unsigned int alarm(unsigned int seconds);

参数:

  • seconds:在seconds秒后发送信号

返回值:

  • 如果之前有还没响的闹钟:取消上一次的闹钟,并返回上一次闹钟的剩余秒数
  • 如果之前没有闹钟了:返回0

示例:

void handler(int sig)
{
    cout << "get sig: " << sig << endl;
    exit(1);
}

int main()
{
    signal(SIGALRM, handler);
    alarm(1);

    int i = 0;
    while(true)
    {
        cout << i++ << endl;
    }

    return 0;
}

以上代码中,先通过signal(SIGALRM, handler);自定义信号(14) SIGALRM的处理方式。然后通过alarm(1);设定一秒钟的闹钟,在一秒内,程序会不断执行while循环让i++,我们可以看看一秒内计算机可以执行多少次i++

输出结果:

在这里插入图片描述

可以看到,计算到76451时,就收到了SIGALRM,终止进程了。


硬件信号

硬件信号指的是由 硬件事件 触发的信号,而不是由软件代码逻辑控制的。

具体来说,以下情况属于硬件条件发出的信号:

  • 中断: 硬件设备,例如键盘、鼠标、网络接口等,在发生事件时会向 CPU 发送中断信号,例如键盘按键按下、网络数据包到达等。
  • 异常: CPU 在执行指令过程中,如果遇到错误情况,例如除以零、内存访问错误等,会产生异常信号。
  • 时钟中断: 系统定时器会定期向 CPU 发送时钟中断信号,用于调度进程和执行定时任务。
键盘产生信号

通过键盘发送信号是最简单的信号发送方式,最常用的有ctrl + Cctrl + \

  • ctrl + C:向前台进程发送(2) SIGINT信号,效果为直接终止进程
  • ctrl + \:向前台进程发送(3) SIGQUIT信号,效果为直接终止进程

示例:

在这里插入图片描述

该进程每隔一秒输出Hello world,第一次执行进程,我按下ctrl + C后直接终止了进程,第二次按下ctrl + \输出了一个Quit后终止进程。


硬件中断

硬件给进程发送信号的本质,其实是通过硬件中断

硬件中断是指硬件设备向 CPU 发送的信号,通知 CPU 有事件发生需要处理。

想象一下,你的电脑就像一个忙碌的办公室,CPU 就像办公室的经理,负责处理各种任务。而键盘、鼠标、网卡等硬件设备就像办公室的员工,需要向经理汇报工作进度或请求帮助。

当一个员工需要向经理汇报工作进度时,他会敲响经理办公室的门,发出一个“中断”信号。经理收到这个信号后,会暂停当前的工作,转而去处理员工的请求。

硬件中断也是类似的机制。当一个硬件设备需要向 CPU 汇报事件发生时,它会向 CPU 发送一个中断信号。CPU 接收这个信号后,会暂停当前执行的任务,转而去处理硬件设备的请求

看到以下代码:

int main()
{
    int a = 5 / 0;

    return 0;
}

这是一个很经典的除零错误,如果我们强行运行这个进程,会报出如下错误:

在这里插入图片描述

发送了错误Floating point exception,本质上是收到了信号(8) SIGFPE,那么为什么除零会发送这个信号呢?其实本质上是发生了硬件中断。

如下图:

在这里插入图片描述

在以上过程中,各个部分发挥作用如下:

  • CPUCPU是一个硬件,其要处理计算,而5/0这个计算过程就是在CPU中完成的,当CPU检测到这个计算中0做了除数,于是对自己发起硬件中断
  • 操作系统:操作系统检测到硬件中断后,跳转到中断处理程序中断处理程序Linux内核的一部分),然后检测该硬件中断是什么原因,发现是因为除零错误,于是给进程发送(8) SIGPFE信号
  • 进程:进程收到操作系统发来的(8) SIGPFE信号后,执行相应的处理措施

这就是硬件中断发送信号的过程,其实发送信号本质还是操作系统来发送,而硬件发现异常后,只是通知操作系统去发送信号。

再比如说我们之前的ctrl + C按键发送(2) SIGINT信号,本质也是硬件中断,当我们从键盘输入了数据后,键盘向CPU发出硬件中断,随后CPU去执行操作系统中的硬件中断程序,发现是用户按下了ctrl + C,于是操作系统向进程发送(2) SIGINT信号。

再看到这样的代码:

void handler(int sig)
{
    cout << "get sig: " << sig << endl;
    sleep(1);
}

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

    int a = 5 / 0;

    return 0;
}

我们已经知道,除0错误是(8) SIGFPE信号,现在我们把其对应的处理方式改为输出一条语句,我们看看会发生什么:

在这里插入图片描述

可以看到,进程现在在不断的输出get sig: 8,可是我们只发生了一次int a = 5 / 0,为什么会产生这么多信号?

现在先想一想,为什么我们进程会收到(8) SIGFPE信号,这是因为CPU检测到5 / 0的错误,发生了硬件中断。请问我们调用了handler函数之后,CPU内部的5 / 0被处理了吗?答案是没有的!

也就是说,CPU会被一直卡在5 / 0CPU不知道怎么计算这个表达式,于是一直硬件中断,操作系统就一直发送(8) SIGFPE信号,所以进程就一直执行handler函数了。

与软件条件信号相比,硬件条件信号具有以下特点:

  • 由硬件触发: 硬件条件信号的产生是由硬件事件触发的,而不是由代码逻辑决定的
  • 不可控性: 硬件条件信号的产生通常无法被进程控制,例如硬件设备的故障、网络连接中断等。
  • 不可预测性: 硬件条件信号的产生通常是不可预测的,因为它们是由硬件事件触发的。

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

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

相关文章

利用K8S技术栈打造个人私有云

1.三个节点&#xff1a;master&#xff0c;slave&#xff0c;client 在Kubernetes集群中&#xff0c;三个节点的职责分别如下&#xff1a; Master节点&#xff1a; docker&#xff1a;用于运行Docker容器。 etcd&#xff1a;一个分布式键值存储系统&#xff0c;用于保存Kuberne…

微信投票源码系统+礼物+道具投票 无限多开 带完整的安装代码包以及搭建教程

系统概述 微信投票源码系统是一款基于先进技术开发的综合性投票平台&#xff0c;它不仅融合了传统投票的核心功能&#xff0c;还创新性地引入了礼物和道具投票机制&#xff0c;为用户带来了全新的投票体验。 该系统支持无限多开&#xff0c;这意味着用户可以根据实际需求&…

AI写真:ControlNet 之 InstantID

但是 IPAdapter-FaceId 目前只在 SD 1.5 模型上表现较好&#xff0c;SDXL 模型上的表现较差&#xff0c;不能用于实际生产。可是很多同学已经在使用SDXL了&#xff0c;而且SDXL确实整体上出图效果更好&#xff0c;怎么办&#xff1f; 这篇文章就来给大家介绍一个在SDXL中创作A…

多线程环境下,HashMap 为什么会出现死循环?

引言&#xff1a;HashMap作为一个常用的键值对存储结构&#xff0c;其内部实现在不同的JDK版本中有所演变&#xff0c;但其基本原理始终是通过哈希算法和数组来实现快速查找和存储。我们将探讨HashMap在多线程环境下出现死循环的根本原因&#xff0c;深入分析其中的数据结构特点…

网络安全(完整)

WAPI鉴别及密钥管理的方式有两种&#xff0c;既基于证书和基于预共享密钥PSK。若采用基于证书的方式&#xff0c;整个国产包括证书鉴别、单播密钥协商与组播密钥通告&#xff1b;若采用预共享密钥方式&#xff0c;整个国产则为单播密钥协商与组播密钥通告蠕虫利用信息系统缺陷&…

Tailwind CSS 响应式设计实战指南

title: Tailwind CSS 响应式设计实战指南 date: 2024/6/13 updated: 2024/6/13 author: cmdragon excerpt: 这篇文章介绍了如何运用Tailwind CSS框架创建响应式网页设计&#xff0c;涵盖博客、电商网站及企业官网的布局实例&#xff0c;包括头部导航、内容区域、侧边栏、页脚…

国产24位I2S输入+192kHz立体声DAC音频数模转换器CJC4344

CJC4344是一款立体声数模转换芯片&#xff0c;内含插值滤波器、multi bit数模转换器、输出模拟滤波器。CJC4344系列支持大部分的音频数据格式。CJC4344基于一个带线性模拟低通滤波器的四阶multi-bitΔ-Σ调制器&#xff0c;而且本芯片可以通过检测信号频率和主时钟频率&#xf…

新能源汽车的能源动脉:中国星坤汽车电缆在新能源汽车电气化中的应用!

随着新能源汽车行业的蓬勃发展&#xff0c;汽车电缆组件作为汽车电气系统的核心组成部分&#xff0c;其重要性日益凸显。中国星坤汽车电缆组件以其卓越的性能和创新技术&#xff0c;为汽车的电能传输、信号传递和控制提供了坚实的保障。本文将深入解析星坤汽车电缆组件的特性、…

PCB雕刻切割用德国自动换刀主轴SycoTec 4033 AC-ESD

随着电子行业的蓬勃发展&#xff0c;印刷电路板&#xff08;PCB&#xff09;的应用范围正在迅速扩大&#xff0c;涵盖了通信、计算机、消费电子等诸多领域。伴随着PCB的广泛应用&#xff0c;对PCB板切割加工技术的要求也日益严格。高速电主轴作为分板机的关键零部件之一&#x…

Pyqt QCustomPlot 简介、安装与实用代码示例(三)

目录 前言实用代码示例Line Style DemoDate Axis DemoParametric Curves DemoBar Chart DemoStatistical Box Demo 所有文章除特别声明外&#xff0c;均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 nixgnauhcuy’s blog&#xff01; 如需转载&#xff0c;请标明出处&#x…

最新QT安装程序安装QT旧版本

1、下载Qt在线安装程序 官方下载地址&#xff1a;https://download.qt.io/official_releases/online_installers/ 也可以选择国内镜像地址下载&#xff0c;如清华大学的镜像地址&#xff1a; https://mirrors.tuna.tsinghua.edu.cn/qt/official_releases/online_installers/…

C#ListView的单元格支持添加基本及自定义任意控件

功能说明 使用ListView时&#xff0c;希望可以在单元格显示图片或其他控件&#xff0c;发现原生的ListView不支持&#xff0c;于是通过拓展&#xff0c;实现ListView可以显示任意控件的功能&#xff0c;效果如下&#xff1a; 实现方法 本来想着在单元格里面实现控件的自绘的…

python-日历库calendar

目录 打印日历 基本日历类Calendar TextCalendar类 HTMLCalendar类 打印日历 设置日历每周开始日期(周几) import calendarcalendar.setfirstweekday(calendar.SUNDAY) # 设置日历中每周以周几为第一天显示 打印某年日历 print(calendar.calendar(2024, w2, l1, c6, m…

事实证明:企业级中后台框架,大厂还是主角,小厂打酱油。

提及中后台框架&#xff0c;你或许能够想到antdesign、arcodesign还有fusion等等&#xff0c;这些都是背靠大厂&#xff0c;是市场的主角&#xff0c;而一些小厂框架往往是扮演者陪太子读书的角色。本文将给大家分享市面上有哪些大厂的中后台框架&#xff1f;为什么大厂要开源自…

MySQL修改用户权限(宝塔)

在我们安装好的MySQL中&#xff0c;很可能对应某些操作时&#xff0c;不具备操作的权限&#xff0c;如下是解决这些问题的方法 我以宝塔创建数据库为例&#xff0c;创建完成后&#xff0c;以创建的用户名和密码登录 这里宝塔中容易发生问题的地方&#xff0c;登录不上去&#…

29. 透镜阵列

导论&#xff1a; 物理传播光学&#xff08;POP&#xff09;不仅可以用于简单系统&#xff0c;也可以设计优化复杂的光学系统&#xff0c;比如透镜阵列。 设计流程&#xff1a; 透镜阵列建模 在孔径类型中选择“入瞳直径”&#xff0c;并输入2 在视场设定中。设置一个视场&…

求二叉树最大深度-二叉树

104. 二叉树的最大深度 - 力扣&#xff08;LeetCode&#xff09; 1、用层序遍历&#xff0c;一层层遍历 class Solution { public:int maxDepth(TreeNode* root) {if(root nullptr)return 0;vector<TreeNode*> que;que.push_back(root);int res 0;//记层数while(!que.e…

【GIS技术】Shp矢量图斑数据的四至点坐标或四至坐标计算

经常有从事gis相关、地信相关行业的朋友或者是需要对图斑矢量进行四至坐标的计算的时候&#xff0c;按照各类搜索引擎或者是教学文章中的教学步骤求出来的四至坐标是错的。特别是比如林业图斑求四至点、农田四至点等等。 错的原因在于很多文章中教学的四至坐标实际上指的是图斑…

带你走进CCS光源——环形低角度光源LDR2-LA系列

机器视觉系统中&#xff0c;光源起着重要作用&#xff0c;不同类型的光源应用也不同&#xff0c;选择合适的光源成像效果非常明显。今天我们一起来看看CCS光源——工业用环形低角度光源LDR2-LA系列。 LDR2-LA系列 采用柔性基板&#xff0c;创造最佳倾斜角度。 通过从低角度向…

目标检测——室内服务机器人LifelongSLAM数据集

引言 亲爱的读者们&#xff0c;您是否在寻找某个特定的数据集&#xff0c;用于研究或项目实践&#xff1f;欢迎您在评论区留言&#xff0c;或者通过公众号私信告诉我&#xff0c;您想要的数据集的类型主题。小编会竭尽全力为您寻找&#xff0c;并在找到后第一时间与您分享。 …