[Linux打怪升级之路]-信号的产生

news2025/1/16 5:15:58

前言

作者小蜗牛向前冲

名言我可以接受失败,但我不能接受放弃

  如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正

目录

一、信号基础知识

1、信号是什么

2、信号的定义

3、信号的处理方式 

二、有关信号操作的函数

1、signal函数(捕捉信号)

2、kill函数

3、raise函数

4、abort函数

三、信号的产生 

1、通过终端按键产生信号

2、调用系统函数向进程发信号

3、硬件异常产生信号 

4、由软件条件产生信号 


本期学习目标: 了解什么是信号,明白部分信号操作的相关函数,理解信号产生的过程

一、信号基础知识

1、信号是什么

        在日常生活中,我们在遇到十字路口过马路,我们知道红灯停绿灯行。为什么我们能够知道红灯停绿灯行呢?这不难理解,这些规则都刻画在我们的脑海中了,也就是说红灯其实就是一种信号,当我们识别红灯信号,大脑就会做出反应控制身体不动,当然有时候会人闯红灯,信号肯定是会传递了的,也就是说其实信号也是可以被忽略的。

那在Linux下的信号又是指的是什么

下面我们见一个现象

这里我们运行程序,当我们按下Ctrl+c将的时候,我们发现进程退出。

 这也就说明,操作系统肯定给进程发送了一个信号。

操作系统发送一个SIGINT信号给当前正在运行的进程。SIGINT信号表示一个中断请求,它的默认行为是终止进程。

当你按下Ctrl+C时,终端会捕获这个键盘事件,并转发一个SIGINT信号给当前在前台运行的进程。这个信号告诉进程有一个中断请求,通常意味着用户希望终止该进程。

2、信号的定义

信号是一个用来表示事件或消息的物理量。在操作系统中,信号是一种软件中断,用于通知进程发生了某个事件。这种事件可能是硬件异常、用户键入特殊的终端控制字符,或者其他进程发送给该进程的消息。每个信号都有一个唯一的正数描述和一个符号名。例如,SIGINT是用于表示中断的信号,其编号通常是2。信号可以用于进程间的简单通信,也可以用于处理异步事件

在操作系统下我们可以通过:

//查看系统定义的命令
kill -l

在Linux中总共62共信号 

  • 每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到,例如其中有定 义 #define SIGINT 2
  • 编号34以上的是实时信号,这里只讨论编号34以下的信号,不讨论实时信号。这些信号各自在什么条件下 产生,默认的处理动作是什么,在signal(7)中都有详细说明: man 7 signal
//查看7号手册
man 7 signal

3、信号的处理方式 

在Linux中,信号处理的常见方式包括以下几种:

  1. 忽略信号:进程可以选择忽略某些信号,即当这些信号发生时,进程不会执行任何操作。这种方式可以用于屏蔽一些不必要的信号干扰。
  2. 默认行为:对于每个信号,系统都定义了一个默认行为。如果进程没有为某个信号设置自定义处理函数,那么信号发生时将会执行默认行为。默认行为可以是终止进程、忽略信号、停止进程等。
  3. 自定义处理函数:进程可以通过信号处理函数来捕获信号,并在信号发生时执行自定义的操作。这种方式常用于实现一些特定的行为,比如在接收到某个信号时进行日志记录、清理资源等。进程可以使用signal函数或者sigaction函数来设置信号处理函数。

二、有关信号操作的函数

1、signal函数(捕捉信号)

功能:用于捕捉信号并执行相应的处理操作

原型:  sighandler_t signal(int signum, sighandler_t handler);

参数 :

        signum:要捕捉的信号编号

        handler:指定一个函数指针,它指向的信号处理函数将在接收到指定的信号时被调用

返回值:signal函数返回之前为sig信号设置的处理函数的地址

typedef void (*sighandler_t)(int);

这里我们要特别  sighandler_t其实是一个函数指针。

signal函数的第二个参数handler是一个指向信号处理函数的指针,也就是sighandler_t类型的函数指针。这个参数用于指定当信号发生时应该执行的函数。

使用handler参数的方式如下:

  1. 自定义信号处理函数:首先,你需要定义一个符合sighandler_t类型的函数,即接受一个整数参数(信号编号)并返回void的函数。这个函数将用于处理信号。例如:

    void my_handler(int signal_num) {  
        // 处理信号的代码  
    }
  2. 设置信号处理函数:使用signal函数来设置信号的处理函数。将信号编号和自定义的处理函数作为参数传递给signal函数。例如,为了设置SIGINT信号(通常由Ctrl+C发送)的处理函数为my_handler

    signal(SIGINT, my_handler);

注意:

  • 处理函数通常会根据信号编号采取不同的操作。因此,在处理函数中,你可以使用传递的信号编号来判断是哪个信号被接收。
  • 如果你将handler设置为SIG_IGN,那么信号将被忽略。
  • 如果你将handler设置为SIG_DFL,那么将采取信号的默认行为。

举例用法:

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

using namespace std;

void my_handler(int signal_num)
{
    cout<< "接收到的信号码: "<<signal_num << " "<<"my_pid: "<< getpid()<<endl;
}
//测试
int main()
{
    signal(SIGINT,my_handler);
    while(1)
    {
        cout<< "我是一个进程,我正在运行" <<endl;
        sleep(1);
    }
    return 0;
}

这里我们发现,当我们在按Ctrl+ c,进程不再中止,而是去执行my_handler函数中的操作,singal也捕获了2号命令。(这里我们是用kill -9 pid中止进程的) 。

2、kill函数

功能:用于向指定进程发送信号

原型: int kill(pid_t pid, int sig)

参数 :

       pid:数指定要发送信号的进程ID

        sig:参数指定要发送的信号类型

返回值:当调用成功时,kill函数返回0;否则,返回-1并设置errno来表示错误原因

 kill函数可以发送多种类型的信号,例如:

  • SIGINT:中断进程。通常是通过用户按下Ctrl+C来产生的。
  • SIGTERM:终止进程。这是一个终止进程的请求,进程可以捕获该信号进行清理操作,然后退出。
  • SIGKILL:强制终止进程。这个信号会直接终止进程,进程无法捕获或忽略它。 

3、raise函数

功能:用于发送信号给当前进程

原型:int raise(int sig)

参数 :

        sig:参数指定要发送的信号类型

 当调用raise函数时,它会向当前进程发送指定的信号。如果信号处理程序已经注册了对应的信号,它将被调用进行处理。否则,默认的信号处理动作将被执行,可能会导致进程终止、停止或忽略信号。

举例:

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>

using namespace std;

void my_handler(int signal_num)
{
    cout<< "接收到的信号码: "<<signal_num << " "<<"my_pid: "<< getpid()<<endl;
}
//测试
int main()
{
    
    //注册信号处理程序
    signal(SIGINT,my_handler);

    cout<<"SIGINT to slef"<<endl;

    //发送SIGINT给当前进程
    raise(SIGINT);
    cout<<" signal after" <<endl;

    return 0;
}

 

4、abort函数

功能:用于异常终止程序的函数

原型:void abort(void);

调用abort函数将导致程序异常终止,并返回一个非零的退出码给操作系统,以指示程序异常结束。

由于abort函数会立即终止程序,所以它通常会在发现严重错误的情况下使用,例如内存泄漏、无效参数等。它向程序员提供了一种机制,用于在无法恢复正常执行流程的情况下紧急停止程序

然而,由于abort函数不进行任何清理操作,因此不建议在正常情况下使用它来结束程序。在大多数情况下,应该尝试通过其他方式恢复程序的执行,或者使用更适当的函数来进行正常的程序终止,如exit函数。

三、信号的产生 

1、通过终端按键产生信号

通过终端按键产生信号是指用户在终端(命令行界面)按下特定的按键组合,向当前进程发送一种信号。这种信号可以是一种通知或请求,告诉进程执行某种特定的操作

在Unix和类Unix系统中,终端按键可以产生信号,这些信号可以与进程进行交互。例如,当用户按下 Ctrl+C 组合键时,会向当前正在运行的进程发送一个 SIGINT 信号。这个信号的默认处理动作是终止进程

类似地,还有其他按键组合可以发送不同的信号,比如 Ctrl+\ 会发送 SIGQUIT 信号默认处理动作也是终止进程,并生成 core dump 文件

这种通过终端按键产生信号的方式,提供了一种用户与进程交互的手段,用户可以通过这种方式控制进程的行为,比如终止进程、暂停进程等。同时,进程也可以根据自身需要对这些信号进行处理,比如忽略信号、自定义处理函数等。

那么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 file size文件数量是0 

2、调用系统函数向进程发信号

这里我们可以通过系统函数kill,raise,abort函数来向进程发送我们想要的信息

 int cnt = 0;
    while(cnt <= 10)
    {
        printf("cnt : %d ,pid: %d\n",cnt++,getpid());
        sleep(1);
        // if(cnt >= 5) abort();
        if(cnt >= 5) raise(9);
    }

 调用abort终止程序

 调用raise(9)终止程序

3、硬件异常产生信号 

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

除以0的指令

当我们在编写的程序进行除0操作,为什么就会发送8好信号终止进程呢?

通过下图进行理解:

当我们进行除0操作,在CPU中有一个专门用来检测运算的,状态寄存器,里面有一个溢出标记位当除0时,标记位就会溢出,从而被操作系统知晓,发送8好信号给进程。 

非法内存地址 

OS怎么知道呢??我野指针了呢?,为什么程序中有野指针就会崩溃?

我们知道,其实指针指向的是虚拟地址,通过页表映射到物理地址,当指针越界访问的时候,操作系统就可以察觉到,从而对进程发送11号信号终止进程。

void my_handler(int signal_num)
{
    cout<< "接收到的信号码: "<<signal_num << " "<<"my_pid: "<< getpid()<<endl;
    sleep(1);
}
//测试
int main(int argc, char *argv[])
{

    //硬件异常产生信号

    signal(11,my_handler);
    while(true)
    {
       std::cout << "我在运行中...." << std::endl;
        sleep(1);
        int *p = nullptr; 
       //写一个野指针
        *p = 1;
    }
    return 0;
}

4、由软件条件产生信号 

在理解软件条件产生信号之前,我们需要理解allarm函数

头文件:#include unsigned

类型:int alarm(unsigned int seconds);

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

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

void my_handler(int signal_num)
{
    cout<< "接收到的信号码: "<<signal_num << " "<<"my_pid: "<< getpid()<<endl;
    exit(1);
}
//测试
int main(int argc, char *argv[])
{
 
    //4、软件异常产生信号
    int cnt = 0;
    signal(SIGALRM,my_handler);
    //统计1秒数据能够累计到多少
    alarm(1);
    while(true)
    {
        cnt++;
        cout<< cnt <<endl;
    }
   
    return 0;
}

这里是没有用signal捕捉信号 

 这里是用singal捕捉了14号信号

 从现象上来看,当调用alarm函数,在经过设置的时间后,会给进程发送14号信号。

总结思考

上面所说的所有信号产生,最终都要有OS来进行执行,为什么?

这是因为操作系统是计算机硬件和软件之间的中介,负责管理计算机的资源,并提供一个运行环境,使得程序能够在计算机上正确、高效地运行。 

 OS是进程的管理者 信号的处理是否是立即处理的?

信号的处理并不一定是立即的,它取决于信号的类型以及接收进程的状态 

在合适的时候 信号如果不是被立即处理,那么信号是否需要暂时被进程记录下来? 

是的,如果一个信号在适当的时候不能立即处理,操作系统通常会将信号暂时记录下来,以便稍后处理。这种记录信号的机制通常称为信号队列(Signal Queue)。

信号队列允许操作系统将接收到的信号排队,而不会丢失它们。每个进程都有一个相关联的信号队列,用于存储接收到的信号。当信号到达时,它会被添加到接收进程的信号队列中。接收进程可以随后检查队列中的信号并决定如何处理它们。

一个进程在没有收到信号的时候,能否能知道,自己应该对合法信号作何处理呢? 

一个进程可以预先定义信号的处理方式,无论是否收到信号。这就是通过设置信号处理程序(Signal Handler)来实现的。信号处理程序是一段特定的代码,它规定了进程在接收特定信号时应采取的操作。

进程可以使用操作系统提供的系统调用(如signal()sigaction()等,具体取决于操作系统和编程语言)来注册信号处理程序。一旦设置了信号处理程序,当进程接收到相应的信号时,操作系统将执行信号处理程序中定义的操作。

如何理解OS向进程发送信号?能否描述一下完整的发送处理过程?

操作系统(OS)可以向进程发送信号,这是一种进程间通信的机制,用于通知接收进程发生了某些事件或条件。下面是完整的发送信号的处理过程:

  1. 信号产生:信号的产生通常是由操作系统、其他进程或硬件事件引发的。信号可以表示不同的事件,如用户按下终止键(Ctrl+C),进程除零错误,或者定时器超时等。

  2. 信号的种类:每个信号都有一个唯一的标识符,如SIGTERM、SIGINT、SIGSEGV等,用来表示不同的事件类型。操作系统和程序员可以根据需要定义自定义信号。

  3. 信号发送:操作系统将信号发送给目标进程。这通常涉及到操作系统查找目标进程的进程标识符(PID)或进程组标识符(PGID),然后将信号发送给目标。

  4. 信号传递:操作系统将信号传递给目标进程。目标进程的执行可能会被中断,以便它可以处理接收到的信号。这意味着操作系统在进程执行期间会修改进程的执行流程,以引发信号处理。

  5. 信号处理程序执行:目标进程在接收到信号后,会查找它为该信号注册的信号处理程序。信号处理程序是一段用户定义的代码,用于定义在接收信号时应执行的操作。处理程序可以是默认的操作,用户自定义的操作,或者忽略信号。

  6. 信号处理:根据信号处理程序的定义,进程采取相应的操作。这可以包括终止进程、忽略信号、记录事件、执行自定义操作等。处理程序执行后,进程可以继续执行原来的任务。

  7. 信号传递结果:在信号处理程序执行后,进程可以向操作系统报告信号的处理结果,通常以退出状态码的形式。这可以帮助操作系统了解信号处理的成功与否,以及进程是否需要进一步处理。

总结来说,操作系统向进程发送信号的过程涉及信号的产生、发送、传递、处理,以及可能的反馈。这种机制使得操作系统和不同进程之间能够进行通信和协作,通常用于处理异常情况、用户交互、进程控制等。

 

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

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

相关文章

node教程(五)接口+会话

文章目录 一.接口1.1接口是什么?1.2接口的作用1.3接口的开发与调用1.4接口的组成 一.接口 1.1接口是什么? 接口是前后端通信的桥梁 1.2接口的作用 实现前后端通信 1.3接口的开发与调用 大多数接口都是由后端工程师开发的&#xff0c;开发语言不限 一般情况下接口都是由…

ts和js的区别?

文章目录 前言是什么&#xff1f;二、特性三、区别后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&#xff1a;Typescript &#x1f431;‍&#x1f453;博主在前端领域还有很多知识和技术需要掌握&#xff0c;正在不断努力填补技术短板。(如果出现…

树状数组(分析+代码)

在2023年4月29日的力扣103夜喵双周赛上&#xff0c;我被第四题所困扰&#xff0c;又于2023年5月4日早上的Linux系统基础课上&#xff0c;我初次接触到了树状数组。从那时候我就想写一篇博客记录一下&#xff0c;鸽到了现在… 参考视频 树状数组的作用 维护一个序列修改某一个…

yum

什么是yum? Linux中我们也要进行工具/指令/程序&#xff0c;安装&#xff0c;检查卸载等&#xff0c;需要yum的软件 安装软件的方式&#xff1a; 1.源代码安装--交叉编译工作 2.rpm包直接安装 3.yum / apt-get yum:yum是我们linux预装的一个指令&#xff0c;搜索&#x…

【C++深入浅出】STL之string用法详解

目录 一. 前言 二. STL概要 2.1 什么是STL 2.2 STL的六大组件 2.3 STL的缺陷 三. string类概述 3.1 什么是string类 3.2 为什么要使用string类 四. string类的使用 4.1 包含头文件 4.2 构造函数 4.3 赋值运算符重载 4.4 容量操作 4.5 访问/遍历操作 4.6 查找修改…

Redis之Java操作Redis的使用

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是君易--鑨&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的博客专栏《Redis实战开发》。&#x1f3af;&#x1f3af; …

07 点积

点积 基本运算几何解释投影运算和基本运算的联系多维空间到一维空间的投影 点积的作用 这是关于3Blue1Brown "线性代数的本质"的学习笔记。 基本运算 两个维数相同的向量 [ 2 , 7 , 1 ] T , [ 8 , 2 , 8 ] T [2, 7, 1]^{T},[8, 2, 8]^{T} [2,7,1]T,[8,2,8]T,求它们…

Spring Boot 整合RabbitMQ

系列文章目录 第一章 Java线程池技术应用 第二章 CountDownLatch和Semaphone的应用 第三章 Spring Cloud 简介 第四章 Spring Cloud Netflix 之 Eureka 第五章 Spring Cloud Netflix 之 Ribbon 第六章 Spring Cloud 之 OpenFeign 第七章 Spring Cloud 之 GateWay 第八章 Sprin…

【m98】abseil-cpp的cmake构建

m79的代码有些头文件没有,比如#include "absl/numeric/bits.h"使用m98版本里的代码,支持cmake构建cmake版本 WIN32 DEBUG configure Selecting Windows SDK version 10.0.22000.0 to target Windows 10.0.22621. The CXX compiler identification is MSVC 19.37.32…

第七章 Python常用函内置函数

系列文章目录 第一章 Python 基础知识 第二章 python 字符串处理 第三章 python 数据类型 第四章 python 运算符与流程控制 第五章 python 文件操作 第六章 python 函数 第七章 python 常用内建函数 第八章 python 类(面向对象编程) 第九章 python 异常处理 第十章 python 自定…

解决Visual Studio 2010 运行时屏幕一闪而过,无结果显示的问题

安装配置&#xff1a;Visual Studio 2010 软件安装教程&#xff08;附下载链接&#xff09;——计算机二级专用编程软件https://blog.csdn.net/W_Fe5/article/details/134218817?spm1001.2014.3001.5502 1、 我们在运行时会出现窗口一闪而过&#xff0c;这时候我们右键Test_1…

C++初阶-类和对象(中)2

类和对象&#xff08;中&#xff09;2 一、赋值运算符重载运算符重载赋值运算符重载前置和后置重载 二、日期类的实现三、const成员四、取地址及const取地址操作符重载 一、赋值运算符重载 运算符重载 C为了增强代码的可读性引入了运算符重载&#xff0c;运算符重载是具有特殊…

​软考-高级-信息系统项目管理师教程 第四版【第14章-项目沟通管理-思维导图】​

软考-高级-信息系统项目管理师教程 第四版【第14章-项目沟通管理-思维导图】 课本里章节里所有蓝色字体的思维导图

c语言练习100(贪吃蛇的实现)

贪吃蛇的实现 先实现主界面&#xff0c;后续将会不断完善。&#xff08;逐渐添加更多的功能&#xff09; test.c #define _CRT_SECURE_NO_WARNINGS #include"snake.h" void Test() {Snake snake { 0 };//创建贪吃蛇//1.游戏开始 - 初始化游戏GameStart(&sn…

类锁和实例对象锁你分清了吗?

系列文章目录 文章目录 系列文章目录前言一、什么是锁竞争&#xff1f;二、什么是类锁&#xff1f;什么是实例对象锁&#xff1f;三、给类对象加锁不是锁住了整个类四、总结 前言 java选手们应该都对锁不陌生&#xff0c;加锁了就是为保证操作语句的原子性&#xff0c;如果你是…

二AcW826. 单链表

#include<iostream>using namespace std;const int N100010;//head头结点下标//e[i]值//ne[i]下一个位置的地址//idx当前已经用到了哪个点int head, e[N],ne[N],idx;void init(){head-1;idx0; }void add_to_head(int x)//插到head{e[idx]x;ne[idx]head;//以前head指针是指…

多媒体应用设计师 2023年(含答案回忆版)

以下是小红书上的回忆版 软考考完疯狂回忆&#xff0c;多媒体应用设计师选择题 1.pattern 2.effective 3.merge 4.applications 5.graphic 6.udp 7.rtp 8.rtsp 9.10cm 10.永久 11…97 12.工作技术管理标准 13.管理型元数据 14.premiere 15.wave 16.500km/h 17.3M 18.44000 19.…

11.1~11.2数电实验一些点+11.4~11.5报错复盘

方框写在前面是说这个数有多大&#xff0c;写在后面是说这类数有多少 前面的用于计数&#xff0c;每位无实际意义&#xff1b;后面每位都代表一个同类型的&#xff0c;即数组&#xff0c;每位有实际意义 使用四位格雷码作为深度为8的FIFO的读写指针 将格雷码转换成四位二进制…

Amlogic IR模块Linux驱动分析

目录 一、简介 1、了解IR协议 2、代码结构介绍 二、硬件原理及连接 2、芯片手册解读 三、驱动代码分析 1、设备树介绍 1&#xff09;reg 2&#xff09;protocol 3&#xff09;pinctrl 4&#xff09;map 2、linux驱动介绍 1&#xff09;makefile 2&#xff09;数据…

【C++--string模拟实现】

一、基本思路 新建一个项目&#xff0c;在项目中创建头文件string.h 源文件string.cpp 在头文件中&#xff0c;先定义一个新的命名空间&#xff08;为了防止与库中的string发生冲突&#xff09;&#xff0c;命名空间的名字可以按照自己意愿来命名。 接下来就可以在命名空间中…