【Linux之拿捏信号1】什么是信号以及信号的产生

news2024/9/26 1:21:42

文章目录

  • 生活角度的信号
  • 技术应用角度的信号
    • 系统中的信号
    • 信号函数signal
    • 调用系统函数向进程发信号
    • 由软件条件产生信号
    • 由硬件异常产生信号
    • 总结


生活角度的信号

在我们的生活中,什么可以被称为信号呢?

那可太多啦,有红绿灯,闹钟,下课铃,倒计时,狼烟,冲锋号,肚子叫,眼色脸色,手势,外卖电话。。。。。。

联系实际,理解信号

大致从三个角度来讲,如图所示:
在这里插入图片描述

  1. 当我们看到红绿灯(信号)的时候,会有匹配的动作,红灯停,绿灯行。但是我们收到这个信号时,为什么会知道怎么做呢?那是因为曾经有人“教育”过我们,信号还没有产生,我们也知道如果收到信号该怎么做,也就是说我们具有“识别”信号的能力。推导得出——>进程就是我,信号是一个数字,进程在没有收到信号的时候,其实它早就知道一个信号该怎么被处理了!也就是说,程序员设计进程的时候,早就已经设计了对信号的识别能力
  2. 同时因为信号随时都有可能产生,所以在信号产生前,我可能正在做优先级更高的事情,我可能不能立马去处理这个信号!我们需要在后续合适的时间才能处理这个信号。比如大家可能都有这个经历,当你在房间打王者的时候,正推塔呢,突然外卖员给你打电话,说你的外卖到了,但是你要打完这把游戏再去拿外卖,在这个时间窗口内你脑子里面是记得等会游戏结束要去拿外卖,也就是说你保存了“拿外卖”这个信号。同理,信号产生——>时间窗口——>信号处理,进程收到信号的时候,如果没有立马处理这个信号(处在时间窗口),需要进程具有记录信号的能力
  3. 信号的产生对于进程来讲是异步的。(即外卖的到来,对你来讲是异步的,你不能确定外卖小哥具体什么时间点给你打电话)
  4. 当你时间合适时,顺利拿到你的外卖后,就要开始处理外卖了(处理信号),而处理外卖的一般方式有三种:(1)执行默认动作(开始幸福的吃起外卖);(2)忽略外卖(拿到外卖后,先晾在一遍,再打一局王者);(3)执行自定义动作(你突然想高歌一曲,跳支舞)。同理,进程在收到信号之后,处理信号有三种方式:(1)执行默认动作(处理信号);(2)忽略信号;(3)执行自定义操作。

技术应用角度的信号

系统中的信号

认知了上述形象生动的例子之后,我们知道若进程不及时处理信号,就需要记录保存对应产生的信号,那么到底将信号记录保存在哪里呢?

由于系统内部可能有多个进程,同时也有多个信号,对应的进程处理对应的信号,所以我们依然要采取先描述,再组织的方法,管理信号。那么怎么描述一个信号,用什么数据结构管理这个信号呢?

在linux中,我们可以通过命令kill -l可以察看系统定义的信号列表,如图所示。

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

在这里插入图片描述

那么其实马上就可以想到之前学过的数据结构——位图。没错,这31个信号就是采用位图结构就记录的,32个比特位,0,1表示信号是否被记录。所以task_struct内部必定要存在一个位图结构,用int表示:uint32_t signals;假设该进程收到了9号kill信号,那么该位图的第九个比特位就会由0置1,9号信号就被记录了,待该进程处理。

综上,所谓的发送信号,本质其实是向进程PCB写入信号,直接修改特定进程的信号位图中的特定比特位,0->1。task_struct 数据内核结构,只能由OS操作系统进行修改,所以无论后面我们有多少种信号产生的方式,最终都必须OS完成最后的发送过程!

信号函数signal

我们通过理解Ctrl+c,来学习一下signal信号函数

  1. 功能
    设置某一信号的对应动作
  2. 函数声明
#include <signal.h>
typedef void (*sighandler_t)(int);//函数指针
sighandler_t signal(int signum, sighandler_t handler);
  1. 参数说明 
    第一个参数signum指明了所要处理的信号类型(信号编号),它可以取除了SIGKILL和SIGSTOP外的任何一种信号。  
    第二个参数handler描述了与信号关联的动作,它可以取以下三种值:
    (1)SIG_IGN  :这个符号表示忽略该信号
    (2)SIG_DFL  :这个符号表示恢复对信号的系统默认处理。不写此处理函数默认也是执行系统默认操作。
    (3)sighandler_t类型的函数指针,当接收到一个类型为sig的信号时,就执行handler 所指定的函数。

Ctrl+c?

  • 用户输入命令,在Shell下启动一个前台进程。用户按Ctrl+c,这个键盘产生一个硬件中断,被OS获取,解释成信号,发送给目标进程,前台进程因为收到信号,进而引起进程退出。
  • 所以Ctrl+c的本质是是向前台进程发送对应的信号。那么如何证明这一点呢?我们来看如下代码:

代码:

  1 #include<iostream>
  2 #include<signal.h>
  3 #include<unistd.h>
  4 void handler(int signo)//自定义方法
  5 {
  6     std::cout<<"get a signal:"<< signo <<std::endl;
  7 }
  8 int main()
  9 {
 10     signal(2,handler);
 11     while(true)
 12     {
 13         std::cout<<"我是一个进程,我正在运行......,我的pid是:"<<getpid()<<std::endl;                                                                                             
 14         sleep(1);
 15     }
 16     return 0;
 17 }

我们在键盘按下 Ctrl+c会终止进程,实际是因为键盘产生硬件中断(一个硬件对应一个中断)后,OS获取并解析成了2号信号,在OS中2号信号对应的动作就是结束进程。而上述代码,我们用signal函数改变了2号信号对应的动作,所以2号信号的动作由结束进程——>打印get a signal:2。所以我们再将程序跑起来,当我们在键盘上桥下Ctrl+c,这个进程就不会被终止,而是打印。。。看图吧
在这里插入图片描述
综上:

  1. 2号信号:进程的默认处理动作是终止进程
  2. signal方法:可以进行对指定的信号设定自定义处理动作

注意:signal(2,handler)调用完这个函数的时候,handler方法并没有被调用,只是更改了2号信号和对应handler方法的映射关系,并没有调动handler函数,例如以下的代码show函数并没有调用Print函数,只是接收了这个函数的参数,但没有进行回调handler函数。当2号信号产生的时候,handler函数才会被调用,执行自定义捕捉

void Print()
{
	printf("hello world\n");
}
void show(int a,func_t f)
{
	printf("hello show\n");
}

int main()
{
	show(10,Print);
	return 0;
}

PS:前台进程与后台进程

  • ./进程名——运行起来的是前台进程,./进程名 &——运行起来的是后台进程,只能kill-9干掉后台进程。
  • Ctrl+c产生的信号只能发送给前台进程。如果是后台进程,则程序可以放到后台运行,即不能干掉后台进程,这样shell就不必等待进程结束,就可以接受新的命令,启动新的进程。
  • 前台进程在运行过程中用户随时可能按下Ctrl+c而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到SIGINT信号而终止,所以信号相对于进程的控制流程来说是异步(Asynchronous)的

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

当我们要使用kill命令向一个进程发送信号时,我们可以用kill -信号名(信号编号) 进程ID的形式进行发送。

kill函数

实际上kill命令是通过调用kill函数实现的,kill函数可以给指定的进程发送指定的信号,kill函数的函数原型如下:

int kill(pid_t pid, int sig);

kill函数用于向进程ID为pid的进程发送sig号信号,如果信号发送成功,则返回0,否则返回-1。
我们可以用kill函数模拟实现一个kill命令,实现逻辑如下:

//测试代码loop.cc
                                                    
  1 #include<iostream>
  2 #include<unistd.h>
  3 
  4 int main()
  5 {
  6     while(true)
  7     {
  8         std::cout<<"我是一个进程。。。pid:"<<getpid()<<std::endl;
  9         sleep(2);                                                                    
 10     }
 11 }
  1 #include<iostream>
  2 #include<cstdlib>
  3 #include<string>
  4 #include<unistd.h>
  5 #include<signal.h>
  6 #include<sys/types.h>
  7 #include<cstring>
  8 #include<cerrno>
  9 #include<cassert>
 10 
 11 void Usage(std::string proc )
 12 {
 13     printf("\tUsage\n\t");
 14     std::cout << proc << "信号编号 目标进程" << std::endl;
 15 }
 16 int main(int argc,char* argv[])
 17 {
 18     if(argc!=3)
 19     {
 20         Usage(argv[0]);
 21         exit(1);
 22     }
 23     int signo = atoi(argv[1]);
 24     int target_id = atoi(argv[2]);                                                   
 25     int n = kill(target_id,signo);
 26     if(n!=0)
 27     {
 28         std::cerr << errno <<":"<<strerror(errno) << std::endl;
 29     }
 30 
 31 }
//makefile文件
  1 .PHONY:all
  2 all:mykill loop
  3 
  4 loop:loop.cc
  5     g++ -o $@ $^ -std=c++11
  6 
  7 mykill:mykill.cc
  8     g++ -o $@ $^ -std=c++11
  9 .PHONY:clean
 10 clean:
 11     rm -f mykill loop                                                                

在这里插入图片描述

raise函数

raise函数可以给当前进程发送指定信号,即自己给自己发送信号,raise函数的函数原型如下:

int raise(int sig);

raise函数用于给当前进程发送sig号信号,如果信号发送成功,则返回0,否则返回一个非零值。

例如,下列代码当中用raise函数每隔一秒向自己发送一个2号信号。

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

void handler(int signo)
{
	printf("get a signal:%d\n", signo);
}
int main()
{
	signal(2, handler);
	while (1){
		sleep(1);
		raise(2);
	}
	return 0;
}

在这里插入图片描述

abort函数

raise函数可以给当前进程发送SIGABRT信号,使得当前进程异常终止,abort函数的函数原型如下:

void abort(void);

abort函数是一个无参数无返回值的函数。

例如,下列代码当中每隔一秒向当前进程发送一个SIGABRT信号。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

void handler(int signo)
{
	printf("get a signal:%d\n", signo);
}
int main()
{
	std::cout << "begin " << std::endl;
	sleep(1);
	abort();//给自己发送指定信号
	std::cout << "end " << std::endl;
	
	return 0;
}

运行结果:只打印出来begin,没打印end,因为执行到abort函数时直接终止了进程。
在这里插入图片描述
也可以这样写:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

void handler(int signo)
{
	printf("get a signal:%d\n", signo);
}
int main()
{
	signal(SIGABRT,handler);

	while(true)
	{
		std::cout << "begin " << std::endl;
		sleep(1);
		abort();//给自己发送指定信号
		std::cout << "end " << std::endl;
	}
	
	
	return 0;
}

但是上述代码并未产生循环,打印完get a signal之后,就aborted退出了,总之,abort可以被自定义捕捉,但是“我”依然要终止进程。

由软件条件产生信号

alarm函数 和SIGALRM信号

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

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

我们先来看一段代码:
在这里插入图片描述
上面这段代码是用以验证算力的,算力结果如下:
在这里插入图片描述

但是为什么算力这么小呢?原因是因为有IO(需要打印到显示器),网络的约数限制,所以说IO的效率其实非常低。

那我们让算力安心计算,1s到了,就调用自定义函数对其捕捉。
在这里插入图片描述

此次运行结果:
在这里插入图片描述

alarm函数的自举:自己调用自己

在这里插入图片描述
运行结果:
在这里插入图片描述

由硬件异常产生信号

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

除0错误的本质,就是就是触发CPU硬件异常,如将CPU的某个状态寄存器置1,代表本次计算有溢出问题,然后OS找到发生错误的进程,向该进程的PCB内写入对应的信号,使该进程异常退出。

在这里插入图片描述

运行结果:我们的进程确实是收到了:8信号导致崩溃的

对空指针的解引用问题:导致MMU内存管理单元出现硬件异常问题。

在这里插入图片描述
在这里插入图片描述

总结

信号的产生:

  • 键盘
  • 系统调用
  • 指令
  • 软件条件
  • 硬件异常

都是借助OS之手向目标进程发送信号,即向目标进程pcb写信号位图。

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

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

相关文章

通过NFS与Windows共享文件

通过NFS与Windows共享文件 1. 连接同一wifi网络 将开发板子连接至和PC同网络&#xff0c;并保证能互相ping通 2.启动Windows 端NFS Server 在电脑端启动NFS server&#xff08;下载链接&#xff1a; https://www.hanewin.net/nfs-e.htm &#xff09; 3. 双击nfssrv-64.exe…

轻创数字人集团一文论述如何助力中小型企业数字化转型 ?

随着互联网技术的不断发展&#xff0c;人工智能已经成为了许多企业数字化转型的重要工具。然而&#xff0c;在人工智能领域中&#xff0c;不乏一些大型公司在研发和应用方面占据着主导地位。例如&#xff0c;以基础技术和软硬件设施为主的上游层&#xff0c;以英伟达、Meta、Ep…

基于 DDR3 的native接口串口传图帧缓存系统设计实现(整体设计)

文章目录 前言一、串口传图顶层系统设计框图二、各模块说明三、uart_ddr3_native_tft四、 uart_ddr3_native_tft仿真模块五、fifo_ddr3_native_fifo 模块六、fifo_ddr3_native_fifo 仿真模块七、传图展示 前言 结合串口接收模块和 tft 显示屏控制模块&#xff0c;设计一个基于…

sklearn房价预测(随机森林)

文章目录 一、前言二、实现方法一、前言 任务目标:根据统计在csv中的房屋属性相关数据,预测房屋最终成交价格数据集:《住宅属性数据集》,自取https://download.csdn.net/download/weixin_43721000/87785277数据集字段解释: 这个文件中有字段详细说明↓ 二、实现方法 # pi…

ARM_key1按键按下,打印key1

include/key.h #ifndef __KEY_H__ #define __KEY_H__#include "stm32mp1xx_exti.h" #include "stm32mp1xx_gic.h" #include "stm32mp1xx_rcc.h" #include "stm32mp1xx_gpio.h"//对RCC/GPIO/EXTI进行初始化 void hal_key1_exit_init()…

路径规划算法:基于类电磁机制优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于类电磁机制优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于类电磁机制优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能…

面试交流会

面试交流 目录&#xff1a; 01&#xff1a;关于人生目标 02&#xff1a;关于心态 03&#xff1a;关于选择 04&#xff1a;关于学习 05&#xff1a;关于简历 06&#xff1a;关于找工作 1&#xff1a;关于人生目标 1.01、自己想成为什么样的人&#xff1f; 1.02、自己的人生目标是…

sqoop 详解

1 sqoop原理 1.1 sqoop介绍# Sqoop是Apache旗下的一款“hadoop和关系型数据库服务器之间传送数据”的工具。   导入数据&#xff1a;MySQL、Oracle导入数据到hadoop的hdfs、hive、hbase等数据存储系统。   导出数据&#xff1a;从hadoop的文件系统中导出数据到关系型数据…

Mysql数据库——表操作的练习

题目一 &#xff08;1&#xff09; mysql> create database Market; &#xff08;2&#xff09; mysql> use Market; Database changed mysql> create table customers(->c_num INT(11) primary key auto_increment,->c_name varchar(50),->c_contact varcha…

chatGPT之100个例子-从体验到精通

简介 本博文演示了100个chatGPT在各行各业的使用例子,全部看完有助于培养chatGPT解决问题的思维。 在人工智能时代,智能软件并不会淘汰人类,淘汰人类的是会使用人工智能的人! 我们直接使用openAI官方chatGPT,生动演示了chatGPT的一些妙用! 请仔细看完,一定会有收获! 每…

Python 之 基本概述

这里写目录标题 一、Python 简介1. 历史背景2. 特点3. 优缺点4. 应用领域 二、Python 解释器1. 解释器是什么&#xff1f;2. 解释器的构成及其各部分功能3. 解释器的执行过程 三、Python 环境安装四、第一个 Python 程序1. 实际操作2. 常见问题 五、Python 开发环境 一、Python…

Ubuntu 22.04.2 LTS 安装搜狗输入法后,修改区域格式Regional Format crash 崩溃 ,改用bash 指令修改

故障 系统已经升级到最新 基于Ubuntu 20.04 LTS apt upgrade升级而来。 yeqiangyeqiang-MS-7B23:~$ cat /etc/os-release PRETTY_NAME"Ubuntu 22.04.2 LTS" NAME"Ubuntu" VERSION_ID"22.04" VERSION"22.04.2 LTS (Jammy Jellyfish)"…

【LangChain】Document篇

概述 这些是处理文档的核心链。它们对于总结文档、回答文档问题、从文档中提取信息等很有用。 这些链都实现了一个通用接口&#xff1a; class BaseCombineDocumentsChain(Chain, ABC):"""Base interface for chains combining documents."""a…

Vue:Elemenu-Plus递归型菜单组件封装

前端开发中&#xff0c;经常遇到需要与后端配置&#xff0c;前端动态渲染菜单的应用场景&#xff0c;而究其本质&#xff0c;就是菜单组件的应用&#xff0c;只是在不确定菜单级数的情况下&#xff0c;我们需要对组件做一个递归处理&#xff0c;让它能够适应大多数应用场景。 递…

9.17UEC++代码段、编码和字符串

1. 编码定义&#xff1a; 自行转码&#xff1a; 字符串&#xff1a; FName&#xff1a;名称&#xff0c;访问快&#xff0c;用FName做键值。&#xff08;键值容器&#xff09;资产名称基本都是FName。 FText&#xff1a;一般是和UI有关&#xff0c;专门对接UI&#xff0c;也是…

IDEA动态调试WebLogic

IDEA动态调试WebLogic 环境&#xff1a;Windows 10 Windows7(192.168.52.181) Idea WebLogic12.2.1.4 Java8102 0x01 安装weblogic 安装成功后&#xff0c;在domains下的bin目录下有个startWebLogic.cmd文件 0x02 配置被调试端 0x0201 添加调试参数 2.1.1 方式一 在…

基于单片机智能衣柜 智能衣橱 换气除湿制系统紫外线消毒的设计与实现

功能介绍 以51单片机作为主控系统&#xff1b;液晶显示当前衣柜温湿度和柜门开启关闭状态&#xff1b;按键设置当前衣柜湿度上限值、衣柜门打开和关闭&#xff0c;杀菌消毒&#xff1b;当湿度超过设置上限&#xff0c;继电器闭合开启风扇进行除湿&#xff1b;进行杀菌消毒时&am…

7.5 cloneGitHub项目到服务器端/wget/print/spikes_max

在机器学习和深度学习中&#xff0c;位量化&#xff08;Bit Quantization&#xff09;是一种将模型参数或激活值表示为较低精度的二进制数的技术。通常情况下&#xff0c;模型的参数和激活值是以浮点数形式存储和计算的&#xff0c;占用较大的存储空间和计算资源。位量化通过减…

找不到vcruntime140.dll无法继续执行代码的解决方法

计算机提示找不到vcruntime140.dll无法继续执行代码是什么原因呢&#xff1f;在我们运行photoshop&#xff0c;数据库的时候&#xff0c;电脑提示找不到vcruntime140.dll是由于电脑系统中的Microsoft Visual C Redistributable包损坏&#xff0c;vcruntime140.dll是Microsoft V…

(8)基础强化:内存流,压缩流,序列化,资料管理器,编码,File类,文件流,文本流

一、内存流 1、为什么要有内存流&#xff1f; 答&#xff1a;内存流(MemoryStream)是一个特殊的流&#xff0c;它将数据存储在内存中而不是磁盘或网络。 使用内存流的主要原因和使用场景包括&#xff1a; 内存操作&#xff1a; …