Linux学习之信号

目录

1.信号的概念

2.信号的产生

3.信号的保存

4.信号的捕捉

信号的其它内容:

SIGCHLD信号


1.信号的概念

在Linux中,信号是一种用于进程之间通信的基本机制。它是一种异步事件通知,用于通知进程发生了某些事件。如下是一些常见的Linux信号类型:

SIGINT (2):中断进程,通常由终端产生,例如用户按下Ctrl+C。
SIGKILL (9):立即终止进程,无法被捕获或忽略。
SIGTERM (15):请求终止进程,可以被捕获或忽略。
SIGQUIT (3):请求进程退出并生成核心转储文件,可以被捕获或忽略。
SIGSTOP (17):暂停进程的执行,无法被捕获或忽略。
SIGCONT (19):恢复进程的执行,无法被捕获或忽略

 这些信号在进程控制、异常处理和进程间通信中扮演着重要角色。请注意,信号只是通知进程发生了什么事件,并不传递任何数据。进程对不同信号有不同的处理方式,可以指定处理函数、忽略或保留系统的默认值。信号机制在Linux编程中非常重要,帮助实现进程之间的协作和控制。

2.信号的产生

先举两个样例:

eg1:

首先我们编写一个死循环代码,编译运行后,我们的命令行就不再有用了,现在是前台程序,只运行当前的程序,当我们编译时加上&,使他成为后台程序,此时的命令行也可以继续使用,

程序在运行的时候,前台程序只能有一个,后台程序可以有多个。后台程序在运行时,我们的键盘可以输入数据,指令可以运行。

一般操作系统会自动根据情况把shell程序提到前台或者后台。下面的指令对shell无效。

前后台程序切换

./可执行 &  把程序放到后台

jobs  查看后台任务

fg number(任务编号) 把任务放到前台

ctrl+z 再加 bg number   把后台任务转到前台

ctrl+\ 默认终止

ctrl + z 暂停程序,先放到后台

而这就是信号的产生,除此之外操作系统知晓键盘的输入也是一种信号:

eg2:当键盘的某个按钮被按下的时候,就会产生高电平信号间接给cpu,cpu得知了之后某个按钮的高电平,发生中断,就产生对应的数据。

而信号的产生就是用软件来模拟中断行为。我们的指令都是发出信号,

例如接口signal

可以发出我们需要的信号。

如下一段代码:

#include<iostream>
#include<unistd.h>
#include <signal.h>
#include <stdlib.h>void handler(int signo)
{std::cout<<"获得一个"<<signo<<"信号"<<std::endl;exit(1);
}int main()
{signal(2,handler);while(true){std::cout<<"pid:"<<getpid()<<",i am running......"<<std::endl;sleep(1);}return 0;
}

 再运行的时候,我们ctrl+z,此时退出进程就会获得一个为2的信号。

因此信号的产生可以通过键盘发出,对于我们的linux也是有许多信号的(kill -l):

其中,没有0号信号,从1-31的信号我们把它叫做普通信号,没有32,33信号,从34到64的信号,我们把它叫做实时信号。这些信号的本质就是一些函数指针数组,对应的下标就与他们的编号有关。

对于普通信号,进程是否收到了普通信号,操作系统(pcb中)会用一张位图来表示,利用位图中的第几个比特位表示编号,0表示没收到,1表示收到。

无论信号有多少种,都是只能让os来写(写)信号,因为os是进程的管理者。

了解到了信号的接收,因此我们在编写程序时就可以直接发送信号,之后自动运行对应handler方法,例如之前我们使用kill -9杀进程,现在我们发送一个为9的信号,此时自定义它的处理方法,例如只是打印一句话,那么我们kill -9的指令就不会再杀掉我们的进程,而是打印一句话。

但实际上并不可以,操作系统对于某些信号是不可以被自定义捕捉的。

除此以外,Linux提供了三种接口供我们产生信号。

方式一:通过键盘组合键发送产生信号。

方式二:通过函数接口

接口 raise 可以自己给自己发送任意信号

接口 abort  收到信号后终止运行

方式三,通过异常:

 以我们熟知的除零错误为例,首先除零错误并不是语言错误,而是进程错误,再cpu中通过各个寄存器来计算除零,此时cpu中还有表示状态的寄存器,当发生除零问题后,状态寄存器就会产生溢出标记位,从而转化为信号,就是信号8 SIGFPE  也就是flaot point exception。

当然发出信号也不仅仅可能是因为异常而导致的,也有可能是闹钟响了:

方式四:由软件条件产生信号:

alarm接口可以设置闹钟

#include<iostream>
#include<unistd.h>
#include <signal.h>
#include <stdlib.h>
int cnt=0;
void handler(int signo)
{std::cout<<"获得一个"<<signo<<"信号"<<"alarm is:"<<cnt<<std::endl;exit(1);
}int main()
{std::cout<<"pid:"<<getpid()<<std::endl;//本质上就是修改函数指针数组的位置signal(14,handler);//设置1s闹钟,到点了终止进程alarm(1);while(true){//cout<<cnt++<<endl;  可以看出外设是很慢的cnt++;}}

 操作系统的时间:

当我们电脑关机了,程序结束了,再次重新启动,我们会发现,时间永远是跟着走的,实际上,即使关机了,在电脑里也会有一个纽扣电池一直给硬件供电,固定时间间隔计数,再将计数器转换为时间戳给我们的电脑。CMOS周期性的高频的发送时间中断。

3.信号的保存

. 信号其他相关常见概念
实际执行信号的处理动作称为信号递达 (Delivery)
信号从产生到递达之间的状态 , 称为信号未决 (Pending)
进程可以选择阻塞 (Block ) 某个信号。
被阻塞的信号产生时将保持在未决状态 , 直到进程解除对此信号的阻塞 , 才执行递达的动作 .
注意 , 阻塞和忽略是不同的 , 只要信号被阻塞就不会递达 , 而忽略是在递达之后可选的一种处理动作

 递达就是开始处理信号,当信号被记录再为途中时就是信号未决状态,阻塞:被阻塞的信号一直处在未决状态,只有当阻塞取消时,才进入递达状态。

阻塞与忽略是有区别的,忽略本身没有阻塞而是递达,处理了信号,效果为忽略,而阻塞是没有抵达,且没处理。

了解了以上概念,因此再管理信号的状态时,os就需要维护这三张位图表,用来表示阻塞,未决,递达这三个状态的信号。

比特位的位置:代表信号的编号

比特位的内容:对特定信号进行阻塞还是屏蔽。 

每个信号都有两个标志位分别表示block(阻塞)和pending(未决),其次还有一个函数指针表示要处理的方法。

void handler(int signo)
{cout<<"signo is "<<signo<<endl;exit(1);
}
int main()
{//发送2信号signal(2,signo);//把信号的粗粒设置为原来默认的signal(2,SIG_DFL);//当然还可以把信号忽略signal(2,SIG_IGN);std::cout<<"my pid id:"<<getpid()<<endl;while(true){cout<<"i am running....."<<endl;sleep(1);}}

由于有这么多信号集,操作系统还提供了许多信号及操作接口:

sigset_t 类型对于每种信号用一个 bit 表示 有效 无效 状态 , 至于这个类型内部如何存储这些 bit 则依赖于系统 实现, 从使用者的角度是不必关心的 , 使用者只能调用以下函数来操作 sigset_ t 变量 , 而不应该对它的内部数据做 任何解释, 比如用 printf 直接打印 sigset_t 变量是没有意义的。
#include <signal.h>
int sigemptyset(sigset_t *set);  //对指定的位图进行清零
int sigfillset(sigset_t *set);   //对指定的位图进行置1
int sigaddset (sigset_t *set, int signo); //对指定信号添加到指定的位图中
int sigdelset(sigset_t *set, int signo);
int sigismember const sigset_t *set, int signo); //判定一个信号是否在为位图中

对于block表的修改:

sigprocmask 调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)

如下代码:


int main()
{//例如对2号信号屏蔽cout<<"my pid is"<<getpid()<<endl;//先定义两个信号集位图sigset_t block,oblock;//先对信号集清空sigemptyset(&block);sigemptyset(&oblock);//其次对2号信号添加到信号集sigaddset(&block,2);  //当前并没有让操作系统2信号屏蔽,只是语言层面的定义sigaddset(&oblock,2);sigprocmask(SIG_BLOCK,&block,&oblock);   //真正让操作系统屏蔽、更改信号while(true){sleep(1);}return 0;
}

 此时我们再发2号信号就没有作用了,ctrl+c也无法中断程序。

既然如此,那么我们是否可以将一个程序的所有信号屏蔽,这样他就有金刚不坏之身,谁也干不掉他,实际上并是不是所有的信号你都能屏蔽,就跟不是所有的信号的处理可以自定义是一样的。

比如说9号信号就无法被屏蔽。

那么pending表的修改:接口 sigpending

重要的是获取pending表.

接下来我们用一个整体的实例来认识这些接口:

void printpending(const sigset_t &pending)
{for(int signo=31;signo>0;signo--){if(sigismember(&pending,signo)){cout<<"1";}else{cout<<"0";}}cout<<"\n";
}
//自定义捕捉
void handler(int signo)
{cout<<"已接受到信号"<<signo<<endl;//exit(1);}int main()
{//例如对2号信号屏蔽cout<<"my pid is"<<getpid()<<endl;signal(2,handler);//先定义两个信号集位图sigset_t block,oblock;//先对信号集清空sigemptyset(&block);sigemptyset(&oblock);//其次对2号信号添加到信号集sigaddset(&block,2);  //当前并没有让操作系统2信号屏蔽,只是语言层面的定义sigaddset(&oblock,2);sigprocmask(SIG_BLOCK,&block,&oblock);   //真正让操作系统屏蔽、更改信号//下打印pending表int cnt=0;sigset_t pending;while(true){sigpending(&pending);printpending(pending);sleep(1);cnt++;if(cnt==5){//直到5S,解除2信号的屏蔽cout<<"解除对2号信号的屏蔽,2号准备抵达"<<endl;sigprocmask(SIG_SETMASK,&oblock,nullptr); //设置为旧的信号 }}return 0;
}

 运行结果如图:

4.信号的捕捉

信号在什么时候去被捕捉处理呢,在合适的时候---从内核态返回到用户态的时候,进行信号的检测和信号的处理。

内核态:内核态是操作系统的一种状态,能够大量访问资源

用户态:用户态是一种受控的转台,能够访问的资源是有限

用户想要访问操作系统只能通过系统调用的方式访问。

首先无论进程如何调度,cpu都会找到os,我们的进程的所有代码的执行,都可以在地址空间中通过跳转的方式进行调用和返回。

 那么对于系统的信号的捕捉,首先介绍第一个接口sigaction

第三个参数表示把旧的handler表返回给我,达尔戈参数就是新的handler的设置,第一个参数为信号编号,接口的作用是检测和修改信号动作。

返回类型是sigaction的结构体类型,其中有五个字段。其中我们比较重点关注的是sa_mask字段,

如果在调用信号处理函数时,除了当前信号被屏蔽外,还希望屏蔽些别的信号,此时sa_mask就是需要被额外屏蔽的信号。

以该代码为例:

#include<signal.h>
#include<unistd.h>
#include<iostream>
using namespace std;
void print(sigset_t &pending);
void handler(int signo)
{cout<<"接收到信号"<<signo<<"......"<<endl;while(true){//获取当前pending列表sigset_t pending;sigpending(&pending);print(pending);sleep(1);}
}
void print(sigset_t &pending)
{for(int signo=31;signo>0;signo--){if(sigismember(&pending,signo)){cout<<"1";}else{cout<<"0";}}cout<<endl;
}
int main()
{cout<<"my pid is "<<getpid()<<endl;//定义新的与旧的actstruct sigaction act,oact;//设置handler为当前自定义的处理方法act.sa_handler=handler;sigaction(2,&act,&oact);while(true) sleep(1);return 0;
}

用改接口接受2号信号时,和之前一样,运行程序,第一次我们ctrl+c,发出2信号时接收到2好信号,但自此之后的2好信号都被屏蔽掉了,再次crtl+c时,信号无法被接受处于未决状态。

例如:当我们要修改信号2时,这里默认会自动屏蔽信号2,如下图

信号的其它内容:

可重入函数

数被不同的控制流程调用 , 有可能在第一次调用还没返回时就再次进入该函数 , 这称 为重入,insert 函数访问一个全局链表 , 有可能因为重入而造成错乱 , 像这样的函数称为 不可重入函数 , 反之 , 如果一个函数只访问自己的局部变量或参数, 则称为可重入 (Reentrant) 函数。
如果一个函数符合以下条件之一则是不可重入的 :
调用了 malloc free, 因为 malloc 也是用全局链表来管理堆的。
调用了标准 I/O 库函数。标准 I/O 库的很多实现都以不可重入的方式使用全局数据
在这里我们就这样理解,住执行流与信号捕捉流是两种不同的流。
关键字volatile
volatile 作用:保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量 的任何操作,都必须在真实的内存中进行操作。
那么对于信号有什么作用呢?
int flag = 0;
void handler(int sig)
{printf("chage flag 0 to 1\n");flag = 1;
}
int main()
{signal(2, handler);while(!flag);printf("process quit normal\n");return 0;
}
优化情况下,键入 CTRL - C ,2 号信号被捕捉,执行自定义动作,修改 flag 1 ,但是 while 条件依旧满足 , 进 程继续运行!但是很明显flag 肯定已经被修改了,但是为何循环依旧执行?很明显, while 循环检查的 flag , 并不是内存中最新的flag ,这就存在了数据二异性的问题。 while 检测的 flag 其实已经因为优化,被放在了 CPU寄存器当中。如何解决呢?很明显需要 volatile。
实际中在gcc中,也是有自带优化的选项。

SIGCHLD信号

我们 早已经了解到子进程在退出的时候,是要给父进程发送退出信息的,不然父进程还要维护一份没必要的资源,而子进程是给父进程发送什么样的信号呢?---SIGCHLD

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void handler(int sig)
{pid_t id;//收到退出信号  等待子进程while( (id = waitpid(-1, NULL, WNOHANG)) > 0){printf("wait child success: %d\n", id);}printf("child is quit! %d\n", getpid());
}
int main()
{signal(SIGCHLD, handler);pid_t cid;if((cid = fork()) == 0){//childprintf("child : %d\n", getpid());sleep(3);exit(1);}while(1){printf("father proc is running\n");sleep(1);}return 0;
}

可以看到子进程退出时,时回给父进程发信号的。

在Linux中支持手动忽略信号SIGCHDL,可以不用wait子进程。退出自动回收。

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

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

相关文章

[计算机网络]--五种IO模型和select

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、五种IO…

线性规划问题的高斯消元法

线性规划的算法和解方程组的方法很像,常用的方程组的解法叫做高斯消元法,对于高斯消元法的基本流程,现给定一组线性方程: 添加图片注释,不超过 140 字(可选) 对于给定的线性方程组,目的是将方程组中同时能够满足三个等式的变量x,y,z求解出来,对于高斯消元法的基本过程…

【精通Spring】基于注解管理Bean

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大三在校生&#xff0c;喜欢AI编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;落798. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc;️…

集智书童 | YOLO+混合注意力机制 | YOLOv5再加4.3%才可以做对手,Transformer混合设计依旧可以卷

本文来源公众号“集智书童”&#xff0c;侵权删&#xff0c;干货满满。YOLOv5重出江湖&#xff01; 原文链接&#xff1a;https://mp.weixin.qq.com/s/vb7HsA0fKDgRc3uC8Z-2yw 在工业生产过程中&#xff0c;由于低效率、不统一的评估、高成本以及缺乏实时数据&#xff0c;传统…

C语言-指针(上)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 本篇文章将为大家介绍C语言中的核心内容-指针&#xff0c;指针在C语言的中知识内容比…

Java 小项目开发日记 04(文章接口的开发、oss图片上传)

Java 小项目开发日记 04&#xff08;文章接口的开发、oss图片上传&#xff09; 项目目录 配置文件&#xff08;pom.xml&#xff09; <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:sc…

机器学习:集成学习(Python)

一、Adaboost算法 1.1 Adaboost分类算法 adaboost_discrete_c.py import numpy as np import copy from ch4.decision_tree_C import DecisionTreeClassifierclass AdaBoostClassifier:"""adaboost分类算法&#xff1a;既可以做二分类、也可以做多分类&#…

uniapp的h5端在线预览文件

步骤如下&#xff1a; 1、下载需要准备的工具文件包 2、将其解压到/static/pdf文件夹下,如图&#xff1a; 3、创建在线查看文件的页面&#xff1a; <template><view><web-view :src"path"></web-view></view> </template>&l…

JavaScript练手小技巧:一文看懂<script>标签的 ansyc 和 defer

<script>标签的 ansyc 和 defer 属性。只对外部加载 JS 文件有效。 <script src"js/app.js" async></script> <script src"js/app.js" defer></script> 普通加载 js&#xff08;同步加载&#xff09;&#xff1a;会打断 …

Vue3 isProxy,isReactive,isReadonly 三者解析

1、isProxy 作用&#xff1a;判断当前数据是否为代理数据。 注意&#xff1a;它只对通过 reactive&#xff0c;readonly&#xff0c;shallowReactive&#xff0c;shallowReadonly 这四个方法包裹的数据返回true&#xff0c;对于 ref 以及通过 new Proxy 代理的数据返回都是fal…

ChatGPT科研与AI绘图及论文高效写作教程

原文链接&#xff1a;ChatGPT科研与AI绘图及论文高效写作教程 2023年随着OpenAI开发者大会的召开&#xff0c;最重磅更新当属GPTs&#xff0c;多模态API&#xff0c;未来自定义专属的GPT。微软创始人比尔盖茨称ChatGPT的出现有着重大历史意义&#xff0c;不亚于互联网和个人电…

HPE ProLiant MicroServer Gen8更换坏硬盘(RAID 1+0)

HPE ProLiant MicroServer Gen8今天硬盘告警&#xff0c;坏了一块硬盘&#xff08;估计还是由于上次突然断电导致的&#xff09;&#xff0c;关机&#xff0c;拆下坏硬盘&#xff0c;更换新硬盘&#xff0c;开机后按了一次F1键&#xff0c;系统继续启动并正常使用&#xff0c;同…

高性能MySQL 第4版

第一章MySQL架构 MySQL提供了多种锁的颗粒度&#xff0c;每种MySQL存储引擎都可以实现自己的锁策略和锁力度。 行级锁是在存储引擎而不是在服务器中实现的。 隔离界别 READ UNCOMMITTED - 脏读 在事务中可以可以查看到其他事务中还没有提交的修改。实际中很少用。 READ C…

Linux网络编程——socket 通信基础

Linux网络编程——socket 通信基础 1. socket 介绍2. 字节序2.1 简介2.2 字节序举例2.3 字节序转换函数 3. socket 地址3.1 通用 socket 地址3.2 专用 socket 地址 4. IP地址转换&#xff08;字符串ip -> 整数&#xff0c;主机、网络字节序的转换 &#xff09;5. TCP 通信流…

算法------(13)KMP

例题&#xff1a;&#xff08;1&#xff09;AcWing 831. KMP字符串 。。其实写完也不太理解。。随便写点吧 KMP就是求next数组和运用next的数组的过程。相比传统匹配模式一次更新一单位距离的慢速方法&#xff0c;next数组可以让下表字符串一次更新n - next【n】个距离&#x…

Java读取文件

读取文件为String 、访问链接直接跳转html 环境&#xff1a;SpringMVC 、前端jsp InputStreamReader FileInputStream fileInputStream new FileInputStream(formatFile.getHtmlpath());InputStreamReader reader new InputStreamReader(fileInputStream, StandardCharsets…

【EAI 026】RoboGen: 通过自动数据生成管线实现机器人技能学习

Paper Card 论文标题&#xff1a;RoboGen: Towards Unleashing Infinite Data for Automated Robot Learning via Generative Simulation 论文作者&#xff1a;Yufei Wang, Zhou Xian, Feng Chen, Tsun-Hsuan Wang, Yian Wang, Zackory Erickson, David Held, Chuang Gan 作者单…

C++:菱形继承问题

目录 1、什么是菱形继承 2、虚拟继承 3、一些常见问题 1. 什么是菱形继承&#xff1f;菱形继承的问题是什么&#xff1f; 2. 什么是菱形虚拟继承&#xff1f;如何解决数据冗余和二义性的 3. 继承和组合的区别&#xff1f;什么时候用继承&#xff1f;什么时候用组合&#…

Qt 自定义长条进度条(类似播放器进度条)

1.运行界面 2.步骤 其实很简单。 2.1绘制底图圆角矩形 2.2绘制播放进度圆角矩形 参考&#xff1a;painter绘图 3.源码 #pragma once#include <QWidget> #include <QLabel> #include <QHBoxLayout> #include <QMouseEvent> #include <QDebug&g…

Slicer学习笔记(六十五) 3DSlicer的医学图像数据增强扩展模块

1. 医学图像数据增强扩展模块 基于3D Slicer5.1.0 编写了一个测试医学图像的数据增强测试扩展模块。 扩展模块名&#xff1a;DataAugementation 项目地址&#xff1a;DataAugmentation 下载该项目后&#xff0c;可以将该扩展模块添加到3D Slicer的扩展中。 关于如何给3DSlicer…