当前位置: 首页 > article >正文

【Linux修行路】信号的产生

目录

⛳️推荐

一、信号的产生

二、产生信号的系统调用

2.1 kill——给指定的进程发送指定的信号

2.2 模拟实现指令 kill

2.3 raise——给调用的进程发送指定的信号

2.4 abort——给调用者发送 6 号信号

三、验证哪些信号不可以被捕捉

四、为什么除0和解引用空指针会给进程发信号呢?

五、alarm——设置闹钟


⛳️推荐

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站【Linux修行路】动静态库详解点击跳转到网站

一、信号的产生

  • 键盘组合键ctrl+c 给进程发送2号信号;ctrl+\ 给进程发送3号信号;ctrl+z 给进程发送19号信号(该信号无法被 signal 信号捕捉)。

  • 指令kill -signo pid

  • 系统调用killraiseabort

  • (硬件)异常:例如常见的除0(Floating point exception),当程序中出现除0异常,操作系统就会给对应的进程发送 8 号信号,来终止进程(这是 8 号信号的默认动作);对空指针解引用(Segmentation fault),当程序中出现对空指针解引用的时候,操作系统会给对应的进程发送 11 号信号。

  • 软件条件:管道通信中,写端正常,读端关闭,操作系统会给写端进程发送 13 号信号,终止掉正在进行向管道中写入的进程。alarm 闹钟 。

无论信号是如何产生的,最终一定是由操作系统发送给进程的,因为操作系统是进程的管理者。

二、产生信号的系统调用

2.1 kill——给指定的进程发送指定的信号

image-20240308092323511

2.2 模拟实现指令 kill

#include <iostream>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
#include <string>

using namespace std;

void Manual(char *directives)
{
    cout << "\n\t" << directives << " signum pid\n\n";
}

int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        Manual(argv[0]);
        exit(1);
    }

    int signum = stoi(argv[1]);

    int pid = std::stoi(string(argv[2]));

    cout << signum << ' ' << pid <<endl;

    pid_t ret = kill(pid, signum);
    if(ret == -1)
    {
        perror("kill");
        exit(1);
    }
    return 0;
}

2.3 raise——给调用的进程发送指定的信号

image-20240308102100461

raise 就相当于:kill(getpid(), signum)

2.4 abort——给调用者发送 6 号信号

image-20240308102625369

abort 函数内部,不仅会执行自定义捕捉(前提是捕捉了 6 号信号),在执行完自定义捕捉之后,还要去执行 6 号信号默认的终止动作。通过 kill 指令去给进程发送 6 号信号,进程只会执行捕捉动作(前提是对 6 号进行了捕捉)或者只会执行默认动作(前提是没有对 6 号信号做捕捉)。

三、验证哪些信号不可以被捕捉

#include <iostream>
#include <signal.h>
#include <unistd.h>

using namespace std;

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

int main()
{
    // signal(2, myhandler);
    signal(19, myhandler);

    for(int i = 1; i <= 31; i++)
    {
        signal(i, myhandler);
    }

    while(true)
    {
        cout << "Hello Linux" << ' ' << getpid() << endl;
        sleep(1);
    }
    return 0;
}

先通过循环将31个信号都捕捉,然后在命令行通过 kill 给进程发送信号,看是否被成功捕捉。经过验证 9、19 号信号都无法被捕捉。其中 9 号信号是杀进程,19 号信号是暂停进程。

四、为什么除0和解引用空指针会给进程发信号呢?

以除0为例,首先,程序中所有的指令是需要被 CPU 来执行的,计算任务也不例外。所以 CPU 里面有特定的寄存器来存储操作数,还有一个状态寄存器,里面都是比特位级别的标记位,其中有一个标记位就是用来表示当前运算是否发生溢出,当 CPU 在执行除 0 的时候,会发生溢出,此时 CPU 就会去修改溢出标记位,这些寄存器中的内容,都属于当前进程的上下文数据,当进程切换的时候,这些数据同时也会被其它进程的上下文数据替换,当该进程再一次被替换进 CPU 执行的时候,这些上下文数据又会恢复出来。**所以任何异常只会影响该进程本身,并不会波及到操作系统。**CPU 是也是硬件资源,操作系统作为硬件的管理者,它是必须要关心硬件的健康,所以当 CPU 中的溢出标志位被设置成溢出的时候,操作系统一定是能知道的,(本质上,发生溢出会给操作系统发送中断),操作系统知道后就会向该进程发送对应的信号。

空指针解引用、越界访问等本质都是因为虚拟到物理转化失败,转化失败还导致硬件报错,最终被操作系统识别到。

程序对于异常信号,默认动作是让程序立即终止。但是我们可以在程序中对异常信号进行捕捉,如果捕捉方法里没有做特殊处理,比如说让程序退出,那这导致的后果就是程序不会立即终止,而是一直被调用运行,硬件错误就一直存在,操作系统就会一直给进程发送异常信号

总结:程序中出现的所有异常,最终一定会转化成硬件错误,操作系统能够识别这种硬件错误,最终给进程发送对应的信号。

image-20240308133955080

信号捕捉并不是为了让我们来解决问题的,而是当收到这个信号后,程序可能要被立即终止,信号捕捉给了我们应对程序即将被终止的机会,我们可以在捕捉函数里做一些数据保存,打印日志等工作。

五、alarm——设置闹钟

image-20240308135828669

  • seconds:闹钟将在 seconds 秒时候响起(给进程发送 14 号信号),如果 seconds == 0 ,则之前设置的闹钟会被取消,并将剩下的时间返回。

  • 返回值:返回之前闹钟的剩余秒数,如果之前未设闹钟,或者上一次设置的闹钟已经响过了,那么返回的就是 0 。

小Tips:如果在上一次闹钟还没响的时候,再一次调用 alarm 函数设置闹钟,那么这一次调用的返回值就是上一次闹钟的剩余时间,并且闹钟的响应时间会被更新成这一次的,上一次那个还没响的闹钟就会被作废。

设置闹钟

#include <iostream>
#include <unistd.h>

using namespace std;

int main()
{
    int n = alarm(5);
    while(true)
    {
        cout << "process is running..." << endl;
        sleep(1);
    }
    return 0;
}

image-20240308141730584

捕捉闹钟信号

#include <iostream>
#include <signal.h>
#include <unistd.h>

using namespace std;

void handler(int signum)
{
    cout << "get a signal: " << signum << endl;
}

int main()
{
    signal(14, handler);
    int n = alarm(5);
    while(true)
    {
        cout << "process is running..." << endl;
        sleep(1);
    }
    return 0;
}

image-20240308142040976

alarm 返回值验证

#include <iostream>
#include <signal.h>
#include <unistd.h>

using namespace std;

void handler(int signum)
{
    cout << "get a signal: " << signum << endl;
    int n = alarm(5); // 闹钟每五秒响一次
    cout << n << endl;
}

int main()
{
    signal(14, handler);
    int n = alarm(50);
    while(true)
    {
        cout << "process " << getpid() << " is running..." << endl;
        sleep(1);
    }
    return 0;
}

第一次设置的闹钟是 50 秒,在前 50 秒内,通过命令行向进程发送 14 号信号,此时程序就会去执行 handler 方法,在该方法中又调用了一个 alarm,这就是前一个闹钟还没响,就又设置了一个闹钟。

在这里插入图片描述

🎁结语:

        今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,您的支持就是我前进的动力!


http://www.kler.cn/news/308720.html

相关文章:

  • AI与自然语言处理(NLP):中秋诗词生成
  • ffmpeg硬件解码一般流程
  • 关于RabbitMQ重复消费的解决方案
  • 大数据新视界 --大数据大厂之数据挖掘入门:用 R 语言开启数据宝藏的探索之旅
  • 图数据库的力量:深入理解与应用 Neo4j
  • Vue2知识点
  • makefile 的语法(7):函数 word wordlist words firstword lastword ;
  • SurrealDB:现代应用的端到端云原生数据库解决方案
  • Golang | Leetcode Golang题解之第401题二进制手表
  • 【图像拼接】基于SIFT/SURF特征算法的图像拼接,matlab实现
  • 【重学 MySQL】三十三、流程控制函数
  • 探索未来游戏边界:AI驱动的开放世界RPG引擎与UGC平台
  • 【每日一题】LeetCode 2332.坐上公交的最晚时间(数组、双指针、二分查找、排序)
  • 大数据新视界 --大数据大厂之Kafka消息队列实战:实现高吞吐量数据传输
  • Wophp靶场漏洞挖掘
  • 如何在webots中搭建一个履带机器人
  • RISC-V交叉编译器下载
  • 誉龙视音频综合管理平台 RelMedia/FindById SQL注入漏洞复现
  • 如何为聊天机器人添加检索功能:增强响应能力
  • 已开源!无限场景生成和高效数据迁移:3D金字塔扩散模型斩获ECCV24 Oral
  • 错误: 找不到或无法加载主类 org.apache.zookeeper.server.quorum.QuorumPeerMain
  • 设计模式 桥接模式(Bridge Pattern)
  • MySQL——数据库的高级操作(三)权限管理(1)MySQL 的权限
  • 基于微信小程序的高校实验室管理系统的设计与实现
  • 25届校招IQCAT思维能力自适应测验智鼎测评指南:题库获取、刷题策略与真题解析!
  • 单片机实现内存管理的C语言实现
  • 【计网】从零开始使用TCP进行socket编程 --- 客户端与服务端的通信实现
  • 如何使用ssm实现物流配送人员车辆调度管理系统的设计与实现+vue
  • vue3前端tab切换
  • dll修复工具4DDiG DLL Fixer,解决电脑dll丢失问题