Linux信号详解~

目录

前言

一、初识信号

二、信号的概念

三、信号的发送与捕捉

3.1 信号的发送

3.1.1 kill 命令

3.1.2 kill 函数

3.1.3 raise函数

3.1.4 abort函数

3.2 信号的捕捉

3.2.1 signal函数

3.2.2 sigaction函数

3.2.3 图示

四、信号的产生

4.1 硬件异常产生信号

4.2 软件条件产生信号

五、Core dump

5.1 core dump介绍

5.2 core dump作用

 六、阻塞信号

6.1 相关概念

6.2 在内核中的表示

6.3 sigprocmask

6.4 sigpending

七、可重入函数


前言

        在日常生活中,有许多方面都涉及到了信号的知识,例如信号弹、上下课铃声、红绿灯、闹钟等等。我们可以仔细想想由信号引发的几个问题:

  1. 我们是如何认识这些信号的?----有人教,随后记住了;
  2. 即使现在没有信号产生,我们也知道信号产生之后应该做什么
  3. 信号产生了,我们可能并不会立即处理这个信号,因为我们可能在做一些更重要的事情,由此可以得出信号产生后到信号处理之间其实会有一段时间窗口,而在这个时间窗口内,我们必须记住信号的到来。

        而在计算机当中,执行的主体就是进程,也就是说进程必须能够识别并处理信号,这是属于进程内置功能的一部分。同样的,一个进程由信号产生,到信号开始被处理,就一定会有时间窗口,而进程具有临时保存哪些信号已经发生了的能力。

一、初识信号

        在linux中运行某一个进程时,我们可以随时按下 ctrl+c 来杀掉前台进程,如下图一个死循环的输出我们用ctrl+c使进程退出:

它为什么能够杀掉前台进程呢?

        在Linux中的一次登录中,一个终端一般会配上一个bash,每一个登录只允许一个进程是前台进程,可以允许多个进程是后台进程。因此如果我们运行时在进程名后面加上&,则代表让它在后台运行,这个时候我们使用ctrl+c就没有用了,必须使用kill -9 + pid 号来杀掉进程。

        所以正常我们在运行一个进程时,在对bash进行输入命令就没有用了,因为这个进程运行时就成为了前台进程。

kill -l 可以查看操作系统拥有的信号:

        ctrl + c 的本质是被进程解释成为收到了2号信号SIGINT,需要注意的是,不同的操作系统可能对信号的编号有所不同,因此在跨平台开发时应当注意信号编号的兼容性。

        普通信号可以不立即处理,实时信号必须立即处理。

信号的处理方式:

        1. 忽略此信号。

        2. 执行该信号的默认处理动作。

        3. 自定义提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉 (Catch)一个信号。

二、信号的概念

        信号是 Linux 操作系统中用于进程间通信、处理异常等情况的一种机制。它是由操作系统向一个进程或者线程发送的一种异步通知,用于通知该进程或线程某种事件已经发生,需要做出相应的处理。

        信号的产生和我们自己的代码的运行是异步的,这意味着信号的产生与代码的执行没有直接的关联,信号属于软中断。

三、信号的发送与捕捉

3.1 信号的发送

在 Linux 中,进程可以通过向其他进程或自身发送信号的方式进行通信或处理异常情况。下面介绍几种常见的发送信号的方法。

3.1.1 kill 命令

kill [-signal] PID

其中,-signal 可选参数表示要发送的信号类型,如果省略该参数,则默认发送 SIGTERM 信号。PID 表示接收信号的进程 ID。

例如,要向进程 ID 123 发送 SIGINT 信号,可以执行以下命令:

kill -SIGINT 123

3.1.2 kill 函数

我们也可以使用系统调用的一些函数来发送信号:

其中,pid 表示接收信号的进程 ID,sig 表示要发送的信号类型。如果函数调用成功,则返回 0,否则返回 -1 并设置 errno。

例如,要向进程 ID 123 发送 SIGINT 信号,可以执行以下代码:

#include <signal.h>
#include <unistd.h>int main() {pid_t pid = 123;int sig = SIGINT;if (kill(pid, sig) == -1) {perror("kill");return 1;}return 0;
}

3.1.3 raise函数

raise 函数是一个简单的发送信号的函数,可以用来向当前进程发送信号。raise 函数的原型如下:

其中,sig 表示要发送的信号类型。如果函数调用成功,则返回 0,否则返回 -1 并设置 errno。

例如,要向当前进程发送 SIGTERM 信号,可以执行以下代码:

#include <signal.h>int main() {int sig = SIGTERM;if (raise(sig) == -1) {perror("raise");return 1;}return 0;
}

3.1.4 abort函数

abort函数的作用是引起一个正常函数的终止,它会给自己发送一个6号信号SIGABRT:

3.2 信号的捕捉

        在上文提到过,信号是可以被自定义捕捉的,下面介绍几种常见的捕捉信号的方法。

3.2.1 signal函数

signal 函数可以用来注册信号处理函数。signal 函数的原型如下:

其中,sig 表示要注册的信号类型handler 是一个函数指针,指向信号处理函数。signal 函数返回一个函数指针,指向之前注册的信号处理函数。如果注册信号处理函数失败,则返回 SIG_ERR。

例如,要注册 SIGINT 信号的处理函数,自定义处理函数名称为“sigcb”, 在sigcb当中完成打印触发本次事件的信号值,可以执行以下代码:

#include<iostream>
#include<signal.h>
#include <unistd.h>
using namespace std;
void sigcb(int signo)
{cout << "process get a SIGINT signal: " << signo <<endl;// exit(1);
}
int main()
{if (signal(SIGINT, sigcb) == SIG_ERR) {perror("signal");return 1;}while(true){cout<<"I am a process,pid: "<<getpid()<<endl;sleep(1);}return 0;
}

当我们按下ctrl+c时,可以看到程序输出,最后我们使用ctrl+\退出程序,运行结果如下:

3.2.2 sigaction函数

在Linux中,sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回 -1。

其中,sig 表示要注册的信号类型act 是一个指向 struct sigaction 结构体的指针,表示新的信号处理函数和信号处理选项,oldact 是一个指向 struct sigaction 结构体的指针,用于获取之前注册的信号处理函数和信号处理选项。

struct sigaction 结构体的定义如下:

struct sigaction {void (*sa_handler)(int);void (*sa_sigaction)(int, siginfo_t *, void *);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void);
};
  • 其中,sa_handler 字段指定信号处理函数的地址。如果设置为SIG_IGN,则表示忽略该信号。如果设置为SIG_DFL,则表示使用默认处理器,也可以自己设置需处理的函数逻辑。
  • sa_sigaction 字段指定一个信号处理器函数,这个函数包含三个参数:一个整数表示信号编号,一个指向siginfo_t结构体的指针,和一个指向void类型的指针。
  • sa_mask字段指定了在执行信号处理函数期间要阻塞哪些信号。
  • 后面两个字段本章不做详细解释。

例如,要注册 SIGINT 信号的处理函数,自定义处理函数名称为“sigcb”, 在sigcb当中完成打印触发本次事件的信号值,可以执行以下代码:

#include<iostream>
#include<signal.h>
#include <unistd.h>
using namespace std;
void sigcb(int signo)
{cout << "process get a SIGINT signal: " << signo <<endl;// exit(1);
}
int main()
{struct sigaction newact = {newact.sa_handler = sigcb};struct sigaction oldact;if (sigaction(SIGINT, &newact, &oldact) == -1) {perror("sigaction");return 1;}while(true){cout<<"I am a process,pid: "<<getpid()<<endl;sleep(1);}return 0;
}

3.2.3 图示

当我们的进程从内核态返回到用户态的时候,进行信号的检测和处理:

四、信号的产生

4.1 硬件异常产生信号

        硬件异常产生信号指硬件发现进程的某种异常,而硬件是被操作系统管理。硬件会将异常通知给系统,系统就会向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。

4.2 软件条件产生信号

        alarm函数相当于设置一个闹钟,告诉内核多少秒后,发送一个SIGALRM信号给当前进程,它的返回值是一个闹钟的剩余时间。

#include<iostream>
#include<signal.h>
#include <unistd.h>
using namespace std;
int main()
{int n=0;alarm(1);    //1秒后给进程发送SIGALRM信号while(true){cout<<n<<endl;n++;}return 0;
}

五、Core dump

5.1 core dump介绍

        当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump。但默认云服务器上面的core功能是被关闭的,我们可以使用ulimit-a 查看, ulimit-c +字节数 设置core文件大小:

 

        子进程的status可以当作位图来看,因此我们可以手写一段代码来提取出Core Dump的值,原理就是通过使用(status >> 8) & 0xFF 获取子进程的退出码(高8位),通过使用(status & 0x7F)获取子进程的退出信号(低7位),最后使用((status >> 7) & 1) 表达式用于判断是否发生了核心转储(第8位即core dump标志)。

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>using namespace std;int main()
{pid_t id = fork();if(id == 0){//childint cnt = 500;while(cnt){cout << "i am a child process, pid: " << getpid() << "cnt: " << cnt << endl;sleep(1);cnt--;}exit(0);}// fatherint status = 0;pid_t rid = waitpid(id, &status, 0);if(rid == id){cout << "child quit info, rid: " << rid << " exit code: " << ((status>>8)&0xFF) << " exit signal: " << (status&0x7F) <<" core dump: " << ((status>>7)&1) << endl; }return 0;
}

对于8号信号,默认云服务器上面的core功能是被关闭的,可以看到它的core dump为0,使用ulimit -c 设置文件大小以后,再运行可以看到core dump标志位变为1并且生成了core.pid号的文件:

5.2 core dump作用

        假设我们写一段除0的错误代码:

#include <iostream>
using namespace std;int main()
{int a = 10;int b = 0;a /= b;cout << " a = " << a << endl;return 0;
}

 运行后可以结合gdb来进行事后调试:

 六、阻塞信号

6.1 相关概念

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

6.2 在内核中的表示

在task_struct结构中信号的构成实质是两个位图和一个数组,我们看的顺序也是横着从左往右看。

  • 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。
  • 信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号被阻塞未产生过,当它递达时执行默认处理动作。
  • SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
  • SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。
  • 当一个信号被阻塞时,它仍然可以被发送到进程,并且会被添加到未决信号集合中。阻塞仅仅阻止信号的传递,即阻止信号的处理,但不阻止信号的接收。
  • 如果一个信号被设置为忽略,那么即使该信号被发送到进程,它也不会被添加到未决信号集合中,因为忽略的信号不会对进程产生任何影响。

6.3 sigprocmask

        函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集/BLOCK表),成功返回0,出错返回-1

how参数:

sigset_t 称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,它用来存储未决和阻塞标志。

set参数:指向一个信号集的指针,这个信号集指定了要阻塞或解除阻塞的信号。如果 set 是一个空指针(NULL),则 how 参数没有效果指向一个信号集的指针,这个信号集指定了要阻塞或解除阻塞的信号,是一个输入型函数。如果 set 是一个空指针(NULL),则 how 参数没有效果。

oldset参数:如果不是空指针(NULL),则进程的当前信号屏蔽字会被存储在 oset 指向的位置。如果 oset 是空指针,则不返回当前的信号屏蔽字,是一个输出型参数。

6.4 sigpending

        sigpending函数读取当前进程的未决信号集,通过set参数传出,即把调用进程所对应的pending表带出来,调用成功则返回0,出错则返回-1

 例如,可以使用下段代码屏蔽2号信号并通过打印pending表来观察:

#include<iostream>
#include<unistd.h>
#include<signal.h>
using namespace std;void PrintPending(sigset_t &pending)
{for(int signo=31;signo>=1;signo--){//存在打印1,否则为0if(sigismember(&pending,signo)){cout<<"1";}else{cout<<"0";}}cout<<endl;
}
int main()
{//阻塞2号新号       --数据预备sigset_t bset,oset;//函数sigemptyset初始化set所指向的信号集使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。sigemptyset(&bset); sigemptyset(&oset); //函数sigaddset在该信号集中添加2号信号sigaddset(&bset,2); //----系统调用,将数据设置进内核----sigprocmask(SIG_SETMASK,&bset,&oset);//重复打印pending信息便于观察sigset_t pending;while(true){//获取int n = sigpending(&pending);if(n<0) continue;//打印PrintPending(pending);sleep(1);}return 0;
}

注意,9号和19号信号是无法被屏蔽的:

七、可重入函数

如果一个函数在被重复进入的情况下不会出错,则是可重入函数,否则是不可重入函数。

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

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

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

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

相关文章

CMake Msys2 搭配vscode

(一)MSYS2介绍 MSYS2&#xff08;Minimal SYStem 2&#xff09;是一个集成了大量的GNU工具链、工具和库的开源软件包集合。它提供了一个类似于Linux的shell环境&#xff0c;可以在Windows系统中编译和运行许多Linux应用程序和工具。 MSYS2基于MinGW-w64平台&#xff0c;提供了…

Linux---进程间通信 | 管道 | PIPE | MKFIFO | 共享内存 | 消息队列

管道 管道是UNIX中最古老的进程间通信的形式&#xff0c;我们把从一个进程连接到另一个进程的数据流称为一个管道。 一个文件&#xff0c;可以被多个进程打开吗&#xff1f;可以&#xff0c;那如果一个进程打开文件&#xff0c;往文件里面写数据&#xff0c;另一个进程打开文…

MySQL 中 int(1) 和 int(10) 会影响存储的长度吗

一、MySQL 中 int(1) 和 int(10) 在MySQL数据库设计中&#xff0c;经常会遇到 int 类型的字段&#xff0c;并会习惯性的指定长度&#xff0c;比如&#xff1a; int(1) 和int(10)&#xff0c;而一些新手可能会误解它们之间的关系&#xff0c;认为 int(10) 能够存储更多的数据。…

Android Camera2 API 后台服务

最近在搞CameraAPP需要将Camera2弄成一个后台服务&#xff0c;发现跟预览的Activity没多大变动只是加了Service&#xff0c;和一些简单的修改。之前的公司也用到Camera2&#xff0c;发现用到的时候还是蛮多的所以记录一下&#xff0c;代码在文章末尾 camera2的结构如下&#x…

Peter算法小课堂—Dijkstra最短路算法

大家好&#xff0c;我们人见人爱、花见花开、车见车爆胎的Peter Pan来啦&#xff0c;hia~hia~hia。今天&#xff0c;我们今天来学习毒瘤的最短路算法啦。啊这……什么是Dijkstra算法&#xff1f;长文警告⚠ 正经点啊 手算样例 大家思考一下&#xff0c;你在手算样例的时候&am…

企业申请sectigo ip https证书

Sectigo&#xff08;原名Comodo&#xff0c;在整合https证书业务后改名为Sectigo&#xff09;是一家知名的数字证书提供商&#xff0c;拥有多种类型的数字证书&#xff0c;例如单域名https证书、多域名https证书、通配符https证书、IP https证书和代码签名证书等满足各类用户的…

2024022期传足14场胜负前瞻

2024022期赛事由英超4场&#xff0c;德甲2场、意甲4场、西甲4场组成。售止时间为2月4日&#xff08;周日&#xff09;19点00分&#xff0c;敬请留意&#xff1a; 本期中深盘较多&#xff0c;1.5以下赔率3场&#xff0c;1.5-2.0赔率7场&#xff0c;其他场次是平半盘、平盘。本期…

TCP 协议的相关特性

1. TCP格式 TCP特性&#xff1a;有连接&#xff0c;全双关&#xff0c;面向字节流&#xff0c;可靠传输。&#xff08;TCP安身立命的本钱&#xff0c;初心就是解决“可靠传输”问题&#xff09; 其实TCP的特征有很多这里我就简单的介绍几个。 2. 确认应答 其实用来确保可靠性&…

Java并发基础:CountDownLatch全面解析!

内容概要 CountDownLatch的优点在于能够简洁高效地协调多个线程的执行顺序&#xff0c;确保一组线程都完成后才触发其他线程的执行&#xff0c;适用于资源加载、任务初始化等场景。它提供了清晰的等待/通知机制&#xff0c;易于理解和使用&#xff0c;是提升多线程程序性能和可…

面试数据结构与算法总结分类+leetcode题目目录【基础版】

&#x1f9e1;&#x1f9e1;&#x1f9e1;算法题目总结&#xff1a; 这里为大家总结数据结构与算法的题库目录&#xff0c;如果已经解释过的题目会标注链接更新&#xff0c;方便查看。 数据结构概览 Array & String 大家对这两类肯定比较清楚的&#xff0c;同时这也是面试…

Java基础—反射

Java基础-反射 前置知识动态语言JVM堆Java引用变量类型编译时类型运行时类型举栗特殊情况 RRTI概念为什么需要RTTI例子 Class类对象前置知识类加载器概念作用 Class类对象的概念Class类对象的总结 总结一下前置知识 反射基础概念为什么要学反射我需要学到什么程度 反射的基础内…

GMT绘图笔记

(1)图框设置。在利用GMT绘制图件时&#xff0c;需要设置边框的类型&#xff0c;字体的大小&#xff0c;标记距离边框的距离。主要涉及的参数有&#xff1a; gmt set MAP_FRAME_TYPE plain/fancy 可以调整边框为火车轨道或者线段。 (2)调整图框的粗细&#xff1a;主要是包含有…

解决vue3+ts打包,ts类型检查报错导致打包失败,goview打包报错options

最近拉的开源大屏项目goview&#xff0c;在打包的过程中一直报Ts类型报错导致打包失败&#xff0c;项目的打包命令为&#xff1a; “build”: “vue-tsc --noEmit && vite build” 是因为 vue-tsc --noEmit 是 TypeScript 编译器&#xff08;tsc&#xff09;的命令&…

Mov转MP4怎么转换?如何播放mov视频?

MOV文件格式的使用场景 MOV文件格式以其支持多种媒体数据类型的特性而闻名&#xff0c;包括视频、音频、文本、动画等。它常用于存储包含视频剪辑、电影、音频轨道等多媒体元素的文件。由于其在质量和编辑方面的优越性&#xff0c;MOV文件在电影制作、广告宣传、多媒体演示等领…

MySQL篇----第三篇

系列文章目录 文章目录 系列文章目录前言一、InnoDB与MyISAM的区别二、索引三、常见索引原则有前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 一、InnoDB与MyISAM…

【华为】GRE VPN 实验配置

【华为】GRE VPN 实验配置 前言报文格式 实验需求配置思路配置拓扑GRE配置步骤R1基础配置GRE 配置 ISP_R2基础配置 R3基础配置GRE 配置 PCPC1PC2 抓包检查OSPF建立GRE隧道建立 配置文档 前言 VPN &#xff1a;&#xff08;Virtual Private Network&#xff09;&#xff0c;即“…

【京东云新品发布月刊】2024年1月产品动态来啦

1&#xff09;【莫奈可视化平台】新品上线 京东莫奈可视化平台通过自由拖拽、图形化编辑、所见即所得的方式&#xff0c;快速实现极致酷炫、直观清晰的视觉场景&#xff0c;将海量繁杂数据背后所蕴含的价值更直观、深层、全面的展现出来&#xff0c;辅助决策者合理决策。 2&a…

Redis集群环境搭建

Redis集群环境搭建 Redis主从复制 概念 主从复制是指将一台Redis服务器的数据&#xff0c;复制到其他的Redis服务器&#xff0c;前者称为主节点(master/leader)&#xff0c;后者称为从节点(slave/followe)&#xff1b;数据的复制是单向的&#xff0c;只能从主节点到从节点&a…

学成在线: 新增/修改课程计划

新增/修改课程计划(同接口) 界面原型 第一步: 在课程计划界面,点击添加章新增第一级课程计划,点击添加小节可以向某个第一级课程计划下添加小节 新增章/节成功后会自动发起请求刷新课程计划列表并且把新增的课程计划信息添加到数据库当中,新增的课程计划自动排序到最后 第二…

CentOS 8 下载

https://mirrors.bfsu.edu.cn/centos/8-stream/isos/x86_64/ 下载地址&#xff1a; https://mirrors.bfsu.edu.cn/centos/8-stream/isos/x86_64/CentOS-Stream-8-x86_64-latest-dvd1.iso