linux信号相关概念

signal

  • 信号引入
  • 什么是信号?
  • 如何产生信号?
    • 通过按键产生信号
    • 调用系统函数向进程发信号
      • 系统调用函数发送信号的流程:
    • 由软件条件产生信号
      • 软件发送信号的流程:
    • 硬件异常产生信号
      • 硬件异常的流程:
  • Deliver、Pending、Block概念
    • 信号在内核表示示意图
    • sigset_t
    • 信号集操作函数
    • 注意
  • 信号捕捉
    • 捕捉信号的时机:
  • 可重入函数
  • volatile

信号引入

我们在linux编写代码时,如果想提前结束一个进程,通常我们会按ctrl+c组合键:
在这里插入图片描述
其实这就是想OS传递了一个中断进程的信号,我们平常就有意无意的在使用它!

  • 如何理解组合键变成信号呢?‘
    OS解释组合键->查找进程列表->前台运行的进程->OS写入对应的信号到进程内部的位图结构中(OS直接修改进程PCB的位图结构)。

什么是信号?

  • 信号是进程之间事件异步通知的一种方式,属于软中断。(进程无论怎么运行,我们都能使用信号来通知他们执行动作)。

  • 使用kill -l 命令,可查看linux下的信号。(1~31是常用的信号,34~64是实时信号)
    在这里插入图片描述

  • 每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到,如下图: 在这里插入图片描述

  • 对于信号,一般有三种处理函数:忽略此信号、执行该信号的默认处理动作、修改信号的默认处理动作(本来在OS内核执行的默认动作,被切换到用户态执行用户自己的函数,这种方式被称为Catch一个信号)。

如何产生信号?

我们先认识一个,可以修改信号处理动作的函数:signal()
在这里插入图片描述

第一个参数是信号,可填宏定义可填数字。
第二个参数是回调函数,用于定义用户想要执行的动作。

#include<iostream>
#include <unistd.h>
#include<signal.h>
using namespace std;

void catchSignal(int signum)
{
    cout<<"我收到了一个信号,正在处理:"<<signal<<" Pid:"<<getpid()<<endl;
}


int main()
{
    int i = 0;
    signal(SIGINT, catchSignal);
    while(1)
    {
        sleep(1);
        cout<<"这是一个死循环"<<++i<<endl;
    }

    return 0;
}

该例子我们修改了SIGINT的默认处理动作,所以当我们按Crtl+C的时候,进程并没有中止,而是执行了自己定义的函数。
在这里插入图片描述

通过按键产生信号

SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且Core Dump。

  • 什么是Core Dump?
    当一个进程要异常终止时,可以选择把进程的用户控件内存数据全部保存到磁盘上,文件名通常是core,这就叫Core Dump。然后事后可以检查core文件,查看错误原因,这叫Post-mortem Debug(事后调试)。一个进程允许产生多大的core文件取决于进程的Resource Limit(这个信息保存在PCB中)。默认不允许产生core文件,因为core可能包含密码等敏感信息,不安全。在开发调试阶段可以用ulimit命令改变这个限制。

ulimit -c 1024
修改shell进程的Resource Limit,允许core文件最大为1024K
ulimit -a
查看相关信息

在这里插入图片描述
在这里插入图片描述
只要动作是action的都会产生core文件:
在这里插入图片描述

下面演示如何产生core 文件:设置了一段除0的代码

int main()
{
    int i = 10;

    while(1)
    {
        sleep(1);
        i/=0;
    }
    
    return 0;
}

在这里插入图片描述

如何使用core文件呢?使用gdb调试命令:
这样就可以直接根据core文件,定位出错误的地方了。

在这里插入图片描述

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

我们平常使用的kill命令,其实就是调用的系统kill函数:
在这里插入图片描述
这个函数的功能就是给指定的进程发送信号:
在这里插入图片描述


raise函数:
自己给自己发信号,成功返回0,错误返回-1;

int main()
{
    int i = 10;

    while(1)
    {
        sleep(1);
        raise(SIGSEGV);  //段错误信号
    }
    
    return 0;
}

在这里插入图片描述


abort函数:

void abort(void)
使当前进程接收到信号而终止;

int main()
{
    int i = 10;

    while(1)
    {
        sleep(1);
        abort();   //什么也不填,相当与exit
    }
    
    return 0;
}

在这里插入图片描述

系统调用函数发送信号的流程:

用户调用系统调用接口->执行OS对用的系统调用代码->OS提取参数,或者设置特定数值->OS向目标进程写信号->修改对应进程的信号标记位(PCB里)->进程后续执行对应的处理动作

由软件条件产生信号

在这里插入图片描述
该函数可以理解为设置一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号,该信号的默认处理动作是终止当前进程(也可以通过修改默认处理动作,去完成用户的需求)

软件发送信号的流程:

OS先识别到某种软件条件触发或者不满足->OS构建信号,发送给指定的进程。

硬件异常产生信号

CPU除0错误,访问非法内存地址,都是硬件异常。

硬件异常的流程:

除0错误:
1.CPU在计算时,发现状态寄存器的溢出标记位是1->OS系统识别出有溢出问题,立即找到谁在运行这个程序->OS给这个进程发送信号,进程会在合适的时候,进行处理。
2. 出现硬件异常,进程一定会退出吗?不一定!默认是退出,但是我们即使不退出,也做不了什么。
3. 为什么会死循环?如果你把除0的默认动作改了之后,溢出标志位就一直是1(没有人改它),所以会一直执行你改正的动作。

指针越界问题:
4. 指针必须通过地址找到目标位置
5. 而语言层面的地址,是虚拟地址
6. 将虚拟地址转化成物理地址需要(页表+MMU内存管理单元)
7. 如果是野指针,越界->非法地址->MMU转化的时候,OS一定会报错!

Deliver、Pending、Block概念

  • 信号递达(Deliver):执行信号的处理动作
  • 信号未决(Pending):还没有响应的信号
  • 阻塞(Block):阻塞某个信号
  • 阻塞和忽略是不同的,阻塞是未处理,忽略是处理动作是忽略。
    被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。

信号在内核表示示意图

在这里插入图片描述

信号产生会修改PCB(pcb有指向信号数据结构的指针),Block、pending都是位图结构,handler是信号相应的动作

  • block表示的位图为是否阻塞该信号、pending表示接收到该信号。
  • 信号处理流程:OS->pending->block(如果被阻塞了就不处理呢)否则就进入handler处理。

sigset_t

该类型是系统的位图变量(用来描述上面的位图结构),并且OS提供了对它的操作函数。

信号集操作函数

#include<signal.h>
//set是信号集[]
int sigemptyset(sigset_t *set);   //把set都置为0
int sigfillset(sigset_t *set);     //把set都置为1
int sigaddset(sigset_t *set, int signo);      //把signo 数字的信号 置为1
int sigdelset(sigset_t * set, int signo);  //删除signo ,位图置为0
int sigismember(sigset_t *set, int signo);  //该信号集有效信号是否有signo,有就返回1,没有返回0,出错返回-1;

前四个函数成功返回0,出错返回-1;


功能:读取或更改进程的信号屏蔽字(阻塞信号集)
int sigprocmask (int how, const sigset_t *set, sigset_t *oset);
返回值:成功返回0,出错返回-1.

如果oset是非空,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空,则更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都非空,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。

  • SIG_BLOCK:set包含了希望添加到当前信号屏蔽字的信号,相当于mask = mask|set
  • SIG_UNBLOCK:set包含希望解除阻塞的信号,相当于mask = mask&~set
  • SIG_SETMASK:设置当前信号屏蔽字为set指向的值, 相当于mask = set

如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达


读取当前进程pending位图信息,通过set传出
int sigpending(sigset_t *set);
调用成功返回0,调用失败返回-1

注意

如果我们对所有信号都进行block,是不是就可以写出一个无法杀死的进程了?
不对,比如说9号信号是无法被屏蔽的!

信号捕捉

捕捉信号的时机:

在这里插入图片描述

解释:因为信号相关字段在PCB中,所以信号的检测是一定会在内核态进行。主程序遇到异常后,OS要转到内核态处理异常,当处理完准备返回用户态的时候,此时,进行信号的处理,检测信号是否被屏蔽,未屏蔽再中断,回到用户态,执行信号处理函数,然后再返回内核态,接着被中断的位置继续返回用户态,执行函数。


signal.h
功能:读取和修改与指定信号相关联的处理动作(更高级的signal)
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);

  • signo:指定的信号编号
  • 若act非空,则根据act修改该信号的处理动作
  • 若oact非空,则将原来的信号处理动作,保留在此。
  • 成功返回0,出错返回-1

其中该结构体我们只关心画圈圈的两个参数,其他不用管。
在这里插入图片描述

下面的例子屏蔽了2号信号。并获得了2号信号的默认动作。


//makefile
mytext:mytext.cc
	g++ -o $@ $^ -std=c++11 -fpermissive
.PHONY:clean
clean:
	rm -f mytext

//ytext.cc
#include<iostream>
#include<signal.h>
#include <unistd.h>
using namespace std;

void handler(int signum)
{
    cout<<"处理信号:"<<signum<<endl;
}


int main()
{
    // cout<<"hello world"<<endl;


    //内核从数据类型,用户栈定义
    struct sigaction act, oact;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    act.sa_handler = handler;

    //设置到当前进程的PCB中
    sigaction(2, &act,&oact);

    cout<<"默认处理动作oact:"<<(int)(oact.sa_handler)<<endl;

    while(1) sleep(1);
    return 0;
    
}

可重入函数

简单理解说,就是多个进程可同时进入的函数,并且多次运行的结果唯一,此函数就是可重入函数。反之如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant)函数。

如果一个函数符合以下条件之一则是不可重入的:

  • 调用了malloc或free,因为malloc也是用全局链表管理堆的。
  • 调用了标准IO库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

volatile

volatile 作用:保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量的任何操作,都必须在真实的内存中进行操作。
我们有如下代码逻辑,全局flag,当收到信号时,全局flag变成1,进程结束。

int flag = 0;
void handler(int sig)
{
    printf("change flag 0 to 1\n");
    flag = 1;
}

int main()
{
    signal(2, handler);
    while(!flag);
    printf("process quiit normal\n");
    return 0;
}


//makefile
mytext:mytext.cc
	g++ -o $@ $^ -std=c++11 
.PHONY:clean
clean:
	rm -f mytext

但是当我们加上O2优化的时候,此时进程就不退出了!
在这里插入图片描述
这是为什么呢?(因为编译器把代码优化了)
优化情况下,键入 CTRL-C,2号信号被捕捉,执行自定义动作,修改 flag=1,但是 while 条件依旧满足,进程继续运行!但是很明显flag肯定已经被修改了,但是为何循环依旧执行?很明显,while循环检查的flag并不是内存中最新的flag,这就存在了数据二异性的问题。while 检测的flag其实已经因为优化,被放在了CPU寄存器当中。如何解决呢?需要 volatile。

volatile int flag = 0;   //volatile 防止编译器优化!
void handler(int sig)
{
    printf("change flag 0 to 1\n");
    flag = 1;
}

int main()
{
    signal(2, handler);
    while(!flag);
    printf("process quiit normal\n");
    return 0;
}

加了volatile后,问题就被解决了。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Ne4j图数据库入门笔记1】图形数据建模初识

1.1 图形建模指南 图形数据建模是用户将任意域描述为节点的连接图以及与属性和标签关系的过程。Neo4j 图数据模型旨在以 Cypher 查询的形式回答问题&#xff0c;并通过组织图数据库的数据结构来解决业务和技术问题。 1.1.1 图形数据模型介绍 图形数据模型通常被称为对白板友…

明文scheme拉起此小程序

微信开发文档说明&#xff1a;https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/url-scheme.html 1、开发者无需调用平台接口&#xff0c;在MP平台->设置->隐私与安全->明文Scheme拉起此小程序声明后&#xff0c;可自行根据如下格式拼接app…

【静态分析】软件分析课程实验A1-活跃变量分析和迭代求解器

1 作业导览 为 Java 实现一个活跃变量分析&#xff08;Live Variable Analysis&#xff09;。实现一个通用的迭代求解器&#xff08;Iterative Solver&#xff09;&#xff0c;用于求解数据流分析问题&#xff0c;也就是本次作业中的活跃变量分析。 Live Variable Analysis 详…

facenet人脸检测+人脸识别+性别识别+表情识别+年龄识别的C++部署

文章目录 一. 人脸检测二.人脸识别facenet2.1 训练人脸识别模型2.2 导出ONNX2.3 测试 三.人脸属性&#xff08;性别、年龄、表情、是否戴口罩&#xff09;3.1 训练3.2 导出ONNX3.3 测试 四. 集成应用五、Jetson 部署5.1 NX5.2 NANO 一. 人脸检测 代码位置&#xff1a;1.detect …

深入理解数据结构第五弹——排序(2)——快速排序

排序&#xff08;1&#xff09;&#xff1a;深入了解数据结构第四弹——排序&#xff08;1&#xff09;——插入排序和希尔排序-CSDN博客 前言&#xff1a; 在前面我们已经讲过了几种排序方式&#xff0c;他们的效率有快有慢&#xff0c;今天我们来学习一种非常高效的排序方式…

【windows-搭建Ubuntu22LTS】

一、环境要求 1. windows版本要求 至少Windows 10 2020年5月(2004) 版, Windows 10 2019年5月(1903) 版&#xff0c;或者 Windows 10 2019年11月(1909) 版 2. 控制面板开启相关的程序(需要重启) 二、Microsoft store安装unbuntu 下载后直接运行&#xff08;稍微等会&#…

Linux软件安装和部署Java代码

文章目录 1.软件安装1.1.软件安装方式1.2.常用软件安装1.2.1 安装jdk1.2.2 安装Tomcat1.2.3 安装MySQL1.2.4 安装lrzsz 2.项目部署2.1.手工部署项目2.2 通过Shell脚本自动部署项目 1.软件安装 1.1.软件安装方式 &#xff08;1&#xff09;二进制发布包安装&#xff1a; 软件已…

基于SSM的学校在线考试系统的设计与实现

功能需求 管理员模块 管理员模块是整个学校在线考试系统中最为重要的管理者&#xff0c;能够对网站内的各种信息进行管理&#xff0c;能够对教师、学生的个人资料进行管理&#xff0c;对于已经离校的学生将其剔除考试名单&#xff0c;将新入校的学生纳入到考试名单中。对于入…

用 element ui 实现季度选择器

由于在数据项目中经常以各种时间条件查询数据&#xff0c;所以时间选择器&#xff08;DatePicker&#xff09;组件是很常用的组件。但是在我使用的 Element UI 中&#xff0c;缺少了季度选择器的功能。 简易实现 一开始我根据时间范围使用 select 去遍历,如 2024-Q1、2023-Q4…

win/mac达芬奇19下载:DaVinci Resolve Studio 19

DaVinci Resolve Studio 19 是一款功能强大的视频编辑和调色软件&#xff0c;广泛应用于电影、电视和网络节目的后期制作。这款软件不仅提供了专业的剪辑、调色和音频处理工具&#xff0c;还引入了全新的DaVinci Neural Engine AI工具&#xff0c;对100多项功能进行了大规模升级…

美化博客文章(持续更新)

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;游戏实现&#xff1a;贪吃蛇​​​​​​ &#x1f337;追光的人&#xff0c;终会万丈光芒 前言&#xff1a; 该文提供我的一些文章设计的一些方法 目录 1.应用超链接 1.应用超链接

差速机器人模型LQR 控制仿真——路径模拟

LQR路径跟踪要求路径中带角度&#xff0c;即坐标&#xff08;x,y,yaw&#xff09;&#xff0c;而一般我们的规划出来的路径不带角度。这里通过总结相关方法&#xff0c;并提供一个案例。 将点路径拟合成一条完整的线路径算法 将点路径拟合成一条完整的线路径是一个常见的问题…

【Java开发指南 | 第十五篇】Java Character 类、String 类

读者可订阅专栏&#xff1a;Java开发指南 |【CSDN秋说】 文章目录 Java Character 类转义序列 Java String 类连接字符串 Java Character 类 Character 类是 Java 中用来表示字符的包装类&#xff0c;它提供了一系列静态方法用于对字符进行操作&#xff0c;其主要分为静态方法…

06 JavaScript学习:语句

JavaScript 语句是用来执行特定任务或操作的一组指令。它可以包括变量声明、条件语句、循环语句、函数调用等。JavaScript 语句以分号结尾&#xff0c;每个语句都会被解释器执行。 分号 ; 在JavaScript中&#xff0c;分号&#xff08;;&#xff09;用于表示语句的结束。尽管在…

python爬虫-----深入了解 requests 库(第二十五天)

&#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; &#x1f388;&#x1f388;所属专栏&#xff1a;python爬虫学习&#x1f388;&#x1f388; ✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天…

【汇编语言】初识汇编

【汇编语言】初识汇编 文章目录 【汇编语言】初识汇编前言由机器语言到汇编语言机器语言与机器指令汇编语言与汇编指令汇编语言程序示例 计算机组成指令和数据的表示计算机的存储单元计算机的总线 内存读写与地址空间CPU对存储器的读写内存地址空间 总结 前言 为什么要学习汇编…

Numpy重修系列(一) --- 初识Numpy

一、为什么使用Numpy&#xff1f; 1.1、简介 Python科学计算基础包&#xff0c;提供 多维数组对象 、派生对象&#xff08;掩码数组、矩阵&#xff09; 数组的快速操作&#xff08;数学计算、逻辑、形状变化、排序、选择、输入输出、离散傅里叶变换、基本线性代数、基本统计运…

数据分析案例-中国黄金股票市场的EDA与价格预测

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

【数据结构】单链表经典算法题的巧妙解题思路

目录 题目 1.移除链表元素 2.反转链表 3.链表的中间节点 4.合并两个有序链表 5.环形链表的约瑟夫问题 解析 题目1&#xff1a;创建新链表 题目2&#xff1a;巧用三个指针 题目3&#xff1a;快慢指针 题目4&#xff1a;哨兵位节点 题目5&#xff1a;环形链表 介绍完了…

Activity——spring方式创建activiti所需数据表结构

文章目录 前言依赖引入编写数据库连接等配置配置日志文件编写java代码生成数据库表结构问题反馈与解决思路问题一&#xff1a;Cause: java.sql.SQLSyntaxErrorException: Table activiti_02.act_ge_property doesnt exist 为什么文件名必须写死&#xff1f; 前言 在之前创建ac…